Skip to content

Latest commit

 

History

History
3177 lines (2571 loc) · 108 KB

File metadata and controls

3177 lines (2571 loc) · 108 KB

Linux ACPI 与电源管理深度解析

基于 Linux Kernel 源码深度分析 路径:drivers/acpi/ | kernel/power/ | drivers/cpufreq/


目录

  1. ACPI 概述:从 BIOS 到 ACPI 的演进
  2. ACPI 对象模型:命名空间与 AML 解释器
  3. ACPI 设备枚举:总线发现与设备树对比
  4. 系统电源状态(S states):S0 到 S5
  5. 设备电源状态(D states):D0 到 D3cold
  6. Runtime PM 框架:运行时电源管理
  7. cpufreq 框架:CPU 频率调节
  8. cpuidle 框架:CPU 空闲状态管理
  9. 系统 Suspend 流程(S3)
  10. Hibernate 流程(S4):swsusp 机制
  11. 热插拔与电源事件:电池与 AC 适配器
  12. drivers/acpi/kernel/power/ 主要文件一览
  13. 调试工具与接口
  14. ACPI 架构深度:ACPICA 集成与内核胶合层
  15. ACPI 表深度解析:FADT、MADT、DSDT、SSDT、MCFG
  16. AML 解释器:acpi_evaluate_object 全流程
  17. ACPI 中断模型:GPE 机制深度分析
  18. ACPI 全局电源状态:G0-G3 与 S0-S5 映射
  19. ACPI 设备枚举深度:scan.c 代码路径分析
  20. ACPI PCI 配置:MCFG 与 _OSC 协商深度分析
  21. ACPI platform bus 与 acpi_match_table
  22. ACPI 热管理深度:热区方法与 Linux thermal 框架集成
  23. ACPI C-state 与 P-state 深度:处理器电源状态
  24. ACPI 嵌入式控制器(EC)深度分析
  25. ACPI 唤醒机制:_PRW、GPE 与唤醒链路
  26. ACPI 电源资源管理:power.c 深度分析
  27. CPPC:协作式处理器性能控制
  28. 系统级电源管理:PM QoS 约束框架
  29. ACPI 与调度器协同:能效模型
  30. ACPI 固件接口演进:UEFI、_OSI 与兼容性

1. ACPI 概述:从 BIOS 到 ACPI 的演进

1.1 历史背景

在 ACPI 出现之前,系统电源管理由 BIOS 独自掌控,操作系统几乎无法干预。这种 APM(Advanced Power Management)模型存在明显局限:OS 缺乏对硬件细节的了解,无法做出最优的功耗决策。

1996 年,Intel、Microsoft、Toshiba 联合提出 ACPI(Advanced Configuration and Power Interface)规范,将电源管理控制权从 BIOS 转移到操作系统,固件仅提供硬件描述表和底层操作接口。

  旧模型(APM)                      新模型(ACPI)
+-------------+                   +-------------+
|    BIOS     |  <-- 控制权 -->   |     OS      |
| (电源决策)  |                   | (电源决策)  |
+-------------+                   +-------------+
      |                                  |
+-------------+                   +-------------+
|   Hardware  |                   |   Firmware  |
+-------------+                   | (仅描述表)  |
                                   +-------------+
                                          |
                                   +-------------+
                                   |   Hardware  |
                                   +-------------+

1.2 ACPI 核心表结构

ACPI 固件向操作系统提供一系列系统描述表(System Description Tables),这些表存储在物理内存中,通过 RSDP(Root System Description Pointer)定位。

RSDP (Root System Description Pointer)
  |
  +-- RSDT / XSDT (Root/Extended System Description Table)
        |
        +-- FADT (Fixed ACPI Description Table)
        |     |-- 指向 DSDT
        |     |-- 指向 FACS
        |     |-- 固定硬件寄存器地址
        |
        +-- DSDT (Differentiated System Description Table)
        |     |-- 完整的 AML 设备描述
        |     |-- _STA / _CRS / _PRS 方法
        |
        +-- SSDT (Secondary System Description Table, 可多个)
        |     |-- 补充设备描述(CPU、平台特定)
        |
        +-- MADT (Multiple APIC Description Table)
        |     |-- Local APIC / I/O APIC 信息
        |     |-- CPU 拓扑
        |
        +-- SRAT (System Resource Affinity Table)
        |     |-- NUMA 内存/CPU 亲和性
        |
        +-- MCFG (Memory-Mapped PCIe Config Table)
        +-- HPET (High Precision Event Timer)
        +-- SPCR (Serial Port Console Redirection)

主要表格说明:

表格 全称 作用
FADT Fixed ACPI Description Table 固定硬件寄存器位置、睡眠控制寄存器
DSDT Differentiated System Description Table 系统设备的完整 AML 描述
SSDT Secondary System Description Table 扩展 AML(CPU 状态、平台特定)
MADT Multiple APIC Description Table 中断控制器拓扑
SRAT System Resource Affinity Table NUMA 拓扑

ACPI 表解析入口在 drivers/acpi/tables.c,内核在 acpi_table_init() 阶段完成表的映射和验证。


2. ACPI 对象模型:命名空间与 AML 解释器

2.1 ACPI 命名空间

ACPI 命名空间是一个树状结构,以 \ 为根,描述系统中所有可被枚举和管理的对象(设备、方法、变量、电源资源等)。

\ (根命名空间)
|
+-- _SB  (System Bus)
|    +-- PCI0  (PCI 根桥)
|    |    +-- LPCB  (LPC 总线)
|    |    |    +-- EC0   (嵌入式控制器)
|    |    |    +-- HPET  (高精度定时器)
|    |    +-- GFX0  (图形设备)
|    |    +-- USB0  (USB 控制器)
|    |
|    +-- PWRB  (电源按钮)
|    +-- LID0  (盖子传感器)
|    +-- BAT0  (电池)
|    +-- ADP1  (AC 适配器)
|
+-- _PR  (处理器命名空间)
|    +-- CPU0  (处理器 0)
|    |    +-- _PSS  (P-state 表)
|    |    +-- _CST  (C-state 表)
|    +-- CPU1
|
+-- _SI  (System Indicators)
+-- _TZ  (Thermal Zone)
+-- _GPE (General Purpose Events)

命名空间中的每个节点被称为 ACPI 命名空间对象(namespace object),内核通过 acpi_handle 类型的句柄引用它们(见 include/linux/acpi.h)。

2.2 AML 解释器

AML(ACPI Machine Language)是 ACPI 定义的字节码语言,DSDT/SSDT 中的所有设备方法均以 AML 编写。内核内置了一个 AML 解释器(来自 ACPICA 开源实现,位于 drivers/acpi/acpica/),负责运行时求值这些方法。

ASL 源码 (人类可读)          AML 字节码 (固件中)
+----------------+   编译    +----------------+
| Device (BAT0) |  ------>  | Binary bytecode|
|   Method(_STA)|           |  (DSDT/SSDT)   |
|     Return(0xF)|          +----------------+
| }              |                  |
+----------------+          acpica 解释器 (内核)
                                    |
                             返回求值结果

2.3 常用 ACPI 控制方法

方法 用途
_STA 返回设备状态(存在/使能/可见/功能/电池)
_CRS Current Resource Settings,当前占用的资源(IO/IRQ/MEM)
_PRS Possible Resource Settings,设备可支持的资源范围
_SRS Set Resource Settings,设置资源
_HID Hardware ID,设备标识(如 PNP0C0A = 电池)
_CID Compatible ID,兼容 ID 列表
_UID Unique ID
_PSS Performance Supported States,P-state 列表
_CST C-State table,C-state 列表
_PTS Prepare To Sleep,进入睡眠前调用
_TTS Transition To State
_WAK Wake,从睡眠恢复后调用
_PSC Power State Current,读取当前设备电源状态
_PS0~_PS3 将设备切换到对应电源状态
_PRW Power Resources for Wake,唤醒所需电源资源

内核调用 _STA 的核心逻辑见 drivers/acpi/bus.c

// drivers/acpi/bus.c:77
acpi_status acpi_bus_get_status_handle(acpi_handle handle,
                                       unsigned long long *sta)
{
    acpi_status status;

    status = acpi_evaluate_integer(handle, "_STA", NULL, sta);
    if (ACPI_SUCCESS(status))
        return AE_OK;

    if (status == AE_NOT_FOUND) {
        // 没有 _STA 方法,默认认为设备存在且功能正常
        *sta = ACPI_STA_DEVICE_PRESENT | ACPI_STA_DEVICE_ENABLED |
               ACPI_STA_DEVICE_UI      | ACPI_STA_DEVICE_FUNCTIONING;
        return AE_OK;
    }
    return status;
}

_STA 返回值是一个位掩码:

  • Bit 0: 设备存在
  • Bit 1: 设备已使能
  • Bit 2: 设备在 UI 中可见
  • Bit 3: 设备功能正常
  • Bit 4: 电池存在

3. ACPI 设备枚举:总线发现与设备树对比

3.1 ACPI 总线驱动架构

ACPI 总线(acpi_bus_type)是 Linux 设备模型中的一种总线类型,负责将命名空间对象转化为内核设备对象(struct acpi_device)。

ACPI 命名空间 (AML)
        |
        | acpi_walk_namespace()
        v
+--------------------+
| acpi_scan.c        |
| acpi_add_single_   |
| object()           |  --> struct acpi_device 分配/初始化
+--------------------+
        |
        | acpi_scan_attach_handler()
        v
+--------------------+       +----------------------+
| acpi_scan_handler  |       | 匹配驱动 (platform / |
| (PNP0C0A = battery)|  -->  | i2c / spi / ...)     |
+--------------------+       +----------------------+
        |
        v
 /sys/bus/acpi/devices/

枚举的核心函数链(drivers/acpi/scan.c):

acpi_bus_scan()
  --> acpi_walk_namespace()
    --> acpi_add_single_object()      # 行 1859
      --> acpi_init_device_object()   # 行 1804
        --> acpi_set_pnp_ids()        # 解析 _HID / _CID / _CLS
        --> acpi_scan_init_status()   # 调用 _STA
      --> acpi_bus_get_power_flags()
      --> acpi_bus_get_wakeup_device_flags()
      --> device_add()                # 注册到 Linux 设备模型

acpi_init_device_object()drivers/acpi/scan.c:1804 将 ACPI 句柄、设备类型、总线类型等绑定在一起:

// drivers/acpi/scan.c:1804
void acpi_init_device_object(struct acpi_device *device, acpi_handle handle,
                             int type, void (*release)(struct device *))
{
    ...
    device->device_type = type;
    device->handle = handle;
    device->dev.bus = &acpi_bus_type;
    fwnode_init(&device->fwnode, &acpi_device_fwnode_ops);
    acpi_set_pnp_ids(handle, &device->pnp, type);
    acpi_init_properties(device);
    ...
}

3.2 热插拔设备发现

当设备被热插入时,ACPI 固件发送 Notify 事件(ACPI_NOTIFY_BUS_CHECKACPI_NOTIFY_DEVICE_CHECK),内核通过 acpi_device_hotplug() 函数(drivers/acpi/scan.c:442)响应:

// drivers/acpi/scan.c:422
static int acpi_generic_hotplug_event(struct acpi_device *adev, u32 type)
{
    switch (type) {
    case ACPI_NOTIFY_BUS_CHECK:
        return acpi_scan_bus_check(adev);
    case ACPI_NOTIFY_DEVICE_CHECK:
        return acpi_scan_device_check(adev);
    case ACPI_NOTIFY_EJECT_REQUEST:
        ...
        return acpi_scan_hot_remove(adev);
    }
}

3.3 ACPI 与 OF(设备树)的对比

