基于 Linux Kernel 源码深度分析 路径:
drivers/acpi/|kernel/power/|drivers/cpufreq/
- ACPI 概述:从 BIOS 到 ACPI 的演进
- ACPI 对象模型:命名空间与 AML 解释器
- ACPI 设备枚举:总线发现与设备树对比
- 系统电源状态(S states):S0 到 S5
- 设备电源状态(D states):D0 到 D3cold
- Runtime PM 框架:运行时电源管理
- cpufreq 框架:CPU 频率调节
- cpuidle 框架:CPU 空闲状态管理
- 系统 Suspend 流程(S3)
- Hibernate 流程(S4):swsusp 机制
- 热插拔与电源事件:电池与 AC 适配器
drivers/acpi/与kernel/power/主要文件一览- 调试工具与接口
- ACPI 架构深度:ACPICA 集成与内核胶合层
- ACPI 表深度解析:FADT、MADT、DSDT、SSDT、MCFG
- AML 解释器:acpi_evaluate_object 全流程
- ACPI 中断模型:GPE 机制深度分析
- ACPI 全局电源状态:G0-G3 与 S0-S5 映射
- ACPI 设备枚举深度:scan.c 代码路径分析
- ACPI PCI 配置:MCFG 与 _OSC 协商深度分析
- ACPI platform bus 与 acpi_match_table
- ACPI 热管理深度:热区方法与 Linux thermal 框架集成
- ACPI C-state 与 P-state 深度:处理器电源状态
- ACPI 嵌入式控制器(EC)深度分析
- ACPI 唤醒机制:_PRW、GPE 与唤醒链路
- ACPI 电源资源管理:power.c 深度分析
- CPPC:协作式处理器性能控制
- 系统级电源管理:PM QoS 约束框架
- ACPI 与调度器协同:能效模型
- ACPI 固件接口演进:UEFI、_OSI 与兼容性
在 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 |
+-------------+
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() 阶段完成表的映射和验证。
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)。
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 解释器 (内核)
|
返回求值结果
| 方法 | 用途 |
|---|---|
_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: 电池存在
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);
...
}当设备被热插入时,ACPI 固件发送 Notify 事件(ACPI_NOTIFY_BUS_CHECK 或 ACPI_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);
}
}| 维度 | 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))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 和唤醒逻辑供电
| 状态 | 功耗 | 恢复时间 | 内存供电 | 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 可写入 freeze、standby、mem 或 disk 来触发对应睡眠状态。
现代平台(尤其是移动 ARM 和 Intel 低功耗平台)支持 S0ix(也称 suspend-to-idle 或 Modern Standby),它在不断开 CPU 上下文的情况下让系统进入极深的空闲状态:
S0ix = 冻结进程 + 挂起设备 + CPU 进入最深 C-state
(不断开内存,不需要 BIOS 参与)
实现位于 kernel/power/suspend.c 的 s2idle_loop() 函数(行 133),它将所有 CPU 推入 idle 循环并等待唤醒事件。
内核通过 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));
}除系统级睡眠状态外,ACPI 为每个设备定义了独立的设备电源状态(Dx states):
D0 全功耗,设备完全工作
|
D1 部分功耗,保留部分设备状态(可选,较少使用)
|
D2 更低功耗(可选)
|
D3hot 设备低功耗,但仍在总线上(可通过软件唤醒)
|
D3cold 设备完全断电(需要重新初始化才能使用)
ACPI 规范定义了系统睡眠状态与设备电源状态的最低对应关系:
系统状态 设备最低状态
S0 --> D0(设备可以在任何 D state)
S1 --> D1 或更深
S3 --> D3hot 或 D3cold
S4 --> D3cold
S5 --> D3cold
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)";
}
}Runtime PM(运行时电源管理)允许设备在系统运行时(S0 状态下)根据使用情况动态挂起和恢复,是实现精细化功耗控制的关键机制。
驱动调用
|
| 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) -- 空闲检查回调
pm_runtime_get()
+------+ +---------------+ +---------+
| | <---- | | ----> | |
| SUSP | | ACTIVE | | IDLE |
| | ----> | | <---- | |
+------+ +---------------+ +---------+
^ |
| autosuspend_delay 到期 |
+---------------------------------------+
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);
}为避免设备频繁在 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() 计算。
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,cpufreq 框架(drivers/cpufreq/cpufreq.c)提供了 CPU 动态电压频率调节(DVFS)的统一接口,是 CPU 性能与功耗平衡的核心机制。
用户空间 (/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 | --> 保守渐变
+------------------+
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;
...
};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);
};| Governor | 策略 | 适用场景 |
|---|---|---|
performance |
始终使用最高频率 | 性能测试、编译构建 |
powersave |
始终使用最低频率 | 极端省电、嵌入式 |
ondemand |
基于 CPU 使用率采样动态调频 | 通用桌面(旧式) |
conservative |
类似 ondemand 但频率渐变 | 笔记本保守模式 |
schedutil |
基于调度器 PELT 负载信号 | 现代推荐(默认) |
userspace |
由用户空间直接设定频率 | 测试/调试 |
schedutil 是当前推荐的 governor,它直接从调度器的 Per-Entity Load Tracking (PELT) 数据中获取 CPU 利用率,避免了额外的采样开销,实现了频率与负载的近实时响应。
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) // 变更后调度器的负载计算需要考虑 CPU 频率变化的影响。当 cpufreq_supports_freq_invariance() 返回 true 时(drivers/cpufreq/cpufreq.c:64),调度器会基于当前频率与最大频率的比值对计算量做归一化。
当 CPU 没有可运行任务时,cpuidle 框架决定 CPU 进入哪种空闲状态(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)
退出延迟:可达几毫秒
调度器 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)
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(过浅)
...
}| Governor | 预测方式 | 优势 | 劣势 |
|---|---|---|---|
ladder |
按使用频率逐步调整 | 简单可预测 | 反应慢,不适合突发负载 |
menu |
预测下次唤醒时间 + 统计校正 | 精确度高 | 计算量略大 |
teo (Timer Events Oriented) |
基于定时器到期时间 | 适合高精度场景 | 需要 hrtimer 支持 |
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。
当系统中所有 CPU 同时处于足够深的 C-state,整个 CPU Package(封装)才能进入 Package C-state(PC6/PC8/PC10),此时节能效益最大。cpuidle 框架和 menu/teo governor 会考虑这种协同效应。
系统 suspend 是将整个系统状态保存于内存、关闭大部分硬件的流程。
用户写入 /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 通知 |
+---------------------------+
设备挂起分多个阶段,以处理不同时机的依赖关系:
dpm_suspend_start()
--> prepare() 每个设备的 prepare 回调
--> suspend() 主 suspend 回调(中断仍开启)
dpm_suspend_late()
--> suspend_late() 在所有 suspend() 完成后,中断仍开启
dpm_suspend_noirq()
--> suspend_noirq() 禁止中断后的最终 suspend
此时设备必须静止(无 DMA/IO)
恢复时反向执行:resume_noirq → resume_early → resume → complete。
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);
...
}ACPI 要求在 S3/S4 时保存 NVS(Non-Volatile Sleep)内存区域(固件使用的特定内存),以确保恢复后固件状态一致。内核通过 drivers/acpi/nvs.c 实现这一功能,可通过内核命令行参数 acpi_sleep=nonvs 禁用(drivers/acpi/sleep.c:116)。
Hibernate(休眠,即 suspend-to-disk)将全部内存内容序列化写入磁盘,然后完全关机。恢复时从磁盘读回内存快照并继续运行。
用户写入 /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;
}系统启动(冷启动)
|
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)
--> 恢复设备、解冻进程、通知用户空间
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 的数据量,加快恢复速度。
物理内存
+------------------+
| 内核代码/数据 | -- nosave(不保存,恢复后重新使用新内核)
+------------------+
| 页表、struct page| -- nosave
+------------------+
| 驱动/模块数据 | -- 保存(标记为 PAGE_SAVE)
+------------------+
| 用户进程内存 | -- 保存
+------------------+
| 快照自身的内存 | -- 保存(存放压缩后的快照数据)
+------------------+
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 的设备)
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)。
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。
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
| 文件 | 职责 |
|---|---|
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 根桥枚举 |
| 文件 | 职责 |
|---|---|
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 期间控制台管理 |
| 文件 | 职责 |
|---|---|
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 |
/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 特性,需配置开启)
每个设备在 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 - 此设备触发唤醒的次数
/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)
PowerTOP 是 Intel 开发的功耗分析工具,提供:
- 每个设备/进程的唤醒次数统计
- CPU C-state 利用率分布
- 各 C-state 驻留时间比例
- 电源调优建议(可自动应用
--auto-tune)
PowerTOP 数据来源:
/proc/timer_stats --> 定时器唤醒源
/sys/kernel/debug/ --> ftrace 事件
perf_event --> CPU C-state 硬件计数器
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_MPERF、IA32_APERF、Package C-state 残差计数器。
内核在关键路径埋入了 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内核配置中的关键调试选项:
| 配置项 | 功能 |
|---|---|
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_levelACPICA(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);
}| 模块前缀 | 目录 | 职责 |
|---|---|---|
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 | 通用工具函数(内存、字符串、缓存) |
每个 ACPI 设备在内核中用 struct acpi_device(include/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 绑定的物理设备列表 |
+------------------------------------------+
内核启动时 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)
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)
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_UPGRADE 和 drivers/acpi/tables.c 中的 acpi_load_table() 函数实现。
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, ...)
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 实现。
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) /* 输出缓冲 */当 handle 与 pathname 同时提供时,pathname 是相对于 handle 的相对路径;若 pathname 以 \ 开头,则为绝对路径,handle 被忽略。
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 或错误码
内核为常见返回类型提供了包装函数,避免重复处理缓冲区分配:
// 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);_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.c、rsio.c、rsirq.c、rsmemory.c 等)。
AML 方法的返回值被封装为 union acpi_object(include/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;
};GPE(General Purpose Event)是 ACPI 提供的通用中断机制,用于报告各种硬件事件(如电池状态变化、AC 插拔、盖子开关、PCIe 热插拔等)。GPE 有两个来源:
- FADT GPE:由 FADT 中的
gpe0_block/gpe1_block定义,使用固定 I/O 端口 - GPE Block Device:由 ACPI 命名空间中的
ACPI0006设备提供,扩展 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 (引用计数)
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()
GPE 对应的 AML 方法命名规则:
_L00 ~ _L7F 电平触发的 GPE 方法(Level-triggered)
_E00 ~ _E7F 边沿触发的 GPE 方法(Edge-triggered)
命名中的数字为 GPE 号的十六进制表示。例如 _L1F 表示 GPE 号 31(0x1F)、电平触发。
驱动程序或 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 的引用:只有第一次使能时才真正写寄存器,最后一次释放时才真正禁用。
除 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() 分发。
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 过程。
全局视角的 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)
└── 完全断电
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 驱动)
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;
}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,匹配驱动时依次尝试。
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(通用回退路径)
ACPI 5.0 引入了 _DEP 方法,声明一个设备在枚举前需要等待另一个设备就绪(常见于 I2C 触摸板需要等待 I2C 控制器就绪)。
内核用 struct acpi_dep_data(drivers/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() 标记依赖满足,触发第二阶段扫描处理延迟设备。
_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 等
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) 下游端口遏制
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
苹果 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;
}当 ACPI 设备无法被任何 acpi_scan_handler 匹配,也没有注册专用的 acpi_driver 时,内核会自动将其创建为一个 platform_device(drivers/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);
...
}每个 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);
...
};内核在 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
--> 找到匹配则返回对应条目
以电源供应驱动 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 平台上可以编译。
acpi_device_id 的 driver_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;
}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 温度区)
_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)实现此转换。
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);
}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)
当温度超过 _PSV 时,ACPI 驱动通过调节 CPU 性能来降温,使用如下算法(TC1/TC2 常数来自 _TC1/_TC2):
性能调整量 = TC1 * (当前温度 - 上次温度) + TC2 * (当前温度 - PSV 温度)
这是一个比例-微分(PD)控制算法,避免温度大幅波动。
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),异步重新评估热区状态。
_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 框架。
_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[] 数组
// 频率合理性检查(必须递减排列)
// 过滤重复和无效条目_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)
_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 重新计算目标频率
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
嵌入式控制器(Embedded Controller,EC)是笔记本等便携设备中处理低级硬件交互的微控制器,负责电池管理、键盘背光、风扇控制、温度传感等。ACPI 定义了标准的 EC 通信协议(ACPI EC Interface),内核驱动位于 drivers/acpi/ec.c。
EC 通过两个 I/O 端口与主机通信:
- 数据端口(默认
0x62):读写 EC 内存 - 命令/状态端口(默认
0x66):发送命令和读取状态
EC 有两种操作模式:
轮询模式(Polling Mode)
- 适用于不支持 ECDT 的系统
- 通过 spinlock + 忙等待读取 EC
中断模式(Interrupt Mode,GPE 模式)
- EC 通过 GPE 通知主机有数据或事件
- 内核响应 GPE,触发 ec_handle_event()
- 更高效,减少 CPU 占用
// EC 读操作示意
ec_read(u8 addr, u8 *val)
|
+-- ec_transaction(EC_COMMAND_READ, addr, NULL, 0, val, 1)
|
+-- 等待 EC IBF(Input Buffer Full)清空
+-- 写命令到命令端口(0x66)
+-- 等待 EC IBF 清空
+-- 写地址到数据端口(0x62)
+-- 等待 EC OBF(Output 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 方法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 已清空:
唤醒等待发送的事务
_PRW(Power Resources for Wake)描述设备能够触发系统从睡眠状态唤醒所需的条件:
_PRW 返回值格式:
Package {
EventInfo, // GPE 号(整数)或 GPE 设备引用 + GPE 号
WakeLevel, // 此设备能够唤醒的最深睡眠状态(S1~S5)
PowerResource_1, // 唤醒所需的电源资源(可选)
PowerResource_2,
...
}
内核解析 _PRW(drivers/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用户写入 /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 位
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) 记录唤醒事件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)
}
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
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 方法(若存在)
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 频率
_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)
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 通知固件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 设备延迟容忍度
// 添加一个 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);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 的驻留时间不够预期,跳过 */
...
}网络驱动也使用 PM QoS 防止系统进入过深的 C-state,以确保网络响应时间。例如 Intel 以太网驱动在有活跃连接时会设置 PM_QOS_CPU_LATENCY 约束。
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=最大节能)
调度器通过 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 时,会查询能效模型计算不同放置方案的总功耗,选择功耗最低但能满足性能需求的方案。
_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 声明,有时可解决特定平台的电源管理问题。
现代 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 表
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 |
+---------------+
核心设计思想:
- 分层解耦:固件仅描述硬件,OS 做出所有电源决策
- 统一抽象:
dev_pm_ops为所有设备提供统一的 PM 回调接口 - 细粒度控制:Runtime PM 允许设备在系统运行时独立休眠
- 可调度协同:cpufreq + cpuidle + 调度器(schedutil/EAS)三者协同优化性能/功耗比
- 事件驱动:ACPI Notify 和 GPE 机制实现固件到 OS 的异步事件通知
- 兼容性协商:
_OSC/_OSI机制保证 OS 与固件的功能协商和向后兼容 - 引用计数安全:GPE 使能、电源资源、Runtime PM 均使用引用计数避免竞态
- 两阶段枚举:
acpi_bus_scan的两阶段设计优雅处理设备依赖(_DEP)
由 Claude Code 分析生成