维度 ACPI Device Tree (OF)
典型平台 x86 PC、服务器、UEFI ARM ARM 嵌入式、PowerPC
描述格式 AML 字节码(二进制) DTS 文本 → DTB 二进制
存储位置 固件闪存(UEFI 变量) 内核镜像附加 / 独立文件
运行时方法 支持(AML 解释器执行 _STA 等) 不支持(纯静态描述)
电源管理 深度集成(S/D/C states) 由驱动自行实现
设备 ID HID/CID 字符串(如 PNP0C0A compatible 字符串
内核接口宏 ACPI_COMPANION(dev) of_node
两者兼容 include/linux/acpi.h:58 提供 ACPI_COMPANION of_match_table

内核通过 fwnode 统一两种固件接口:

// include/linux/acpi.h:58
#define ACPI_COMPANION(dev)  to_acpi_device_node((dev)->fwnode)
#define ACPI_HANDLE(dev)     acpi_device_handle(ACPI_COMPANION(dev))

4. 系统电源状态(S states):S0 到 S5

ACPI 定义了全局系统电源状态(Sx states),描述整个平台的电源级别:

S0  全速运行
|
|  S1  CPU 停止,缓存仍供电(Standby)
|
|  S2  CPU 上下文丢失,设备仍供电
|
|  S3  Suspend-to-RAM (STR)
|      - 内存保持供电
|      - CPU/设备断电
|      - 恢复极快(通常 1-3 秒)
|
|  S4  Hibernate (Suspend-to-Disk)
|      - 内存内容写入 swap/hibernation file
|      - 全系统断电
|      - 恢复较慢(受磁盘速度影响)
|
S5  软件关机(Soft Off)
    - 系统完全断电
    - 仅保留 RTC 和唤醒逻辑供电

4.1 各睡眠状态比较

状态 功耗 恢复时间 内存供电 ACPI 名称
S0 全功耗 即时 Working
S1 较低 <1s Sleeping
S3 极低(~1W) 1-3s Suspend-to-RAM
S4 零(或微量) 10-60s Hibernate
S5 零(仅待机电源) 冷启动时间 Soft Off

内核睡眠状态枚举在 kernel/power/suspend.c:37

// kernel/power/suspend.c:37
const char * const pm_labels[] = {
    [PM_SUSPEND_TO_IDLE] = "freeze",   // S0ix / suspend-to-idle
    [PM_SUSPEND_STANDBY] = "standby",  // S1
    [PM_SUSPEND_MEM]     = "mem",      // S3
};

/sys/power/state 可写入 freezestandbymemdisk 来触发对应睡眠状态。

4.2 Suspend-to-Idle(S0ix)

现代平台(尤其是移动 ARM 和 Intel 低功耗平台)支持 S0ix(也称 suspend-to-idle 或 Modern Standby),它在不断开 CPU 上下文的情况下让系统进入极深的空闲状态:

S0ix = 冻结进程 + 挂起设备 + CPU 进入最深 C-state
       (不断开内存,不需要 BIOS 参与)

实现位于 kernel/power/suspend.cs2idle_loop() 函数(行 133),它将所有 CPU 推入 idle 循环并等待唤醒事件。

4.3 ACPI 睡眠状态支持检测

内核通过 acpi_sleep_state_supported() 函数(drivers/acpi/sleep.c:87)检测固件是否支持某个睡眠状态:

// drivers/acpi/sleep.c:87
bool acpi_sleep_state_supported(u8 sleep_state)
{
    acpi_status status;
    u8 type_a, type_b;

    status = acpi_get_sleep_type_data(sleep_state, &type_a, &type_b);
    return ACPI_SUCCESS(status) && (!acpi_gbl_reduced_hardware
        || (acpi_gbl_FADT.sleep_control.address
            && acpi_gbl_FADT.sleep_status.address));
}

5. 设备电源状态(D states):D0 到 D3cold

除系统级睡眠状态外,ACPI 为每个设备定义了独立的设备电源状态(Dx states):

D0  全功耗,设备完全工作
|
D1  部分功耗,保留部分设备状态(可选,较少使用)
|
D2  更低功耗(可选)
|
D3hot  设备低功耗,但仍在总线上(可通过软件唤醒)
|
D3cold 设备完全断电(需要重新初始化才能使用)

5.1 D states 与 S states 的映射

ACPI 规范定义了系统睡眠状态与设备电源状态的最低对应关系:

系统状态  设备最低状态
S0    --> D0(设备可以在任何 D state)
S1    --> D1 或更深
S3    --> D3hot 或 D3cold
S4    --> D3cold
S5    --> D3cold

5.2 ACPI 设备电源状态实现

drivers/acpi/device_pm.c 实现了 D-state 的查询和切换。以查询当前电源状态为例(行 75):

// drivers/acpi/device_pm.c:75
int acpi_device_get_power(struct acpi_device *device, int *state)
{
    ...
    // 通过电源资源推断状态
    if (device->power.flags.power_resources) {
        error = acpi_power_get_inferred_state(device, &result);
    }
    // 通过 _PSC 方法直接查询
    // drivers/acpi/device_pm.c:48
    // acpi_evaluate_integer(device->handle, "_PSC", NULL, &psc);
    ...
}

状态字符串映射(drivers/acpi/device_pm.c:30):

const char *acpi_power_state_string(int state)
{
    switch (state) {
    case ACPI_STATE_D0:     return "D0";
    case ACPI_STATE_D1:     return "D1";
    case ACPI_STATE_D2:     return "D2";
    case ACPI_STATE_D3_HOT: return "D3hot";
    case ACPI_STATE_D3_COLD:return "D3cold";
    default:                return "(unknown)";
    }
}

6. Runtime PM 框架:运行时电源管理

Runtime PM(运行时电源管理)允许设备在系统运行时(S0 状态下)根据使用情况动态挂起和恢复,是实现精细化功耗控制的关键机制。

6.1 核心架构

驱动调用
   |
   | pm_runtime_get(dev)   -- 请求唤醒/增加引用计数
   | pm_runtime_put(dev)   -- 释放引用/触发空闲检查
   v
+------------------------+
|   Runtime PM Core      |  (drivers/base/power/runtime.c)
|   usage_count (atomic) |
|   runtime_status       |
|     RPM_ACTIVE         |
|     RPM_IDLE           |
|     RPM_SUSPENDED      |
|     RPM_RESUMING       |
|     RPM_SUSPENDING     |
+------------------------+
   |          |
   |          | (autosuspend timer)
   v          v
+--------+  +------------+
|runtime |  | autosuspend|
|suspend |  | delay      |
|callback|  | mechanism  |
+--------+  +------------+
   |
   v
dev_pm_ops.runtime_suspend(dev)  -- 驱动提供的挂起回调
dev_pm_ops.runtime_resume(dev)   -- 驱动提供的恢复回调
dev_pm_ops.runtime_idle(dev)     -- 空闲检查回调

6.2 Runtime PM 状态机

                    pm_runtime_get()
    +------+       +---------------+       +---------+
    |      | <---- |               | ----> |         |
    | SUSP |       |   ACTIVE      |       |  IDLE   |
    |      | ----> |               | <---- |         |
    +------+       +---------------+       +---------+
       ^                                       |
       |           autosuspend_delay 到期      |
       +---------------------------------------+

6.3 关键 API

include/linux/pm_runtime.h 定义了完整的 Runtime PM API:

// include/linux/pm_runtime.h

// 增加引用计数(同步,若已挂起则先恢复)
int pm_runtime_get_sync(struct device *dev);

// 减少引用计数(同步,若为 0 则触发挂起)
int pm_runtime_put_sync(struct device *dev);

// 异步版本(不等待完成)
void pm_runtime_get(struct device *dev);
void pm_runtime_put(struct device *dev);

// 仅操作引用计数,不触发 resume
void pm_runtime_get_noresume(struct device *dev);  // 行 120
void pm_runtime_put_noidle(struct device *dev);    // 行 131

// 使能/禁用 Runtime PM
void pm_runtime_enable(struct device *dev);
void pm_runtime_disable(struct device *dev);

引用计数实现细节include/linux/pm_runtime.h:122):

static inline void pm_runtime_get_noresume(struct device *dev)
{
    atomic_inc(&dev->power.usage_count);
}

static inline void pm_runtime_put_noidle(struct device *dev)
{
    atomic_add_unless(&dev->power.usage_count, -1, 0);
}

6.4 Autosuspend 延迟机制

为避免设备频繁在 active/suspended 之间切换(抖动),Runtime PM 提供了 autosuspend 延迟机制。设备在最后一次活跃操作后,等待指定时间才真正挂起:

// 驱动初始化时设置 autosuspend 延迟(单位:毫秒)
pm_runtime_set_autosuspend_delay(dev, 2000);  // 2 秒后自动挂起
pm_runtime_use_autosuspend(dev);

// 驱动完成一次操作后标记"最后忙碌时间"
pm_runtime_mark_last_busy(dev);  // 更新 dev->power.last_busy
pm_runtime_put_autosuspend(dev); // 启动倒计时

pm_runtime_mark_last_busy() 实现(include/linux/pm_runtime.h:233):

static inline void pm_runtime_mark_last_busy(struct device *dev)
{
    WRITE_ONCE(dev->power.last_busy, ktime_get_mono_fast_ns());
}

autosuspend 到期时间检查(include/linux/pm_runtime.h)通过 pm_runtime_autosuspend_expiration() 计算。

6.5 dev_pm_ops 回调完整结构

include/linux/pm.h:288 定义了设备 PM 回调的完整结构:

struct dev_pm_ops {
    // 系统睡眠回调
    int (*prepare)(struct device *dev);
    void (*complete)(struct device *dev);
    int (*suspend)(struct device *dev);
    int (*resume)(struct device *dev);
    int (*freeze)(struct device *dev);       // hibernate 专用
    int (*thaw)(struct device *dev);         // hibernate 专用
    int (*poweroff)(struct device *dev);     // hibernate 专用
    int (*restore)(struct device *dev);      // hibernate 专用
    int (*suspend_late)(struct device *dev);
    int (*resume_early)(struct device *dev);
    int (*suspend_noirq)(struct device *dev);
    int (*resume_noirq)(struct device *dev);
    // ... freeze/thaw/poweroff/restore 的 late/noirq 变体

    // Runtime PM 回调
    int (*runtime_suspend)(struct device *dev);
    int (*runtime_resume)(struct device *dev);
    int (*runtime_idle)(struct device *dev);
};

便利宏include/linux/pm.h:314):

// 将同一对回调注册到所有系统睡眠场景
#define SYSTEM_SLEEP_PM_OPS(suspend_fn, resume_fn) \
    .suspend  = pm_sleep_ptr(suspend_fn), \
    .resume   = pm_sleep_ptr(resume_fn),  \
    .freeze   = pm_sleep_ptr(suspend_fn), \
    .thaw     = pm_sleep_ptr(resume_fn),  \
    .poweroff = pm_sleep_ptr(suspend_fn), \
    .restore  = pm_sleep_ptr(resume_fn),

// Runtime PM 回调
#define RUNTIME_PM_OPS(suspend_fn, resume_fn, idle_fn) \
    .runtime_suspend = suspend_fn, \
    .runtime_resume  = resume_fn,  \
    .runtime_idle    = idle_fn,

7. cpufreq 框架:CPU 频率调节

cpufreq 框架(drivers/cpufreq/cpufreq.c)提供了 CPU 动态电压频率调节(DVFS)的统一接口,是 CPU 性能与功耗平衡的核心机制。

7.1 整体架构

用户空间 (/sys/devices/system/cpu/cpuX/cpufreq/)
  |   scaling_governor  scaling_cur_freq  scaling_max_freq ...
  |
  v
+------------------+
|  cpufreq core    |  drivers/cpufreq/cpufreq.c
|  cpufreq_policy  |  -- 每组共享时钟的 CPU 一个 policy
|  governor 管理   |
+------------------+
       |           |
       |           v
       |    +-----------------+
       |    |  cpufreq_driver |  (硬件相关驱动)
       |    |  intel_pstate   |
       |    |  acpi-cpufreq   |
       |    |  cppc_cpufreq   |
       |    +-----------------+
       |           |
       v           v
+------------------+    硬件 P-state
|  Governor        |
|  performance     |  --> 始终最高频
|  powersave       |  --> 始终最低频
|  schedutil       |  --> 基于调度器负载
|  ondemand        |  --> 基于 CPU 使用率采样
|  conservative    |  --> 保守渐变
+------------------+

7.2 cpufreq_policy 结构

include/linux/cpufreq.h:53 定义了策略结构体,描述一组共享频率设置的 CPU:

struct cpufreq_policy {
    cpumask_var_t   cpus;          // 在线 CPU
    cpumask_var_t   related_cpus;  // 所有关联 CPU(包含离线)
    unsigned int    cpu;           // 管理此 policy 的 CPU 号
    struct cpufreq_cpuinfo cpuinfo;// min/max freq, transition latency
    unsigned int    min;           // 当前最低频率 (kHz)
    unsigned int    max;           // 当前最高频率 (kHz)
    unsigned int    cur;           // 当前频率 (kHz)
    unsigned int    suspend_freq;  // 睡眠时设置的频率
    struct cpufreq_governor *governor;
    struct cpufreq_frequency_table *freq_table; // 支持的频率表
    bool fast_switch_possible;     // 是否支持快速切换(调度器热路径)
    bool fast_switch_enabled;
    struct rw_semaphore rwsem;
    ...
};

7.3 cpufreq_driver 接口

include/linux/cpufreq.h:348 定义了 cpufreq 驱动必须实现的接口:

struct cpufreq_driver {
    char name[CPUFREQ_NAME_LEN];
    u16  flags;

    // 必须实现
    int (*init)(struct cpufreq_policy *policy);    // 初始化频率表
    int (*verify)(struct cpufreq_policy_data *);   // 验证并修正频率范围

    // 两者选其一
    int (*setpolicy)(struct cpufreq_policy *);     // 策略模式(无 governor)
    int (*target_index)(struct cpufreq_policy *, unsigned int index); // 表模式
    unsigned int (*fast_switch)(struct cpufreq_policy *, unsigned int); // 快速切换

    // 可选
    unsigned int (*get)(unsigned int cpu);         // 读取当前频率
    int (*suspend)(struct cpufreq_policy *);
    int (*resume)(struct cpufreq_policy *);
    void (*ready)(struct cpufreq_policy *);
    void (*adjust_perf)(unsigned int cpu,          // schedutil 热路径
                        unsigned long min_perf,
                        unsigned long target_perf,
                        unsigned long capacity);
};

7.4 Governor 分析

Governor 策略 适用场景
performance 始终使用最高频率 性能测试、编译构建
powersave 始终使用最低频率 极端省电、嵌入式
ondemand 基于 CPU 使用率采样动态调频 通用桌面(旧式)
conservative 类似 ondemand 但频率渐变 笔记本保守模式
schedutil 基于调度器 PELT 负载信号 现代推荐(默认)
userspace 由用户空间直接设定频率 测试/调试

schedutil 是当前推荐的 governor,它直接从调度器的 Per-Entity Load Tracking (PELT) 数据中获取 CPU 利用率,避免了额外的采样开销,实现了频率与负载的近实时响应。

7.5 P-states 与 Intel P-state 驱动

P-state(Performance State) 是 ACPI 定义的 CPU 工作频率/电压对。Intel 平台提供了专属的 intel_pstate 驱动(drivers/cpufreq/intel_pstate.c),相比通用 acpi-cpufreq 能够利用 HWP(Hardware-managed P-states)让硬件自主管理频率:

ACPI _PSS 表 (Software P-states)
  P0: 最高频率,最高电压
  P1: ...
  Pn: 最低频率,最低电压

Intel HWP (Hardware P-states)
  - CPU 自主在 HWP min/max 范围内调频
  - 无需软件介入,延迟更低
  - 通过 MSR_HWP_REQUEST 设置提示

cpufreq 驱动注册:

// 驱动注册入口
cpufreq_register_driver(&my_driver);  // include/linux/cpufreq.h:480

// 驱动注销
cpufreq_unregister_driver(&my_driver);

频率变更通知链(include/linux/cpufreq.h:521):

#define CPUFREQ_TRANSITION_NOTIFIER  (0)  // 频率变更通知
#define CPUFREQ_POLICY_NOTIFIER      (1)  // 策略创建/删除通知
#define CPUFREQ_PRECHANGE            (0)  // 变更前
#define CPUFREQ_POSTCHANGE           (1)  // 变更后

7.6 频率不变性(Frequency Invariance)

调度器的负载计算需要考虑 CPU 频率变化的影响。当 cpufreq_supports_freq_invariance() 返回 true 时(drivers/cpufreq/cpufreq.c:64),调度器会基于当前频率与最大频率的比值对计算量做归一化。


8. cpuidle 框架:CPU 空闲状态管理

当 CPU 没有可运行任务时,cpuidle 框架决定 CPU 进入哪种空闲状态(C-state),以在功耗节省与唤醒延迟之间取得平衡。

8.1 C-state 定义

C0  CPU 工作(执行指令)
|
C1  Halt - CPU 停止执行,但保持所有状态
|   退出延迟:~1us
|
C1E CPU 增强型 Halt(Intel)
|   退出延迟:~10us
|
C2  Stop Grant - CPU 时钟门控
|   退出延迟:~100us
|
C3  Sleep - 缓存可能刷出/失效
|   退出延迟:~200us
|
C6  Deep Power Down(Intel Nehalem+)
|   - CPU 状态保存到 PCU SRAM
|   - VCC 断电
|   退出延迟:~300us
|
C7 / C8 / C10  平台级睡眠(PC10 = Package C10)
    退出延迟:可达几毫秒

8.2 cpuidle 框架架构

调度器 idle 循环
  |
  | do_idle()
  v
+---------------------+
|   cpuidle_select()  |  -- 询问 governor 选择状态
+---------------------+
          |
          v
+---------------------+
|   cpuidle Governor  |
|   ladder governor   |  -- 按梯次逐步加深
|   menu governor     |  -- 基于期望空闲时长预测
|   teo governor      |  -- 基于定时器期望值预测
+---------------------+
          |  返回选中的状态索引
          v
+---------------------+
|  cpuidle_enter_     |  -- drivers/cpuidle/cpuidle.c:217
|  state()            |
+---------------------+
          |
          v
+---------------------+
|  cpuidle_driver     |
|  states[i].enter()  |  -- 硬件相关的进入函数
+---------------------+
          |
          v
    硬件 C-state 进入(HLT / MWAIT / ACPI _CST)

8.3 cpuidle_enter_state 核心流程

drivers/cpuidle/cpuidle.c:217

noinstr int cpuidle_enter_state(struct cpuidle_device *dev,
                                 struct cpuidle_driver *drv,
                                 int index)
{
    struct cpuidle_state *target_state = &drv->states[index];
    bool broadcast = !!(target_state->flags & CPUIDLE_FLAG_TIMER_STOP);
    ktime_t time_start, time_end;

    // 若需停止本地定时器,切换到广播定时器
    if (broadcast && tick_broadcast_enter()) {
        // 回退到不需要停止定时器的浅层状态
        index = find_deepest_state(..., CPUIDLE_FLAG_TIMER_STOP, false);
    }

    stop_critical_timings();
    ct_cpuidle_enter();
    entered_state = target_state->enter(dev, drv, index);
    ct_cpuidle_exit();
    start_critical_timings();

    // 统计实际驻留时间,供 governor 下次决策参考
    diff = ktime_sub(time_end, time_start);
    dev->last_residency_ns = diff;
    dev->states_usage[entered_state].time_ns += diff;
    dev->states_usage[entered_state].usage++;

    // 反馈:若实际时间远小于预期,记录 above(过深)
    // 若实际时间远大于延迟,记录 below(过浅)
    ...
}

8.4 Governor 比较

Governor 预测方式 优势 劣势
ladder 按使用频率逐步调整 简单可预测 反应慢,不适合突发负载
menu 预测下次唤醒时间 + 统计校正 精确度高 计算量略大
teo (Timer Events Oriented) 基于定时器到期时间 适合高精度场景 需要 hrtimer 支持

8.5 ACPI 与 cpuidle 的集成

drivers/acpi/processor_idle.c 实现了 ACPI 处理器 C-state 驱动,注册了 acpi_idle_driver(行 54):

static struct cpuidle_driver acpi_idle_driver = {
    .name  = "acpi_idle",
    .owner = THIS_MODULE,
};

它从 ACPI _CST(C-State Table)方法读取硬件支持的 C-state 列表,并注册到 cpuidle 框架。现代 Intel 平台通常使用更精准的 intel_idle 驱动代替 acpi_idle

8.6 C-state 对齐与 Package C-state

当系统中所有 CPU 同时处于足够深的 C-state,整个 CPU Package(封装)才能进入 Package C-state(PC6/PC8/PC10),此时节能效益最大。cpuidle 框架和 menu/teo governor 会考虑这种协同效应。


9. 系统 Suspend 流程(S3)

系统 suspend 是将整个系统状态保存于内存、关闭大部分硬件的流程。

9.1 完整流程图

用户写入 /sys/power/state = "mem"
              |
              v
       kernel/power/main.c
       pm_suspend(PM_SUSPEND_MEM)
              |
              v
  +---------------------------+
  |  suspend_prepare()        |  kernel/power/suspend.c:372
  |  1. pm_prepare_console()  |
  |  2. PM_SUSPEND_PREPARE    |  通知链
  |  3. filesystems_freeze()  |
  |  4. suspend_freeze_       |  冻结所有用户态任务(SIGSTOP)
  |     processes()           |  和内核线程
  +---------------------------+
              |
              v
  +---------------------------+
  |  suspend_devices_and_     |  kernel/power/suspend.c:504
  |  enter()                  |
  |  1. console_suspend_all() |
  |  2. dpm_suspend_start()   |  挂起设备(深度优先)
  +---------------------------+
              |
              v
  +---------------------------+
  |  suspend_enter()          |  kernel/power/suspend.c:419
  |  1. dpm_suspend_late()    |  设备 late suspend
  |  2. dpm_suspend_noirq()   |  禁止中断后的 suspend
  |  3. 关闭副 CPU            |
  |  4. 禁止中断              |
  |  5. syscore_suspend()     |  系统核心(RTC等)suspend
  |  6. suspend_ops->enter()  |  ACPI 进入 S3
  +---------------------------+
              |       (系统睡眠)
              |
              v (唤醒事件)
  +---------------------------+
  |  Resume(反向执行)       |
  |  1. syscore_resume()      |
  |  2. 使能中断              |
  |  3. 重新上线副 CPU        |
  |  4. dpm_resume_noirq()    |
  |  5. dpm_resume_early()    |
  |  6. dpm_resume_end()      |  恢复所有设备
  |  7. console_resume_all()  |
  |  8. 解冻进程              |
  |  9. PM_POST_SUSPEND 通知  |
  +---------------------------+

9.2 设备挂起阶段分层

设备挂起分多个阶段,以处理不同时机的依赖关系:

dpm_suspend_start()
  --> prepare()       每个设备的 prepare 回调
  --> suspend()       主 suspend 回调(中断仍开启)

dpm_suspend_late()
  --> suspend_late()  在所有 suspend() 完成后,中断仍开启

dpm_suspend_noirq()
  --> suspend_noirq() 禁止中断后的最终 suspend
                      此时设备必须静止(无 DMA/IO)

恢复时反向执行:resume_noirqresume_earlyresumecomplete

9.3 ACPI 平台 suspend 实现

ACPI 通过 drivers/acpi/sleep.c 提供 platform_suspend_ops,在最终阶段调用:

// drivers/acpi/sleep.c:67
static int acpi_sleep_prepare(u32 acpi_state)
{
    // 对于 S3,设置唤醒向量
    if (acpi_state == ACPI_STATE_S3) {
        acpi_wakeup_address = acpi_get_wakeup_address();
        acpi_set_waking_vector(acpi_wakeup_address);
    }
    acpi_enable_wakeup_devices(acpi_state);
    acpi_enter_sleep_state_prep(acpi_state);  // 执行 _PTS 方法
    return 0;
}

_TTS(Transition To State)方法会在状态切换时被调用(drivers/acpi/sleep.c:36):

static void acpi_sleep_tts_switch(u32 acpi_state)
{
    status = acpi_execute_simple_method(NULL, "\\_TTS", acpi_state);
    ...
}

9.4 NVS 内存保存

ACPI 要求在 S3/S4 时保存 NVS(Non-Volatile Sleep)内存区域(固件使用的特定内存),以确保恢复后固件状态一致。内核通过 drivers/acpi/nvs.c 实现这一功能,可通过内核命令行参数 acpi_sleep=nonvs 禁用(drivers/acpi/sleep.c:116)。


10. Hibernate 流程(S4):swsusp 机制

Hibernate(休眠,即 suspend-to-disk)将全部内存内容序列化写入磁盘,然后完全关机。恢复时从磁盘读回内存快照并继续运行。

10.1 Hibernate 写入流程

用户写入 /sys/power/disk = "platform"
         /sys/power/state = "disk"
              |
              v
kernel/power/hibernate.c
hibernate()
  |
  +-- hibernate_acquire()     原子锁,防止并发
  |
  +-- pm_notifier_call_chain(PM_HIBERNATION_PREPARE)
  |
  +-- suspend_freeze_processes()   冻结所有任务
  |
  +-- platform_begin()          hibernation_ops->begin()
  |
  +-- dpm_suspend_start(PMSG_FREEZE)   挂起设备(freeze 语义)
  |
  +-- create_image()            ---- 核心步骤 ----
  |     |
  |     +-- dpm_suspend_end(PMSG_FREEZE)   设备 late/noirq freeze
  |     |
  |     +-- platform_pre_snapshot()
  |     |
  |     +-- pm_sleep_disable_secondary_cpus()  关闭副 CPU
  |     |
  |     +-- local_irq_disable()
  |     +-- syscore_suspend()
  |     |
  |     +-- in_suspend = 1
  |     +-- save_processor_state()
  |     +-- swsusp_arch_suspend()   <-- 创建内存快照
  |     |   (控制流在此"分叉":
  |     |    写路径:in_suspend=1,继续往下写快照
  |     |    读路径:in_suspend=0,从此处恢复)
  |     |
  |     +-- restore_processor_state()  (仅写路径)
  |
  +-- swsusp_write()           将快照写入 swap 分区
  |   (kernel/power/swap.c)
  |
  +-- power_down()             关机(platform / shutdown / reboot)

create_image() 中的 swsusp_arch_suspend()kernel/power/hibernate.c:358)是关键分叉点——该函数在写快照后返回 0,在从快照恢复时通过特殊机制让 in_suspend 值为 0,从而区分两种控制流:

// kernel/power/hibernate.c:355
in_suspend = 1;
save_processor_state();
error = swsusp_arch_suspend();
/* Restore control flow magically appears here */
restore_processor_state();
if (!in_suspend) {
    // 这里是从磁盘恢复后的路径
    events_check_enabled = false;
}

10.2 Hibernate 恢复流程

系统启动(冷启动)
  |
  v
内核启动,initramfs 检测到 resume= 参数
  |
  v
kernel/power/hibernate.c
software_resume()
  |
  +-- swsusp_check()           检查 swap 设备上的快照标记
  |
  +-- dpm_suspend_start(PMSG_QUIESCE)  挂起设备
  |
  +-- hibernation_restore(0)
  |     |
  |     +-- pm_sleep_disable_secondary_cpus()
  |     +-- syscore_suspend()
  |     |
  |     +-- swsusp_read()       从 swap 读取快照页面
  |     +-- hibernation_restore_memory_image()
  |     |   将快照页面恢复到原始物理地址
  |     |
  |     +-- restore_image()
  |         swsusp_arch_resume()  跳回 create_image() 中
  |                               in_suspend=0 的位置
  |
  +-- (控制流回到写入时的 create_image 内部)
      --> dpm_resume_start(PMSG_RESTORE)
      --> 恢复设备、解冻进程、通知用户空间

10.3 快照压缩

kernel/power/hibernate.c:51 显示快照支持压缩:

static char hibernate_compressor[CRYPTO_MAX_ALG_NAME] = CONFIG_HIBERNATION_DEF_COMP;
char hib_comp_algo[CRYPTO_MAX_ALG_NAME];

默认压缩算法由 CONFIG_HIBERNATION_DEF_COMP 配置(通常为 lzo),可以显著减少写入 swap 的数据量,加快恢复速度。

10.4 swsusp 的内存布局

物理内存
+------------------+
| 内核代码/数据    |  -- nosave(不保存,恢复后重新使用新内核)
+------------------+
| 页表、struct page| -- nosave
+------------------+
| 驱动/模块数据    | -- 保存(标记为 PAGE_SAVE)
+------------------+
| 用户进程内存     | -- 保存
+------------------+
| 快照自身的内存   | -- 保存(存放压缩后的快照数据)
+------------------+

11. 热插拔与电源事件:电池与 AC 适配器

11.1 ACPI Notify 机制

ACPI 固件通过 Notify 事件通知 OS 设备状态变化。内核在 drivers/acpi/scan.c 中为每个设备注册了通用的 notify 处理器。

硬件事件(插拔、状态变化)
          |
          v
    ACPI EC 中断
          |
          v
   ACPI Notify(device, code)
   (code 如 0x80 = 状态变化, 0x01 = BUS_CHECK)
          |
          v
acpi_device_hotplug()  -- scan.c:442
  |
  +-- acpi_generic_hotplug_event()
  |     ACPI_NOTIFY_BUS_CHECK    --> acpi_scan_bus_check()
  |     ACPI_NOTIFY_DEVICE_CHECK --> acpi_scan_device_check()
  |     ACPI_NOTIFY_EJECT_REQUEST-> acpi_scan_hot_remove()
  |
  +-- 自定义 hp->notify()  (注册了 hotplug context 的设备)

11.2 电池驱动

drivers/acpi/battery.c 实现 ACPI 电池驱动,支持的设备 ID(行 60):

static const struct acpi_device_id battery_device_ids[] = {
    {"PNP0C0A", 0},     // 标准 ACPI 电池
    {"MSHW0146", 0},    // Microsoft Surface Go 3
    {"", 0},
};

电池驱动通过以下 ACPI 方法获取信息:

ACPI 方法 含义
_BIF / _BIX Battery Information (Extended) - 容量、化学类型
_BST Battery Status - 当前电量、电流、电压
_BTP Battery Trip Point - 设置电量报警阈值
_STA 电池是否存在

电池状态标志(drivers/acpi/battery.c:40):

#define ACPI_BATTERY_STATE_DISCHARGING      0x1  // 放电中
#define ACPI_BATTERY_STATE_CHARGING         0x2  // 充电中
#define ACPI_BATTERY_STATE_CRITICAL         0x4  // 电量危急
#define ACPI_BATTERY_STATE_CHARGE_LIMITING  0x8  // 充电限制

Notify 代码 0x80 触发状态更新,驱动向 power_supply 子系统上报新状态,最终通过 uevent 通知用户空间(如 UPower)。

11.3 AC 适配器驱动

drivers/acpi/ac.c 实现 AC 适配器驱动(设备 ID:ACPI0003,行 41):

static const struct acpi_device_id ac_device_ids[] = {
    {"ACPI0003", 0},
    {"", 0},
};

通过 _PSR(Power Source)方法查询 AC 在线状态(行 78):

status = acpi_evaluate_integer(ac->device->handle, "_PSR", NULL, &ac->state);

0x80 Notify 事件触发 acpi_ac_notify() 回调,驱动更新 power_supply 状态并发送 uevent。

11.4 电源事件处理总线

ACPI EC (Embedded Controller)
         |
         | EC Query (Q-event)
         v
+------------------+      +----------------+
| ACPI EC driver   | ---> | 电源按钮驱动   |
| (drivers/acpi/   |      | (button.c)     |
|  ec.c)           |      +----------------+
|                  |      +----------------+
|                  | ---> | 电池驱动       |
|                  |      | (battery.c)    |
|                  |      +----------------+
|                  |      +----------------+
|                  | ---> | AC 适配器驱动  |
+------------------+      | (ac.c)         |
                           +----------------+
                                  |
                           power_supply 子系统
                                  |
                           uevent --> udev --> UPower
                                               --> GNOME/KDE

12. drivers/acpi/kernel/power/ 主要文件一览

12.1 drivers/acpi/ 目录

文件 职责
bus.c ACPI 总线驱动核心,_STA 查询,OSC 协商
scan.c ACPI 命名空间扫描,设备对象创建,热插拔处理
sleep.c ACPI 睡眠状态支持(S3/S4/S5),_PTS/_WAK/_TTS
device_pm.c 设备 D-state 管理,_PSC/_PS0-3/_PRW
power.c ACPI 电源资源管理(Power Resources _PR0-3
processor_idle.c ACPI C-state(_CST),注册到 cpuidle 框架
processor_perflib.c ACPI P-state(_PSS/_PPC/_PCT
cppc_acpi.c CPPC(Collaborative Processor Performance Control)
battery.c 电池驱动(_BIF/_BST/_BTP
ac.c AC 适配器驱动(_PSR
button.c 电源键/盖子传感器(_LID
thermal.c 热管理(_TMP/_PSV/_CRT
fan_core.c 风扇控制(_FIF/_FST/_FSL
ec.c 嵌入式控制器驱动(ACPI EC Protocol)
glue.c ACPI 与 Linux 设备模型的胶合层
property.c ACPI 设备属性(_DSD)解析
tables.c ACPI 系统描述表解析
sysfs.c ACPI sysfs 接口(/sys/firmware/acpi/
nvs.c NVS 内存区域保存/恢复
wakeup.c 唤醒设备管理(_PRW
dock.c 扩展坞热插拔
pci_root.c PCI 根桥枚举

12.2 kernel/power/ 目录

文件 职责
main.c /sys/power/ sysfs 接口,pm_suspend() 入口
suspend.c S3 suspend 完整流程,suspend_devices_and_enter()
hibernate.c S4 hibernate 完整流程,create_image()
snapshot.c 内存快照(swsusp):创建/恢复内存镜像
swap.c 快照读写(swap 分区/文件)
process.c 进程冻结/解冻(suspend_freeze_processes()
autosleep.c 自动睡眠(Android wakelock 对应的 Linux 机制)
wakelock.c wake lock 实现(CONFIG_PM_WAKELOCKS
qos.c PM QoS(延迟约束)框架
energy_model.c 能效模型(供调度器和 cpufreq 使用)
console.c suspend 期间控制台管理

12.3 drivers/cpufreq/ 重要文件

文件 职责
cpufreq.c cpufreq 框架核心,policy 管理,notifier
intel_pstate.c Intel HWP/P-state 驱动
acpi-cpufreq.c 通用 ACPI P-state 驱动(_PSS
cppc_cpufreq.c CPPC 频率驱动(ARM/x86 服务器)
cpufreq_schedutil.c schedutil governor
cpufreq_ondemand.c ondemand governor
cpufreq_conservative.c conservative governor
cpufreq_performance.c performance governor
cpufreq_powersave.c powersave governor

13. 调试工具与接口

13.1 /sys/power/ 接口

/sys/power/
  state             - 读取支持的睡眠状态,写入触发睡眠
  mem_sleep         - 选择 mem 状态的具体实现(s2idle/shallow/deep)
  disk              - 选择 hibernate 方式(platform/shutdown/reboot/suspend)
  image_size        - 限制 hibernate 快照最大尺寸(字节)
  pm_test           - 测试 suspend 流程但不真正进入睡眠
  pm_async          - 是否异步并行挂起设备(默认 1)
  pm_print_times    - 打印各设备 suspend/resume 耗时
  pm_debug_messages - 开启 PM 详细调试输出
  wakeup_count      - 防止竞态的唤醒计数器
  reserved_size     - 为 hibernate 保留的内存大小
  autosleep         - 自动睡眠机制(Android 特性,需配置开启)

13.2 /sys/devices/.../power/ 接口

每个设备在 sysfs 下都有 power/ 目录:

/sys/devices/pci0000:00/0000:00:1f.2/power/
  runtime_status        - active / suspended / error
  runtime_usage         - 引用计数值
  runtime_active_time   - 活跃时间统计(ms)
  runtime_suspended_time- 挂起时间统计(ms)
  control               - auto / on(禁用 runtime PM)
  autosuspend_delay_ms  - autosuspend 延迟配置
  wakeup               - 是否允许作为唤醒源
  wakeup_count          - 此设备触发唤醒的次数

13.3 /sys/devices/system/cpu/ 接口

/sys/devices/system/cpu/cpuX/cpufreq/
  scaling_governor      - 当前 governor(可写)
  scaling_cur_freq      - 当前频率(kHz)
  scaling_min_freq      - 最低允许频率(可写)
  scaling_max_freq      - 最高允许频率(可写)
  scaling_available_governors - 所有可用 governor
  cpuinfo_cur_freq      - 硬件实际频率
  cpuinfo_min_freq      - 硬件支持最低频率
  cpuinfo_max_freq      - 硬件支持最高频率
  cpuinfo_transition_latency - 频率切换延迟(ns)
  stats/time_in_state   - 在各频率下的驻留时间

/sys/devices/system/cpu/cpuX/cpuidle/
  stateN/name           - C-state 名称
  stateN/desc           - C-state 描述
  stateN/latency        - 退出延迟(us)
  stateN/power          - 预估功耗(mW)
  stateN/time           - 在此状态的总驻留时间(us)
  stateN/usage          - 进入此状态的次数
  stateN/disable        - 禁用此状态(写 1)

13.4 PowerTOP

PowerTOP 是 Intel 开发的功耗分析工具,提供:

  • 每个设备/进程的唤醒次数统计
  • CPU C-state 利用率分布
  • 各 C-state 驻留时间比例
  • 电源调优建议(可自动应用 --auto-tune
PowerTOP 数据来源:
  /proc/timer_stats      --> 定时器唤醒源
  /sys/kernel/debug/     --> ftrace 事件
  perf_event             --> CPU C-state 硬件计数器

13.5 turbostat

Intel turbostat 工具直接读取 CPU MSR 寄存器,提供比 sysfs 更精准的功耗数据:

turbostat 输出示例:
  CPU  Avg_MHz  Busy%  Bzy_MHz  TSC_MHz  IPC  IRQ  C1%  C6%  Pkg%
   0     523    13.5   3871     3600    1.23  100  23.1  63.4  ...
   1     0       0.0   0        3600    0.00  0    12.3  87.7  ...

数据来源:MSR(Model Specific Registers)如 IA32_MPERFIA32_APERF、Package C-state 残差计数器。

13.6 ftrace / perf 电源事件追踪

内核在关键路径埋入了 tracepoint(trace/events/power.h):

# 追踪 CPU 频率变化
echo 1 > /sys/kernel/debug/tracing/events/power/cpu_frequency/enable

# 追踪 CPU idle 状态进出
echo 1 > /sys/kernel/debug/tracing/events/power/cpu_idle/enable

# 追踪 suspend/resume 各阶段耗时
echo 1 > /sys/kernel/debug/tracing/events/power/suspend_resume/enable

# 追踪设备 runtime PM 状态变化
echo 1 > /sys/kernel/debug/tracing/events/power/rpm_suspend/enable
echo 1 > /sys/kernel/debug/tracing/events/power/rpm_resume/enable

13.7 PM 调试选项

内核配置中的关键调试选项:

配置项 功能
CONFIG_PM_DEBUG 开启 pm_test 功能和详细日志
CONFIG_PM_ADVANCED_DEBUG 设备 PM 状态跟踪
CONFIG_PM_SLEEP_DEBUG suspend/resume 路径调试
CONFIG_ACPI_DEBUG ACPI 子系统详细日志
CONFIG_CPU_FREQ_DEBUG cpufreq 调试日志

运行时调试:

# 查看 suspend 各设备耗时(需 CONFIG_PM_DEBUG)
echo 1 > /sys/power/pm_print_times

# 测试 suspend 流程到 devices 层(不进入睡眠)
echo devices > /sys/power/pm_test
echo mem > /sys/power/state

# 查看 ACPI 调试信息
echo 0x80000000 > /sys/module/acpi/parameters/debug_layer
echo 0x08000000 > /sys/module/acpi/parameters/debug_level

14. ACPI 架构深度:ACPICA 集成与内核胶合层

14.1 ACPICA 的角色与集成方式

ACPICA(ACPI Component Architecture)是由 Intel 主导开发、与 Linux 内核同步维护的开源 ACPI 实现库。内核将其完整地包含在 drivers/acpi/acpica/ 目录下,使用时通过一个平台抽象层(OSL,OS Services Layer)与内核基础设施对接。

+-----------------------------------------------+
|               Linux 内核上层                   |
|  drivers/acpi/*.c  (bus.c, scan.c, sleep.c...) |
+-------------------+--------------------------+
|  ACPICA 公共接口层                            |
|  acpi_evaluate_object()  acpi_walk_namespace() |
|  acpi_install_gpe_handler()  acpi_get_handle() |
+-------------------+--------------------------+
|         ACPICA 内部实现                        |
|  drivers/acpi/acpica/                          |
|  +-------------+  +----------+  +-----------+ |
|  | Namespace   |  | AML      |  | Hardware  | |
|  | nsxfeval.c  |  | Interp   |  | hw*.c     | |
|  | nsaccess.c  |  | exoparg*.c|  | hwacpi.c  | |
|  +-------------+  +----------+  +-----------+ |
|  +-------------+  +----------+  +-----------+ |
|  | Tables      |  | Events   |  | Resources | |
|  | tbfadt.c    |  | evgpe.c  |  | rsaddr.c  | |
|  | tbxface.c   |  | evgpeblk |  | rsio.c    | |
|  +-------------+  +----------+  +-----------+ |
+-------------------+--------------------------+
|  OSL(平台抽象层)drivers/acpi/osl.c           |
|  acpi_os_map_memory()  acpi_os_sleep()         |
|  acpi_os_create_lock()  acpi_os_execute()      |
+-----------------------------------------------+

ACPICA 的 OSL 实现位于 drivers/acpi/osl.c,将 ACPICA 对内存映射、锁、定时器、工作队列的需求翻译为内核对应的 API:

// drivers/acpi/osl.c - OSL 对内存映射的实现
void *acpi_os_map_memory(acpi_physical_address where, acpi_size length)
{
    if (should_use_kmap(where))
        return kmap(phys_to_page(where));
    return ioremap(where, length);
}

// OSL 调度一个延迟工作(用于异步 GPE 处理等)
acpi_status acpi_os_execute(acpi_execute_type type,
                            acpi_osd_exec_callback function, void *context)
{
    return acpi_os_execute_deferred(type, function, context);
}

14.2 ACPICA 内部模块职责

模块前缀 目录 职责
ns* namespace 命名空间管理(节点查找、遍历、对象生命周期)
ps* parser AML 解析(字节码 → 内部 parse tree)
ex* interpreter AML 执行(操作码求值,操作数处理)
tb* tables ACPI 表加载、校验和管理
ev* events 事件处理(GPE、固定事件、通知)
hw* hardware 硬件寄存器读写(PM1、PM2、GPE)
rs* resources 资源描述符解析(_CRS/_PRS 返回值)
ut* utilities 通用工具函数(内存、字符串、缓存)

14.3 acpi_device 结构体关键字段

每个 ACPI 设备在内核中用 struct acpi_deviceinclude/acpi/acpi_bus.h)表示:

struct acpi_device
+------------------------------------------+
| handle          acpi_handle (命名空间节点)|
| dev             struct device (内核设备)  |
| fwnode          struct fwnode_handle      |
| pnp             struct acpi_device_pnp    |
|   .ids          HID/CID/CLS 列表          |
|   .type         ACPI_BUS_TYPE_*           |
| power           struct acpi_device_power  |
|   .state        当前 D-state              |
|   .flags        has PS0..PS3 等标志       |
| wakeup          struct acpi_device_wakeup |
|   .gpe          GPE 信息                  |
|   .resources    _PRW 电源资源             |
| status          _STA 返回值               |
| flags           initialized / enumerated  |
| handler         scan_handler (可选)       |
| driver          acpi_driver               |
| physical_node_list  绑定的物理设备列表    |
+------------------------------------------+

14.4 ACPI 子系统初始化顺序

内核启动时 ACPI 初始化分多个阶段:

early_acpi_boot_init()            # arch/x86/kernel/acpi/boot.c
  --> acpi_table_init()           # 映射 RSDP,载入所有表
  --> acpi_boot_init()            # 解析 MADT,初始化 APIC/IRQ

acpi_init()                       # drivers/acpi/bus.c
  --> acpi_os_initialize()        # OSL 初始化
  --> acpi_initialize_subsystem() # ACPICA 核心初始化
  --> acpi_load_tables()          # 载入 DSDT/SSDT,建立命名空间
  --> acpi_enable_subsystem()     # 使能固定事件、GPE
  --> acpi_initialize_objects()   # 执行 _INI 方法
  --> acpi_bus_init()
      --> acpi_scan_init()        # 扫描命名空间,创建设备树
          --> acpi_bus_scan(ACPI_ROOT_OBJECT)

15. ACPI 表深度解析:FADT、MADT、DSDT、SSDT、MCFG

15.1 FADT 结构深度解析

FADT(Fixed ACPI Description Table)是最重要的 ACPI 表之一,描述了固定功能硬件寄存器的位置和系统能力标志。其结构定义在 include/acpi/actbl1.h:199

// include/acpi/actbl1.h:199
struct acpi_table_fadt {
    struct acpi_table_header header; /* 通用表头 (36 字节) */

    u32 facs;             /* FACS 表物理地址(32 位)*/
    u32 dsdt;             /* DSDT 表物理地址(32 位)*/
    u8  model;            /* 系统中断模型(已过时)*/
    u8  preferred_profile;/* 系统偏好电源配置文件 */
    u16 sci_interrupt;    /* SCI 中断号(ACPI 主中断)*/
    u32 smi_command;      /* SMI 命令端口 */
    u8  acpi_enable;      /* 写入 smi_command 使能 ACPI */
    u8  acpi_disable;     /* 写入 smi_command 禁用 ACPI */
    u8  s4_bios_request;  /* S4BIOS 睡眠请求值 */
    u8  pstate_control;   /* P-state 控制寄存器 */

    /* PM1 事件/控制块地址 */
    u32 pm1a_event_block;
    u32 pm1b_event_block;
    u32 pm1a_control_block;
    u32 pm1b_control_block;
    u32 pm2_control_block;
    u32 pm_timer_block;

    /* GPE 块地址 */
    u32 gpe0_block;
    u32 gpe1_block;
    u8  pm1_event_length;
    u8  pm1_control_length;
    u8  pm2_control_length;
    u8  pm_timer_length;
    u8  gpe0_block_length;
    u8  gpe1_block_length;
    u8  gpe1_base;

    u8  cst_control;      /* C-state 通知值 */
    u16 c2_latency;       /* C2 退出延迟(us)*/
    u16 c3_latency;       /* C3 退出延迟(us)*/

    /* 扩展(64 位)地址版本 */
    u64 Xfacs;
    u64 Xdsdt;
    struct acpi_generic_address xpm1a_event_block;
    struct acpi_generic_address xpm1b_event_block;
    struct acpi_generic_address xpm1a_control_block;
    /* ... 其余 64 位 GAS 字段 ... */

    /* ACPI 5.0 新增:睡眠控制/状态寄存器 */
    struct acpi_generic_address sleep_control;
    struct acpi_generic_address sleep_status;
};

drivers/acpi/acpica/tbfadt.c 中的 fadt_info_table[](第 44 行)列出了内核如何从 FADT 中提取各寄存器地址,优先使用 64 位扩展地址,回退到 32 位地址。

FADT Flags 重要标志位include/acpi/actbl1.h):

ACPI_FADT_WBINVD       (bit 0)  - WBINVD 指令有效
ACPI_FADT_C1_SUPPORTED (bit 2)  - 所有 CPU 支持 C1
ACPI_FADT_C2_MP_SUPPORT(bit 3)  - SMP 下支持 C2
ACPI_FADT_POWER_BUTTON (bit 4)  - 电源按钮不产生固定事件(由设备对象处理)
ACPI_FADT_SLEEP_BUTTON (bit 5)  - 类似,睡眠按钮
ACPI_FADT_32BIT_TIMER  (bit 8)  - PM 定时器宽度(0=24位,1=32位)
ACPI_FADT_DOCKING_SUPPORTED(bit 9) - 平台支持扩展坞
ACPI_FADT_RESET_REGISTER(bit 10) - 支持 RESET_REG 寄存器重置
ACPI_FADT_HW_REDUCED   (bit 20) - 硬件减少模式(ARM/移动平台)
ACPI_FADT_LOW_POWER_S0 (bit 21) - 支持低功耗 S0 状态(Modern Standby)

15.2 DSDT 与 SSDT 加载流程

DSDT(Differentiated System Description Table)包含系统完整的 AML 描述,是唯一的;SSDT 是可选的补充表,可有多个(常用于 CPU 状态描述)。

FADT.dsdt / FADT.Xdsdt
      |
      v
acpi_tb_install_standard_table()   # tbfadt.c:313
      |
      v
acpi_tb_load_namespace()           # tbxfload.c
      |
      v
acpi_ns_load_table()               # nsload.c
      |  解析 AML 字节码,填充命名空间
      v
命名空间节点树建立完成
      |
      v
acpi_initialize_objects()
  --> 对每个设备执行 _INI 方法(初始化设备)
  --> 对每个 processor 对象构建 _PSS/_CST 信息

内核还支持从 initrd 加载自定义 ACPI 表(用于 BIOS 修复),通过 CONFIG_ACPI_TABLE_UPGRADEdrivers/acpi/tables.c 中的 acpi_load_table() 函数实现。

15.3 MADT 解析与 APIC 拓扑发现

MADT(APIC = APIC 签名,include/acpi/actbl1.h:1258)描述了系统中所有中断控制器的拓扑结构。

// include/acpi/actbl1.h:1258
struct acpi_table_madt {
    struct acpi_table_header header; /* 表头 */
    u32 address;   /* Local APIC 地址(物理)*/
    u32 flags;     /* PCAT_COMPAT bit 表示有双 8259 */
};

MADT 后紧跟若干变长子表(subtables),各类型定义在 include/acpi/actbl1.h:1273

ACPI_MADT_TYPE_LOCAL_APIC       = 0  每个 CPU 的本地 APIC
ACPI_MADT_TYPE_IO_APIC          = 1  I/O APIC
ACPI_MADT_TYPE_INTERRUPT_OVERRIDE = 2  ISA IRQ 到 GSI 映射
ACPI_MADT_TYPE_NMI_SOURCE       = 3  NMI 源
ACPI_MADT_TYPE_LOCAL_APIC_NMI   = 4  本地 APIC NMI
ACPI_MADT_TYPE_LOCAL_APIC_OVERRIDE = 5  本地 APIC 地址覆盖(64位)
ACPI_MADT_TYPE_LOCAL_SAPIC      = 6  Itanium SAPIC
ACPI_MADT_TYPE_LOCAL_X2APIC     = 9  x2APIC(UID > 255)
ACPI_MADT_TYPE_LOCAL_X2APIC_NMI = 10  x2APIC NMI

drivers/acpi/tables.c 中的 acpi_table_print_madt_entry() 函数(第 44 行)在内核启动时打印所有子表的 APIC 信息,便于调试。

MADT 解析流程(x86):

acpi_boot_init()                        # arch/x86/kernel/acpi/boot.c
  --> acpi_process_madt()
      --> acpi_parse_madt()             # 读取 Local APIC 基地址
      --> acpi_table_parse_madt(        # 遍历所有子表
            ACPI_MADT_TYPE_LOCAL_APIC,
            acpi_parse_lapic, ...)
      --> acpi_table_parse_madt(
            ACPI_MADT_TYPE_IO_APIC,
            acpi_parse_ioapic, ...)
      --> acpi_table_parse_madt(
            ACPI_MADT_TYPE_INTERRUPT_OVERRIDE,
            acpi_parse_int_src_ovr, ...)

15.4 MCFG 表与 PCIe MMCFG 空间

MCFG(Memory-Mapped Configuration Space)表描述了 PCIe 配置空间的内存映射地址范围,使得 OS 可以通过 MMIO 而非传统 I/O 端口访问 PCIe 配置空间(ECAM 模式)。

drivers/acpi/pci_mcfg.c:16 定义了解析到的条目结构:

// drivers/acpi/pci_mcfg.c:16
struct mcfg_entry {
    struct list_head list;
    phys_addr_t      addr;      /* MMCFG 基地址 */
    u16              segment;   /* PCI 段号 */
    u8               bus_start; /* 起始总线号 */
    u8               bus_end;   /* 终止总线号 */
};

每个条目覆盖一个 PCI 段(Segment Group)内的一段总线范围,其地址映射公式为:

物理地址 = base_addr + ((bus - bus_start) << 20)
                    + (device << 15)
                    + (function << 12)
                    + register_offset

ARM 服务器平台(Graviton、HiSilicon、Cavium 等)存在大量 ECAM 兼容性问题,内核在 mcfg_quirks[] 表(drivers/acpi/pci_mcfg.c:41)中维护了专门的修复条目,为这些平台提供特定的 pci_ecam_ops 实现。


16. AML 解释器:acpi_evaluate_object 全流程

16.1 函数签名与参数

acpi_evaluate_object() 是 AML 解释器最核心的公共 API,定义于 drivers/acpi/acpica/nsxfeval.c:163

// drivers/acpi/acpica/nsxfeval.c:163
acpi_status
acpi_evaluate_object(acpi_handle handle,       /* 对象句柄(可为 NULL)*/
                     acpi_string pathname,      /* 路径字符串(可为 NULL)*/
                     struct acpi_object_list *external_params, /* 输入参数 */
                     struct acpi_buffer *return_buffer)        /* 输出缓冲 */

handlepathname 同时提供时,pathname 是相对于 handle 的相对路径;若 pathname\ 开头,则为绝对路径,handle 被忽略。

16.2 执行流程

acpi_evaluate_object()
  |
  +-- acpi_ns_validate_handle(handle)       # 验证句柄,得到 ns_node
  |
  +-- 分配 acpi_evaluate_info 结构体
  |
  +-- 外部参数转换
  |   acpi_ut_copy_eobject_to_iobject()     # 将 union acpi_object 转为内部对象
  |
  +-- acpi_ns_evaluate(info)                # 核心求值
  |     |
  |     +-- 对于方法对象:
  |     |     acpi_ds_call_control_method()  # 建立新的方法执行帧
  |     |       --> acpi_ps_execute_method() # 解析并执行 AML 字节码
  |     |           --> AML 操作码逐个执行
  |     |               (If/Else/While/Return/Store/Add/...)
  |     |
  |     +-- 对于非方法对象(Package/Integer/String/Buffer):
  |           直接返回对象值
  |
  +-- 结果转换
  |   acpi_ns_resolve_references()          # 解引用 Reference 类型
  |   acpi_ut_copy_iobject_to_eobject()     # 内部对象 -> union acpi_object
  |
  +-- 返回 AE_OK 或错误码

16.3 常用包装函数

内核为常见返回类型提供了包装函数,避免重复处理缓冲区分配:

// include/linux/acpi.h(通过 include/acpi/acpi.h 实现)

// 求值并返回整数(适合 _STA / _TMP / _PSR 等)
acpi_status acpi_evaluate_integer(acpi_handle handle,
                                   acpi_string pathname,
                                   struct acpi_object_list *arguments,
                                   unsigned long long *data);

// 求值并返回字符串
acpi_status acpi_evaluate_string(acpi_handle, acpi_string,
                                  struct acpi_object_list *, char **);

// 求值并返回引用列表(适合 _PSL / _ALx 等)
bool acpi_evaluate_reference(acpi_handle, acpi_string,
                              struct acpi_object_list *,
                              struct acpi_handle_list *);

// 仅求值,不关心返回值(适合 _PS0 / _PS3 等)
acpi_status acpi_execute_simple_method(acpi_handle, char *, u64);

16.4 _CRS 资源描述符解析

_CRS 方法返回一个 Resource Descriptor 格式的 Buffer,描述设备占用的 IO/IRQ/MEM 等资源。内核通过 acpi_walk_resources() 遍历这个 Buffer:

// 示例:遍历 _CRS 资源列表
acpi_walk_resources(adev->handle, METHOD_NAME__CRS,
                    my_resource_callback, &my_data);

// 回调函数接收每个资源描述符
static acpi_status my_resource_callback(struct acpi_resource *res, void *data)
{
    switch (res->type) {
    case ACPI_RESOURCE_TYPE_IO:
        /* IO 端口资源 */
        break;
    case ACPI_RESOURCE_TYPE_IRQ:
        /* 中断资源 */
        break;
    case ACPI_RESOURCE_TYPE_MEMORY32:
        /* 32 位内存资源 */
        break;
    case ACPI_RESOURCE_TYPE_ADDRESS64:
        /* 64 位地址空间资源 */
        break;
    }
    return AE_OK;
}

资源类型解析由 drivers/acpi/acpica/rs*.c 系列文件实现(rsaddr.crsio.crsirq.crsmemory.c 等)。

16.5 acpi_evaluate_object 的返回对象类型

AML 方法的返回值被封装为 union acpi_objectinclude/acpi/actypes.h):

union acpi_object {
    acpi_object_type type;   /* 类型标识 */
    struct { acpi_object_type type; u64 value; } integer;
    struct { acpi_object_type type; u32 length; char *pointer; } string;
    struct { acpi_object_type type; u32 length; u8 *pointer; } buffer;
    struct {
        acpi_object_type type;
        u32 count;
        union acpi_object *elements;
    } package;
    struct { acpi_object_type type; acpi_handle handle; } reference;
    struct { acpi_object_type type; u32 proc_id; u64 pblk_address; u32 pblk_length; } processor;
    struct { acpi_object_type type; u32 system_level; u32 resource_order; } power_resource;
};

17. ACPI 中断模型:GPE 机制深度分析

17.1 GPE 概述

GPE(General Purpose Event)是 ACPI 提供的通用中断机制,用于报告各种硬件事件(如电池状态变化、AC 插拔、盖子开关、PCIe 热插拔等)。GPE 有两个来源:

  1. FADT GPE:由 FADT 中的 gpe0_block/gpe1_block 定义,使用固定 I/O 端口
  2. GPE Block Device:由 ACPI 命名空间中的 ACPI0006 设备提供,扩展 GPE 数量

17.2 GPE 数据结构层次

acpi_gpe_xrupt_info          每个中断号一个(通常为 SCI 中断)
  |
  +-- gpe_block_list_head --> acpi_gpe_block_info  每个 GPE 块一个
                                |
                                +-- register_info[] 每对状态/使能寄存器一个
                                |   struct acpi_gpe_register_info:
                                |     status_address  (8-bit I/O 寄存器地址)
                                |     enable_address  (8-bit I/O 寄存器地址)
                                |     base_gpe_number (此寄存器对应的起始 GPE 号)
                                |     enable_for_run  (运行时使能掩码)
                                |     enable_for_wake (唤醒使能掩码)
                                |
                                +-- event_info[]    每个 GPE 号一个
                                    struct acpi_gpe_event_info:
                                      dispatch          (方法/处理器/通知列表)
                                      register_info     (反向指针)
                                      flags             边沿/电平,方法/处理器
                                      gpe_number
                                      runtime_count     (引用计数)

17.3 GPE 处理流程

SCI 中断触发
      |
      v
acpi_ev_sci_xrupt_handler()          # evxfevnt.c
      |
      v
acpi_ev_gpe_detect(gpe_xrupt_list)   # evgpe.c:340
      |  遍历所有 GPE 块的寄存器组
      |  读取 status & enable 寄存器
      |  找出 pending (status & enable) 的 GPE
      v
acpi_ev_detect_gpe(device, gpe_event_info, gpe_number)
      |
      +-- 清除状态位(边沿触发立即清除)
      |
      +-- 检查分发类型:
      |
      +-- ACPI_GPE_DISPATCH_HANDLER  用户注册了处理函数
      |     --> 直接调用 handler()
      |
      +-- ACPI_GPE_DISPATCH_METHOD   ACPI 命名空间有 _Lxx/_Exx 方法
      |     --> 禁用此 GPE(防止递归)
      |     --> 调度到工作队列
      |         acpi_ev_asynch_execute_gpe_method()  # evgpe.c:455
      |           --> acpi_evaluate_object(_Lxx/_Exx)
      |           --> 重新使能此 GPE
      |
      +-- ACPI_GPE_DISPATCH_NOTIFY   设备 Notify
            --> acpi_ev_add_cpe_notify_handler()

17.4 _Lxx 与 _Exx 方法命名规则

GPE 对应的 AML 方法命名规则:

_L00 ~ _L7F  电平触发的 GPE 方法(Level-triggered)
_E00 ~ _E7F  边沿触发的 GPE 方法(Edge-triggered)

命名中的数字为 GPE 号的十六进制表示。例如 _L1F 表示 GPE 号 31(0x1F)、电平触发。

17.5 GPE 使能/禁用 API

驱动程序或 ACPI 子系统通过以下 API 管理 GPE:

// 安装 GPE 处理函数(evxface.c:840)
acpi_status acpi_install_gpe_handler(
    acpi_handle gpe_device,    /* NULL = FADT GPE */
    u32 gpe_number,
    u32 type,                  /* ACPI_GPE_LEVEL_TRIGGERED 或 EDGE */
    acpi_gpe_handler address,  /* 回调函数 */
    void *context);

// 使能 GPE(增加引用计数)
acpi_status acpi_enable_gpe(acpi_handle gpe_device, u32 gpe_number);

// 禁用 GPE(减少引用计数)
acpi_status acpi_disable_gpe(acpi_handle gpe_device, u32 gpe_number);

// 清除 GPE 状态位
acpi_status acpi_clear_gpe(acpi_handle gpe_device, u32 gpe_number);

acpi_ev_add_gpe_reference() 的引用计数逻辑(drivers/acpi/acpica/evgpe.c)保证了多个驱动可以同时持有同一 GPE 的引用:只有第一次使能时才真正写寄存器,最后一次释放时才真正禁用。

17.6 ACPI SCI 与固定事件

除 GPE 外,ACPI 还有"固定事件"(Fixed Events),这些事件直接映射到 PM1 Status/Enable 寄存器:

固定事件类型(ACPI_EVENT_*):
  ACPI_EVENT_PMTIMER    (0) - PM 定时器溢出
  ACPI_EVENT_GLOBAL     (1) - 全局锁释放
  ACPI_EVENT_POWER_BUTTON (2) - 电源按钮(若 FADT flags 未设置 POWER_BUTTON 位)
  ACPI_EVENT_SLEEP_BUTTON (3) - 睡眠按钮
  ACPI_EVENT_RTC        (4) - RTC 闹钟

固定事件通过 acpi_install_fixed_event_handler() 注册处理函数(evxface.c:584),由 acpi_ev_fixed_event_dispatch() 分发。


18. ACPI 全局电源状态:G0-G3 与 S0-S5 映射

18.1 G-state 概念

ACPI 定义了四个全局系统状态(G-states),是比 S-states 更高层的抽象:

+--------+------------------+---------------------------------------+
| G-state | 描述             | 对应 S-state                          |
+--------+------------------+---------------------------------------+
| G0     | Working          | S0(全速运行)                         |
| G1     | Sleeping         | S1 / S2 / S3 / S4(各种睡眠)          |
| G2     | Soft Off         | S5(软关机,电源键可唤醒)              |
| G3     | Mechanical Off   | 完全断电(需要手动接通电源)            |
+--------+------------------+---------------------------------------+

G3 状态下,系统完全无电,仅靠 CMOS 电池维持 RTC。从 G3 恢复必须重新进行完整的 POST 过程。

18.2 S-state 与 C-state、D-state 的关系

全局视角的 ACPI 电源状态层次:

G0 (Working)
  └── S0 (Running)
      ├── CPU: C0 (执行指令)
      │       C1/C2/C3/C6... (不同深度的 C-state)
      ├── 设备: D0 (完全工作)
      │         D1/D2/D3hot (Runtime PM 挂起)
      └── Package C-states (PC2/PC6/PC8/PC10)

G1 (Sleeping)
  ├── S1 (CPU 停止,缓存供电)
  ├── S2 (CPU 上下文丢失)
  ├── S3 (Suspend-to-RAM,设备 D3)
  └── S4 (Hibernate,全设备 D3cold)

G2 (Soft Off)
  └── S5 (软关机)

G3 (Mechanical Off)
  └── 完全断电

18.3 P-state 与 G0/S0 的关系

P-states(Performance States)仅在 G0/S0 状态下有意义,定义了 CPU 在正常运行时的不同频率/电压工作点:

G0 / S0
  |
  +-- P0: 最高性能(最高频率/电压,Turbo Boost 上限)
  +-- P1: 额定最高性能
  +-- P2: ...
  +-- Pn: 最低性能(最低频率/电压)
  |
  实现:
    软件 P-states: 通过 _PSS 表 + _PCT 寄存器切换(acpi-cpufreq 驱动)
    HWP:          MSR_HWP_REQUEST,CPU 自主管理(intel_pstate 驱动)
    CPPC:         协作式控制,适合 ARM 服务器(cppc_cpufreq 驱动)

19. ACPI 设备枚举深度:scan.c 代码路径分析

19.1 acpi_bus_scan 的两阶段扫描

acpi_bus_scan() 采用两阶段策略(drivers/acpi/scan.c:2721),以处理设备依赖关系:

// drivers/acpi/scan.c:2721
int acpi_bus_scan(acpi_handle handle)
{
    struct acpi_device *device = NULL;

    /* 第一阶段:跳过有未满足依赖的设备(_DEP 依赖)*/
    if (ACPI_SUCCESS(acpi_bus_check_add(handle, true, &device)))
        acpi_walk_namespace(ACPI_TYPE_ANY, handle, ACPI_UINT32_MAX,
                            acpi_bus_check_add_1, NULL, NULL,
                            (void **)&device);

    if (!device)
        return -ENODEV;

    /* 设置 _CRS CSI-2 软件节点(摄像头相关)*/
    acpi_mipi_scan_crs_csi2();
    acpi_mipi_init_crs_csi2_swnodes();

    /* 附加驱动、注册到设备模型 */
    acpi_bus_attach(device, (void *)true);

    /* 第二阶段:处理之前被推迟的设备 */
    acpi_scan_postponed();

    return 0;
}

19.2 acpi_set_pnp_ids 的 ID 解析

acpi_set_pnp_ids()drivers/acpi/scan.c:1388)负责从 ACPI 命名空间提取设备标识,构建设备的 ID 列表:

acpi_set_pnp_ids()
  |
  +-- acpi_add_id(pnp, _HID)   主设备 ID(如 "PNP0C0A")
  |     acpi_evaluate_object(handle, "_HID", ...) 求值
  |     加入 pnp->ids 链表
  |
  +-- acpi_add_id(pnp, _CIDs)  兼容 ID 列表
  |     _CID 可以返回 Package,包含多个 ID
  |
  +-- acpi_add_id(pnp, _CLS)   PCI 类别码(用于 PCI 总线设备)
  |
  +-- acpi_add_id(pnp, _ADR)   地址(PCI 功能号编码)
  |
  +-- 为总线节点添加虚拟 HID(如 "LNXSYBUS")

最终 pnp->ids 是一个链表,包含所有可能的 ID,匹配驱动时依次尝试。

19.3 acpi_scan_handler 机制

ACPI 扫描处理器(acpi_scan_handler)是一种优先级最高的设备匹配机制,在通用平台设备创建之前生效:

// 扫描处理器注册示例(drivers/acpi/pci_root.c:53)
static struct acpi_scan_handler pci_root_handler = {
    .ids = root_device_ids,    /* {"PNP0A03", 0} */
    .attach = acpi_pci_root_add,
    .detach = acpi_pci_root_remove,
    .hotplug = {
        .enabled = true,
        .scan_dependent = acpi_pci_root_scan_dependent,
    },
};

匹配优先级:

1. acpi_scan_handler(设备类型专用,如 PCI 根桥、处理器、电池等)
2. acpi_driver(传统 ACPI 驱动)
3. 自动创建 platform_device(通用回退路径)

19.4 设备依赖机制(_DEP)

ACPI 5.0 引入了 _DEP 方法,声明一个设备在枚举前需要等待另一个设备就绪(常见于 I2C 触摸板需要等待 I2C 控制器就绪)。

内核用 struct acpi_dep_datadrivers/acpi/scan.c:37)跟踪依赖关系:

static LIST_HEAD(acpi_dep_list);
static DEFINE_MUTEX(acpi_dep_list_lock);

struct acpi_dep_data {
    struct list_head  node;
    acpi_handle       supplier;  /* 被依赖的设备 */
    acpi_handle       consumer;  /* 依赖它的设备 */
    bool              honor_dep; /* 是否强制执行 */
    bool              met;       /* 依赖是否已满足 */
};

当 supplier 设备就绪后,调用 acpi_dev_clear_dependencies() 标记依赖满足,触发第二阶段扫描处理延迟设备。


20. ACPI PCI 配置:MCFG 与 _OSC 协商深度分析

20.1 _OSC 方法概述

_OSC(Operating System Capabilities)方法允许 OS 与固件协商控制权,OS 告知固件自己支持哪些功能,固件决定是否将这些功能的控制权交给 OS。

协商使用一个 UUID 标识功能集,最常见的两类:

  • 平台级 _OSC(System Bus UUID:0811B06E-4A27-44F9-8D60-3CBBC22E7B48):协商 APEI、CPPC、USB4 等系统级特性
  • PCI _OSC(PCI/PCIe UUID:33DB4D5B-1FF7-401C-9657-7441C03DD766):协商 PCIe 热插拔、PME、AER、DPC 等

20.2 acpi_run_osc 实现分析

acpi_run_osc()drivers/acpi/bus.c:287)是底层 _OSC 执行函数:

// drivers/acpi/bus.c:287
acpi_status acpi_run_osc(acpi_handle handle, struct acpi_osc_context *context)
{
    union acpi_object in_params[4], *out_obj;
    struct acpi_buffer output;
    guid_t guid;

    /* 解析 UUID 字符串 */
    guid_parse(context->uuid_str, &guid);

    /* 构造 4 个参数:UUID Buffer, Revision, Count, Capabilities Buffer */
    ret = acpi_eval_osc(handle, &guid, context->rev, &context->cap,
                        in_params, &output);

    /* 检查返回的错误位 */
    if (acpi_osc_error_check(...)) {
        status = AE_ERROR;
    }

    /* 保存返回的 Capabilities Buffer(固件确认支持的部分)*/
    context->ret.pointer = kmemdup(retbuf, context->ret.length, GFP_KERNEL);
    return AE_OK;
}

_OSC 的 Capabilities Buffer 结构(4 字节对齐):

Dword 0: Status Dword
  bit 0: Query flag(仅查询时置 1)
  bit 1: Always zero
  bit 2: 功能被固件屏蔽(固件返回)
  bit 3: UUID 未知(固件返回)
  bit 4: Revision 不支持(固件返回)

Dword 1 起: 功能位掩码
  OSC_PCI_EXT_CONFIG_SUPPORT      (bit 4)  扩展配置空间访问
  OSC_PCI_ASPM_SUPPORT            (bit 5)  ASPM(链路电源状态管理)
  OSC_PCI_CLOCK_PM_SUPPORT        (bit 6)  时钟电源管理
  OSC_PCI_MSI_SUPPORT             (bit 8)  MSI 消息中断
  OSC_PCI_EXPRESS_NATIVE_HP_CONTROL (bit 0 of control dword) 原生热插拔控制
  OSC_PCI_EXPRESS_PME_CONTROL     (bit 2)  PME 控制
  OSC_PCI_EXPRESS_AER_CONTROL     (bit 3)  高级错误报告
  OSC_PCI_EXPRESS_DPC_CONTROL     (bit 7)  下游端口遏制

20.3 negotiate_os_control 完整流程

drivers/acpi/pci_root.c 中的 negotiate_os_control() 负责 PCI 根桥的 _OSC 协商:

negotiate_os_control(root)
  |
  +-- calculate_support()    根据内核配置计算支持位
  |   OSC_PCI_EXT_CONFIG_SUPPORT(若 pci_ext_cfg_avail())
  |   OSC_PCI_ASPM_SUPPORT(若 pcie_aspm_support_enabled())
  |   OSC_PCI_MSI_SUPPORT(若 pci_msi_enabled())
  |
  +-- calculate_control()    计算期望控制位
  |   OSC_PCI_EXPRESS_PME_CONTROL(总是请求)
  |   OSC_PCI_EXPRESS_NATIVE_HP_CONTROL(若 CONFIG_HOTPLUG_PCI_PCIE)
  |   OSC_PCI_EXPRESS_AER_CONTROL(若 pci_aer_available())
  |   OSC_PCI_EXPRESS_DPC_CONTROL(若 CONFIG_PCIE_DPC + CONFIG_PCIE_EDR)
  |
  +-- acpi_pci_osc_control_set(handle, &control, support, ...)
  |     1. 先以 OSC_QUERY_ENABLE 调用 _OSC(查询模式)
  |     2. 根据返回值更新 control(移除固件不支持的位)
  |     3. 再次调用 _OSC(实际申请控制权)
  |
  +-- 根据返回的 control 位,设置 root->osc_control_set
  +-- 若不支持 AER,禁用 AER 功能
  +-- 若不支持热插拔控制,调整 no_aspm

20.4 Apple 平台的特殊处理

苹果 Mac 平台(识别通过 x86_apple_machine)的 _OSC 实现存在已知问题:调用 _OSI("Darwin") 成功后,所有 _OSC 调用都会返回失败。内核针对此问题直接跳过 _OSC 协商(drivers/acpi/pci_root.c:568):

// drivers/acpi/pci_root.c:568
if (x86_apple_machine) {
    root->osc_control_set = ~OSC_PCI_EXPRESS_PME_CONTROL;
    decode_osc_control(root, "OS assumes control of", root->osc_control_set);
    return;
}

21. ACPI platform bus 与 acpi_match_table

21.1 ACPI platform 设备创建机制

当 ACPI 设备无法被任何 acpi_scan_handler 匹配,也没有注册专用的 acpi_driver 时,内核会自动将其创建为一个 platform_devicedrivers/acpi/scan.c:2279):

// drivers/acpi/scan.c:2279
} else {
    /* For a regular device object, create a platform device. */
    acpi_create_platform_device(device, NULL);
}

acpi_create_platform_device()drivers/acpi/acpi_platform.c:110)的核心逻辑:

// drivers/acpi/acpi_platform.c:110
struct platform_device *acpi_create_platform_device(struct acpi_device *adev,
                                                     const struct property_entry *properties)
{
    /* 检查是否已有物理设备绑定 */
    if (adev->physical_node_count && !adev->pnp.type.backlight)
        return NULL;

    /* 过滤黑名单(IOxAPIC、IOAPIC、PIC、DMA 控制器等)*/
    match = acpi_match_acpi_device(forbidden_id_list, adev);
    if (match) { /* 检查是否需要 _CRS 资源 */ }

    /* 解析 _CRS 获取资源 */
    count = acpi_dev_get_resources(adev, &resource_list, NULL, NULL);

    /* 填充 platform_device_info,注册设备 */
    pdevinfo.name = dev_name(&adev->dev);
    pdevinfo.id   = -1;
    pdevinfo.res  = resources;
    pdevinfo.num_res = count;
    pdevinfo.fwnode = acpi_fwnode_handle(adev);
    pdev = platform_device_register_full(&pdevinfo);
    ...
}

21.2 struct device_driver 中的 acpi_match_table

每个 platform_driver(及 I2C、SPI 等总线驱动)都可以通过 acpi_match_table 字段声明 ACPI 兼容 ID,定义在 include/linux/device/driver.h:109

// include/linux/device/driver.h:109
struct device_driver {
    const char              *name;
    const struct bus_type   *bus;
    struct module           *owner;
    const char              *mod_name;
    bool suppress_bind_attrs;
    enum probe_type probe_type;

    const struct of_device_id       *of_match_table;
    const struct acpi_device_id     *acpi_match_table;  /* ACPI 匹配表 */

    int (*probe)(struct device *dev);
    int (*remove)(struct device *dev);
    void (*shutdown)(struct device *dev);
    ...
};

21.3 ACPI 驱动匹配流程

内核在 drivers/acpi/glue.c 中实现了 ACPI 与 platform 总线的胶合逻辑。当 platform_device 尝试绑定驱动时,acpi_driver_match_device() 被调用:

platform_bus_type.match(dev, drv)
  |
  +-- platform_match()
      |
      +-- 检查 driver->acpi_match_table
          --> acpi_match_device(drv->acpi_match_table, dev)
              --> ACPI_COMPANION(dev) 获取 acpi_device
              --> 遍历 acpi_device->pnp.ids
              --> 与 acpi_match_table[] 每条记录比较 HID/CID
              --> 找到匹配则返回对应条目

21.4 典型驱动示例

以电源供应驱动 goldfish_battery 为例(drivers/power/supply/goldfish_battery.c):

static const struct acpi_device_id goldfish_battery_acpi_match[] = {
    { "GFSH0001", 0 },
    { },
};
MODULE_DEVICE_TABLE(acpi, goldfish_battery_acpi_match);

static struct platform_driver goldfish_battery_driver = {
    .probe = goldfish_battery_probe,
    .remove = goldfish_battery_remove,
    .driver = {
        .name = "goldfish-battery",
        .of_match_table = goldfish_battery_of_match,
        .acpi_match_table = ACPI_PTR(goldfish_battery_acpi_match),
    },
};

ACPI_PTR() 宏在未开启 CONFIG_ACPI 时展开为 NULL,确保代码在无 ACPI 平台上可以编译。

21.5 acpi_match_table 匹配过程中的 driver_data 使用

acpi_device_iddriver_data 字段可用于区分同一驱动支持的不同变体:

static const struct acpi_device_id my_device_ids[] = {
    { "VEND0001", (kernel_ulong_t)&variant_a_config },
    { "VEND0002", (kernel_ulong_t)&variant_b_config },
    { }
};

static int my_probe(struct platform_device *pdev)
{
    const struct acpi_device_id *match;
    match = acpi_match_device(my_device_ids, &pdev->dev);
    if (match)
        config = (struct my_config *)match->driver_data;
}

22. ACPI 热管理深度:热区方法与 Linux thermal 框架集成

22.1 ACPI 热区概述

ACPI 热区(Thermal Zone)通过命名空间中的 _TZ 作用域描述,提供了一套完整的温度监控和冷却策略接口。

\_TZ
 |
 +-- TZ00  (热区 0,如 CPU 封装温度区)
 |    +-- _TMP   当前温度(单位:deci-Kelvin,即 0.1K)
 |    +-- _CRT   临界温度(超过触发紧急关机)
 |    +-- _HOT   热温度(超过启动 hibernate)
 |    +-- _PSV   被动冷却温度(超过启动 CPU 降频)
 |    +-- _AC0   主动冷却 0 温度(最高优先级风扇触发点)
 |    +-- _AC1   主动冷却 1 温度
 |    ...
 |    +-- _ACn   主动冷却 n(最多 10 级,_AC0~_AC9)
 |    |
 |    +-- _PSL   被动冷却设备列表(CPU 对象引用)
 |    +-- _AL0   主动冷却 0 设备列表(风扇引用)
 |    +-- _AL1   主动冷却 1 设备列表
 |    |
 |    +-- _TC1   被动冷却常数 1(用于 CPU 频率计算)
 |    +-- _TC2   被动冷却常数 2
 |    +-- _TSP   热采样周期(单位:0.1 秒)
 |    +-- _TZP   轮询频率(单位:0.1 秒)
 |
 +-- TZ01  (热区 1,如 GPU 温度区)

22.2 温度读取:_TMP 方法

_TMP 返回当前温度,单位是 deci-Kelvin(摄氏度 + 273.15)* 10。内核代码位于 drivers/acpi/thermal.c:129

// drivers/acpi/thermal.c:129
static int acpi_thermal_get_temperature(struct acpi_thermal *tz)
{
    acpi_status status = AE_OK;
    unsigned long long tmp;

    tz->last_temp_dk = tz->temp_dk;

    /* 调用 _TMP 方法,结果以 deci-Kelvin 返回 */
    status = acpi_evaluate_integer(tz->device->handle, "_TMP", NULL, &tmp);
    if (ACPI_FAILURE(status))
        return -ENODEV;

    tz->temp_dk = tmp;
    return 0;
}

温度转换公式:

摄氏温度 (mC) = (deci_kelvin - 2731) * 100

deci_kelvin_to_millicelsius() 函数(include/linux/units.h)实现此转换。

22.3 热区触发点方法层次

drivers/acpi/thermal_lib.c 中的 acpi_trip_temp() 是所有触发点方法的统一底层实现:

// drivers/acpi/thermal_lib.c:27
static int acpi_trip_temp(struct acpi_device *adev, char *obj_name,
                          int *ret_temp)
{
    unsigned long long temp;
    acpi_status status;

    status = acpi_evaluate_integer(adev->handle, obj_name, NULL, &temp);
    if (ACPI_FAILURE(status))
        return -ENODATA;

    /* 合理性检查:218K (-55°C) 到 448K (175°C) */
    if (temp >= TEMP_MIN_DECIK && temp <= TEMP_MAX_DECIK)
        *ret_temp = temp;
    else
        *ret_temp = THERMAL_TEMP_INVALID;

    return 0;
}

各触发点方法的包装(drivers/acpi/thermal_lib.c):

// 临界温度:超过触发立即关机
int acpi_critical_trip_temp(struct acpi_device *adev, int *ret_temp)
    { return acpi_trip_temp(adev, "_CRT", ret_temp); }  // 行 70

// 热温度:超过触发 hibernate
int acpi_hot_trip_temp(struct acpi_device *adev, int *ret_temp)
    { return acpi_trip_temp(adev, "_HOT", ret_temp); }  // 行 64

// 被动冷却温度:超过降低 CPU 频率
int acpi_passive_trip_temp(struct acpi_device *adev, int *ret_temp)
    { return acpi_trip_temp(adev, "_PSV", ret_temp); }  // 行 58

// 主动冷却温度:超过启动风扇
int acpi_active_trip_temp(struct acpi_device *adev, int id, int *ret_temp)
{
    char obj_name[] = {'_', 'A', 'C', '0' + id, '\0'}; // 行 48
    return acpi_trip_temp(adev, obj_name, ret_temp);
}

22.4 ACPI 热区与 Linux thermal framework 的集成

drivers/acpi/thermal.c 中的 ACPI 热区驱动通过 Linux 热管理框架(drivers/thermal/)暴露给上层:

struct acpi_thermal (ACPI 热区设备)
          |
          | thermal_zone_device_register()
          v
struct thermal_zone_device (Linux thermal framework)
          |
          +-- trip points (THERMAL_TRIP_CRITICAL / _HOT / PASSIVE / ACTIVE)
          |
          +-- cooling devices (CPU freq / fan)
          |
          v
/sys/class/thermal/thermal_zoneX/
  temp           - 当前温度(单位:毫摄氏度)
  type           - 热区类型("acpitz")
  trip_point_N_temp  - 各触发点温度
  trip_point_N_type  - 触发点类型(critical/hot/passive/active)

22.5 ACPI 被动冷却算法

当温度超过 _PSV 时,ACPI 驱动通过调节 CPU 性能来降温,使用如下算法(TC1/TC2 常数来自 _TC1/_TC2):

性能调整量 = TC1 * (当前温度 - 上次温度) + TC2 * (当前温度 - PSV 温度)

这是一个比例-微分(PD)控制算法,避免温度大幅波动。

22.6 ACPI Notify 触发温度更新

ACPI Notify 代码 0x80 表示温度变化,0x81 表示触发点变化,0x82 表示设备列表变化(drivers/acpi/thermal.c:45):

#define ACPI_THERMAL_NOTIFY_TEMPERATURE  0x80
#define ACPI_THERMAL_NOTIFY_THRESHOLDS   0x81
#define ACPI_THERMAL_NOTIFY_DEVICES      0x82
#define ACPI_THERMAL_NOTIFY_CRITICAL     0xF0
#define ACPI_THERMAL_NOTIFY_HOT          0xF1

收到 Notify 后,驱动将工作项提交到专用工作队列 acpi_thermal_pm_queue(行 87),异步重新评估热区状态。


23. ACPI C-state 与 P-state 深度:处理器电源状态

23.1 _CST 方法解析

_CST(C-State Table)方法返回一个 Package,描述该处理器支持的所有 C-state:

_CST 返回值格式:
Package {
    Count,    // C-state 数量
    Package { // C1 描述
        Register,   // GAS:进入此 C-state 的寄存器(MWAIT 指令)
        Type,       // 1=C1, 2=C2, 3=C3
        Latency,    // 退出延迟(us)
        Power       // 功耗(mW)
    },
    Package { // C2 描述
        ...
    },
    ...
}

内核解析代码位于 drivers/acpi/processor_idle.c,通过 acpi_processor_get_cstate_info() 读取并填充 struct acpi_processor_cx 数组,然后注册到 cpuidle 框架。

23.2 _PSS 方法解析

_PSS(Performance Supported States)返回 CPU 支持的所有 P-state 列表:

_PSS 返回值格式(每个 P-state 一个 Package):
Package {
    Frequency,        // 频率(MHz)
    Power,            // 功耗(mW)
    TransitionLatency,// 切换延迟(us)
    BusMasterLatency, // 总线主控等待(us)
    Control,          // 写入 _PCT 控制寄存器的值
    Status            // 对应的状态值
}

内核解析代码(drivers/acpi/processor_perflib.c:326):

status = acpi_evaluate_object(pr->handle, "_PSS", NULL, &buffer);
// 遍历返回的 Package,填充 pr->performance.states[] 数组
// 频率合理性检查(必须递减排列)
// 过滤重复和无效条目

23.3 _PCT 方法:P-state 控制寄存器

_PCT(Performance Control)返回两个 GAS(Generic Address Structure),分别描述控制寄存器和状态寄存器的地址,使内核能够无需调用 AML 直接切换 P-state:

// drivers/acpi/processor_perflib.c:234
status = acpi_evaluate_object(pr->handle, "_PCT", NULL, &buffer);
// 解析返回的 Package[0] 为控制寄存器 GAS
// 解析返回的 Package[1] 为状态寄存器 GAS

典型的 _PCT 描述(x86 使用 MSR 直接控制):

_PCT 控制寄存器:
  FunctionalFixedHardware (space_id = 0x7F)
  Address = 0x0  (使用固定的 IA32_PERF_CTL MSR)

_PCT 状态寄存器:
  FunctionalFixedHardware
  Address = 0x1  (使用固定的 IA32_PERF_STATUS MSR)

23.4 _PPC 方法:性能限制

_PPC(Performance Present Capabilities)返回当前平台允许的最高 P-state 索引。当热量或电源约束出现时,固件通过 Notify(0x80) 触发内核重新读取 _PPC

// drivers/acpi/processor_perflib.c:66
status = acpi_evaluate_integer(pr->handle, "_PPC", NULL, &ppc);
if (ACPI_SUCCESS(status)) {
    /* _PPC = 0 表示所有 P-state 可用
       _PPC = n 表示只能使用 P-state n 及以上(性能更低)*/
    pr->performance_platform_limit = (int)ppc;
}

_PPC 通过 cpufreq 通知链传递给 governor:

_PPC 变化 Notify(0x80)
      |
      v
acpi_processor_ppc_has_changed()
      |
      v
cpufreq_update_policy()
      |
      v  调整 policy->max
governor 重新计算目标频率

23.5 ACPI 处理器驱动与 cpufreq/cpuidle 的关系

drivers/acpi/processor_driver.c   (ACPI Processor 驱动主体)
  |
  +-- acpi_processor_add()
  |     读取 _PSS -> 注册 cpufreq policy(通过 acpi-cpufreq 或 intel_pstate)
  |     读取 _CST -> 注册 cpuidle device(通过 acpi_idle_driver)
  |
  +-- acpi_processor_remove()
        注销 cpufreq policy 和 cpuidle device

drivers/acpi/processor_perflib.c   (P-state 支持)
  +-- acpi_processor_get_performance_info()  解析 _PSS/_PCT/_PPC
  +-- acpi_processor_ppc_has_changed()       响应 _PPC 变化

drivers/acpi/processor_idle.c      (C-state 支持)
  +-- acpi_processor_get_cstate_info()       解析 _CST
  +-- acpi_idle_enter()                      进入 C-state

24. ACPI 嵌入式控制器(EC)深度分析

24.1 EC 协议概述

嵌入式控制器(Embedded Controller,EC)是笔记本等便携设备中处理低级硬件交互的微控制器,负责电池管理、键盘背光、风扇控制、温度传感等。ACPI 定义了标准的 EC 通信协议(ACPI EC Interface),内核驱动位于 drivers/acpi/ec.c

EC 通过两个 I/O 端口与主机通信:

  • 数据端口(默认 0x62):读写 EC 内存
  • 命令/状态端口(默认 0x66):发送命令和读取状态

24.2 EC 中断处理模式

EC 有两种操作模式:

轮询模式(Polling Mode)
  - 适用于不支持 ECDT 的系统
  - 通过 spinlock + 忙等待读取 EC

中断模式(Interrupt Mode,GPE 模式)
  - EC 通过 GPE 通知主机有数据或事件
  - 内核响应 GPE,触发 ec_handle_event()
  - 更高效,减少 CPU 占用

24.3 EC 事务机制

// EC 读操作示意
ec_read(u8 addr, u8 *val)
  |
  +-- ec_transaction(EC_COMMAND_READ, addr, NULL, 0, val, 1)
        |
        +-- 等待 EC IBFInput Buffer Full清空
        +-- 写命令到命令端口0x66+-- 等待 EC IBF 清空
        +-- 写地址到数据端口0x62+-- 等待 EC OBFOutput Buffer Full置位
        +-- 从数据端口读取结果

// EC Query(Q-event):EC 发送事件通知
ec_query_execute(ec_query)
  |   EC 读取 Query Code事件编号+--  ACPI 命名空间查找 _QXX 方法XX = Query Code 十六进制+-- acpi_evaluate_object(_QXX)  执行对应的 AML 方法

24.4 EC GPE 处理

EC 被分配了一个专用 GPE(由 DSDT 中 EC 对象的 _GPE 字段指定)。当 EC 有事件时,触发此 GPE:

EC GPE 触发
      |
      v
acpi_ec_gpe_handler()           # drivers/acpi/ec.c
      |
      +-- 检查 EC 状态寄存器
      |
      +-- 若 SCI_EVT 位置位:
      |     acpi_ec_query_execute()   读取 Q-code,执行 _QXX 方法
      |
      +-- 若 OBF 位置位:
      |     唤醒等待数据的事务
      |
      +-- 若 IBF 已清空:
            唤醒等待发送的事务

25. ACPI 唤醒机制:_PRW、GPE 与唤醒链路

25.1 _PRW 方法

_PRW(Power Resources for Wake)描述设备能够触发系统从睡眠状态唤醒所需的条件:

_PRW 返回值格式:
Package {
    EventInfo,    // GPE 号(整数)或 GPE 设备引用 + GPE 号
    WakeLevel,    // 此设备能够唤醒的最深睡眠状态(S1~S5)
    PowerResource_1,  // 唤醒所需的电源资源(可选)
    PowerResource_2,
    ...
}

内核解析 _PRWdrivers/acpi/wakeup.c):

// drivers/acpi/wakeup.c
acpi_bus_get_wakeup_device_flags(device)
  |
  +-- acpi_evaluate_object(handle, "_PRW", ...)
  +-- 解析 GPE 信息device->wakeup.gpe_number, gpe_device
  +-- 解析睡眠级别device->wakeup.sleep_state
  +-- 解析电源资源device->wakeup.resources

25.2 唤醒使能路径

用户写入 /sys/devices/.../power/wakeup = "enabled"
      |
      v
device_wakeup_enable(dev)
      |
      v
acpi_pm_set_device_wakeup(dev, true)    # drivers/acpi/device_pm.c
      |
      +-- acpi_enable_wakeup_device()
      |     使能 _PRW 中的电源资源(_PS0)
      |
      +-- acpi_enable_gpe(wakeup.gpe_device, wakeup.gpe_number)
            在 GPE register 中设置 enable_for_wake 位

25.3 系统唤醒后的清理

S3/S4 恢复后,acpi_disable_wakeup_devices() 清除所有唤醒使能,acpi_gpe_wakeup() 确定哪个 GPE 触发了唤醒,内核将相应设备标记为唤醒源:

// 唤醒源检测(drivers/acpi/wakeup.c)
acpi_gpe_wakeup(handle, gpe_number, &context)
  --> context.found 指向触发唤醒的设备
  --> pm_wakeup_event(&adev->dev, 0)  记录唤醒事件

26. ACPI 电源资源管理:power.c 深度分析

26.1 ACPI 电源资源对象

ACPI 命名空间中的电源资源(Power Resource)是一个特殊的对象类型,代表一个可以被开关的电源域(如电压调节器)。多个设备可以共享同一电源资源。

命名空间中的电源资源示例:
PowerResource (PUBS, 0, 0)  // PUBS 电源轨
{
    Method (_STA) { Return (^^EC.PPWR) }    // 返回当前状态
    Method (_ON)  { Store (1, ^^EC.PPWR) }  // 上电
    Method (_OFF) { Store (0, ^^EC.PPWR) }  // 下电
}

Device (WIFI)
{
    Name (_PR0, Package { PUBS })  // D0 状态需要 PUBS 电源
    Name (_PR3, Package { PUBS })  // D3 状态需要 PUBS 电源(D3hot)
}

26.2 电源资源引用计数

drivers/acpi/power.c 为每个电源资源维护引用计数。只有当引用计数变为 0 时才关闭电源,变为 1 时才开电:

acpi_power_resource
+---------------------------+
| device      acpi_device   |
| system_level              |  // 电源资源存在的最深睡眠级别
| order        u32          |  // 同一状态下的上电顺序
| ref_count    unsigned      |  // 当前引用计数
| wakeup_enabled bool       |  // 是否用于唤醒
+---------------------------+

引用计数变化:
  acpi_power_on_list()   -- 引用 +1,若从 0 变 1 则执行 _ON
  acpi_power_off_list()  -- 引用 -1,若从 1 变 0 则执行 _OFF

26.3 设备 D-state 转换与电源资源的协调

drivers/acpi/device_pm.c 中的 acpi_device_set_power() 负责在切换设备 D-state 时正确处理电源资源:

acpi_device_set_power(device, D3cold)
  |
  +-- 若目标 = D3cold:
  |     1. 执行 _PS3 方法(若存在)
  |     2. acpi_power_off_list(_PR3 资源)
  |           引用计数 -1,若归零则执行 _OFF
  |
  +-- 若目标 = D0:
        1. acpi_power_on_list(_PR0 资源)
              引用计数 +1,若从 0 变 1 则执行 _ON
        2. 等待 _PRx 稳定延迟
        3. 执行 _PS0 方法(若存在)

27. CPPC:协作式处理器性能控制

27.1 CPPC 概述

CPPC(Collaborative Processor Performance Control,协作式处理器性能控制)是 ACPI 5.0 引入的新型 CPU 性能控制框架,专为 ARM 服务器和现代 x86 设计,相比传统 _PSS 方案具有更细粒度的控制和更低的延迟。

CPPC 使用 _CPC(Continuous Performance Control)对象代替 _PSS,通过共享内存(PCC 通道或 MMIO)与固件通信:

OS (cppc_cpufreq 驱动)
      |
      | 写期望性能值到 Desired_Performance 寄存器
      v
+---------------------------+
|  PCC Mailbox Channel      |  或直接 MMIO 寄存器
|  (Platform Communication  |
|   Channel, 共享内存)      |
+---------------------------+
      |
      v
固件 / 平台控制器
      |  根据期望性能调整实际频率/电压
      v
硬件 CPU 频率

27.2 _CPC 寄存器描述

_CPC 对象描述了一组性能控制寄存器,每个寄存器用 GAS(Generic Address Structure)描述其位置(可以是 PCC 共享内存、MMIO、系统 IO 等):

_CPC 寄存器列表:
  [0]  Highest_Performance       最高性能点(整数,固定值)
  [1]  Nominal_Performance       额定性能(整数)
  [2]  Lowest_Nonlinear_Performance 最低非线性功效性能
  [3]  Lowest_Performance        最低性能
  [4]  Guaranteed_Performance_Register  当前保证性能(GAS)
  [5]  Desired_Performance_Register     OS 写入目标性能(GAS)
  [6]  Minimum_Performance_Register     OS 写入下限(GAS)
  [7]  Maximum_Performance_Register     OS 写入上限(GAS)
  [8]  Performance_Reduction_Tolerance  性能降低容忍度
  [9]  Time_Window_Register             性能采样窗口(GAS)
  [10] Counter_Wraparound_Time          计数器回绕时间
  [11] Reference_Performance_Counter_Register  参考计数器(GAS)
  [12] Delivered_Performance_Counter_Register  实际性能计数器(GAS)
  [13] Performance_Limited_Register    性能受限状态(GAS)
  [14] CPPC_Enable_Register            使能 CPPC(GAS)
  [15] Autonomous_Selection_Enable     自主选择使能
  [16] Autonomous_Activity_Window_Register
  [17] Energy_Performance_Preference_Register  能效偏好(GAS)
  [18] Reference_Performance          参考性能(整数)
  [19] Lowest_Frequency              最低频率(MHz)
  [20] Nominal_Frequency             额定频率(MHz)

27.3 CPPC 驱动 API

drivers/acpi/cppc_acpi.c 提供了 CPPC 的核心 API(行 1341):

// 读取 CPU 的性能能力
int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps);
  // 填充 highest_perf, lowest_perf, nominal_perf,
  //       lowest_nonlinear_perf, guaranteed_perf

// 读取当前性能计数器(用于计算实际频率)
int cppc_get_perf_ctrs(int cpunum, struct cppc_perf_fb_ctrs *perf_fb_ctrs);
  // 读取 reference_counter 和 delivered_counter

// 设置目标性能
int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls);
  // 写 desired_perf, min_perf, max_perf 到对应寄存器
  // 若使用 PCC 通道,触发 PCC 通知固件

28. 系统级电源管理:PM QoS 约束框架

28.1 PM QoS 概述

PM QoS(Power Management Quality of Service)框架允许内核组件(驱动、子系统)声明其对电源管理的延迟要求,防止系统进入某些会导致过高唤醒延迟的低功耗状态。

核心代码位于 kernel/power/qos.c,提供两类约束:

全局 QoS 约束:
  PM_QOS_CPU_LATENCY    CPU 退出 C-state 的最大允许延迟(us)
  PM_QOS_RESUME_LATENCY 系统从挂起恢复的最大允许延迟(us)

设备级 QoS 约束:
  pm_qos_resume_latency_default  设备恢复延迟
  pm_qos_latency_tolerance       设备延迟容忍度

28.2 PM QoS API 使用

// 添加一个 CPU 延迟约束(要求 CPU 唤醒延迟 ≤ 100us)
struct pm_qos_request my_qos;
pm_qos_add_request(&my_qos, PM_QOS_CPU_LATENCY, 100);

// 更新约束(放宽或收紧)
pm_qos_update_request(&my_qos, 200);  // 放宽到 200us

// 移除约束
pm_qos_remove_request(&my_qos);

// 查询当前全局最严格约束(cpuidle governor 使用)
int max_latency = pm_qos_read_value(PM_QOS_CPU_LATENCY);

28.3 PM QoS 与 cpuidle 的交互

cpuidle governor(menu/teo)在选择 C-state 时,会将 PM QoS 约束作为硬性限制:

// drivers/cpuidle/governors/menu.c(示意)
for (i = drv->state_count - 1; i >= CPUIDLE_DRIVER_STATE_START; i--) {
    s = &drv->states[i];
    /* 若此 C-state 的退出延迟超过 QoS 要求,跳过 */
    if (s->exit_latency > pm_qos_read_value(PM_QOS_CPU_LATENCY))
        continue;
    /* 若此 C-state 的驻留时间不够预期,跳过 */
    ...
}

28.4 网络延迟 QoS

网络驱动也使用 PM QoS 防止系统进入过深的 C-state,以确保网络响应时间。例如 Intel 以太网驱动在有活跃连接时会设置 PM_QOS_CPU_LATENCY 约束。


29. ACPI 与调度器协同:能效模型

29.1 能效模型(Energy Model)

kernel/power/energy_model.c 实现了 Linux 能效模型(Energy Model),为调度器(EAS,Energy Aware Scheduling)提供每个 CPU 在不同性能点的功耗估算。

struct em_perf_domain
  |
  +-- nr_perf_states
  +-- em_perf_state[]
        performance   性能归一化值(对应 CPU capacity)
        frequency     频率(kHz)
        power         在此性能点的典型功耗(mW)

能效模型的数据来源:

1. cpufreq 驱动通过 em_dev_register_perf_domain() 注册
   - 数据来自驱动自身知识(如 intel_pstate 从 RAPL 估算)
   - 或来自 DT/ACPI 属性(如 ARM 平台的 dynamic-power-coefficient)

2. ACPI _CPC 中的 energy-performance-preference 寄存器
   - 允许 OS 向固件表达能效偏好(0=最高性能,0xFF=最大节能)

29.2 EAS 与 cpufreq 协同

调度器通过 cpufreq_add_update_util_hook() 将 PELT 利用率信号传递给 cpufreq governor(schedutil),形成闭环:

调度器 PELT 信号
      |  (CPU 利用率 0~1024)
      v
schedutil governor
  cpufreq_driver->fast_switch() 或 target_index()
      |
      v
硬件频率变化
      |  (新频率影响 CPU capacity)
      v
调度器重新计算负载均衡

EAS 在选择任务放置的 CPU 时,会查询能效模型计算不同放置方案的总功耗,选择功耗最低但能满足性能需求的方案。


30. ACPI 固件接口演进:UEFI、_OSI 与兼容性

30.1 _OSI 方法

_OSI(Operating System Interface)方法允许固件查询 OS 支持的特性集,以便提供兼容的 ACPI 描述。内核在启动时主动注入一系列支持声明:

// drivers/acpi/osi.c(示意)
// 默认注入的 _OSI 字符串
"Linux"               // 表示这是 Linux 内核
"Linux-<version>"     // 版本相关声明
"Windows 2015"        // 兼容性声明(让固件使用更优化的路径)
"Module Device"       // 支持模块化设备
"Processor Device"    // 支持处理器设备方法
"3.0 _SCP Extensions" // ACPI 3.0 _SCP 扩展

用户可通过内核命令行参数 acpi_osi= 添加或删除 _OSI 声明,有时可解决特定平台的电源管理问题。

30.2 UEFI 与 ACPI 的集成

现代 UEFI 固件通过 UEFI 系统表(System Table)传递 RSDP 地址,内核从 EFI 系统表而非传统 BIOS 内存(E820 区域)定位 ACPI 表。

UEFI 固件
  |
  +-- EFI System Table
  |     +-- ConfigurationTable[]
  |           EFI_ACPI_20_TABLE_GUID -> 指向 RSDP
  |
  v
内核 efi_init()
  --> 遍历 ConfigurationTable,找到 ACPI GUID
  --> efi.acpi20 = RSDP 物理地址
  --> acpi_table_init() 使用此地址定位所有 ACPI 表

30.3 ACPI 减少硬件模式(Hardware-Reduced ACPI)

ACPI 5.0 引入了硬件减少模式(Hardware-Reduced ACPI),主要面向 ARM 和嵌入式平台。此模式下,固定功能硬件寄存器(PM1 事件、GPE 寄存器等)被移除,改用中断和共享内存实现。

FADT flags 中的 ACPI_FADT_HW_REDUCED(bit 20)标识此模式。内核在 acpi_gbl_reduced_hardware 全局变量为 true 时跳过传统 PM 寄存器操作:

// drivers/acpi/acpica/tbfadt.c:378
if (acpi_gbl_FADT.flags & ACPI_FADT_HW_REDUCED) {
    acpi_gbl_reduced_hardware = TRUE;
}

在 HW-Reduced 模式下:

  • GPE 由 GIC SPI 中断替代
  • PM1 事件寄存器不存在,睡眠/唤醒通过专用寄存器实现
  • SCI 中断不再需要

总结

Linux ACPI 与电源管理子系统是一个高度分层、协作紧密的框架:

用户空间 (UPower, PowerTOP, systemd)
    |
/sys/power/  /sys/devices/.../power/  /sys/devices/system/cpu/
    |
+---+-----------------------------------------------------------+
|                    内核电源管理核心                            |
|  kernel/power/   drivers/base/power/   include/linux/pm*.h   |
+---+---+---+---+----------------------------------------------+
    |   |   |   |
    |   |   |   +-- Runtime PM (pm_runtime_get/put, autosuspend)
    |   |   |
    |   |   +------- System Suspend/Hibernate (dpm_suspend/resume)
    |   |
    |   +----------- cpufreq (P-states, governor, schedutil)
    |
    +--------------- cpuidle (C-states, menu/teo governor)
    |
+---+---+---------------------------+---------------------------+
|       ACPI 子系统                                             |
| drivers/acpi/                                                 |
|  scan.c   bus.c   sleep.c   device_pm.c   processor_idle.c   |
|  pci_root.c  thermal.c  ec.c  battery.c  power.c             |
+---+---+-------------------------------+---------------------+-+
    |                                   |
    |   OSL (osl.c)                     |
    |                                   |
+---+---+-----------+           +-------+---+
|       ACPICA       |           |  cppc_acpi|
|  acpica/           |           |  (CPPC)   |
|  nsxfeval.c        |           +-----------+
|  evgpe.c           |
|  tbfadt.c          |
|  aclocal.h         |
+---+---------------+
    |
+---+-----------+
|   ACPI 固件   |
| DSDT/SSDT     |
| FADT/MADT     |
| MCFG/SRAT     |
+---------------+

核心设计思想:

  1. 分层解耦:固件仅描述硬件,OS 做出所有电源决策
  2. 统一抽象dev_pm_ops 为所有设备提供统一的 PM 回调接口
  3. 细粒度控制:Runtime PM 允许设备在系统运行时独立休眠
  4. 可调度协同:cpufreq + cpuidle + 调度器(schedutil/EAS)三者协同优化性能/功耗比
  5. 事件驱动:ACPI Notify 和 GPE 机制实现固件到 OS 的异步事件通知
  6. 兼容性协商_OSC/_OSI 机制保证 OS 与固件的功能协商和向后兼容
  7. 引用计数安全:GPE 使能、电源资源、Runtime PM 均使用引用计数避免竞态
  8. 两阶段枚举acpi_bus_scan 的两阶段设计优雅处理设备依赖(_DEP

由 Claude Code 分析生成