ACPI休眠按钮触发S0切换到S3流程(附流程图)
- [1 两种硬件实现路径](#1 两种硬件实现路径)
- [2 完整软硬件流程(S0 → S3)](#2 完整软硬件流程(S0 → S3))
-
- [第一阶段:硬件触发(物理层 → 寄存器层)](#第一阶段:硬件触发(物理层 → 寄存器层))
- 第二阶段:OSPM中断处理(内核层)
- [第三阶段:软件准备(用户层 → 内核层)](#第三阶段:软件准备(用户层 → 内核层))
- 第四阶段:ACPI控制方法执行(AML层)
- 第五阶段:最终硬件操作(寄存器写入)
- 第六阶段:唤醒路径(简述)
- [3 完整流程图](#3 完整流程图)
- [4 关键寄存器与控制方法速查](#4 关键寄存器与控制方法速查)
本文属于《 ACPI规范基础系列教程》之一,欢迎查看其它文章。
从硬件(休眠按钮)触发一个电平变化,到操作系统优雅地冻结用户空间、挂起设备驱动,最后让CPU断电、内存进入自刷新------这一切都必须严格遵循ACPI(高级配置与电源接口)这个复杂的标准。
本文将带你以初学者的视角,从头梳理ACPI规范中,从 S0(工作状态) 进入 S3(Suspend to RAM,挂起到内存) 的完整流程。
1 两种硬件实现路径
在深入流程前,需要明确:休眠按钮在硬件层面有两种实现方式,这会直接影响中断处理路径:
| 实现方式 | 硬件信号路径 | 中断类型 | 状态寄存器 |
|---|---|---|---|
| 固定硬件方案 | 按钮 → EC → PWRBTN#引脚 → 芯片组 | 固定事件 | PM1状态寄存器(SLPBTN_STS位) |
| Control Method方案 | 按钮 → EC → GPIO引脚变化 → 芯片组 | 通用事件(GPE) | GPE状态寄存器(某GPIO位) |
下面以常见的固定硬件方案为主线,进行介绍。Control Method方案的差异点我会在流程中标注。
2 完整软硬件流程(S0 → S3)
第一阶段:硬件触发(物理层 → 寄存器层)
- 用户按下休眠按钮:产生物理信号。
- 嵌入式控制器捕获 :EC检测到按钮按下,根据平台设计:
- 固定硬件方案:EC向芯片组的PWRBTN#专用引脚发送一个低脉冲。
- Control Method方案:EC改变某个GPIO引脚的电平状态。
- 芯片组记录事件 :
- 固定硬件方案 :芯片组将PM1状态寄存器 中的
SLPBTN_STS位置为1(PM1x_STS.SLPBTN_STS)。 - Control Method方案 :芯片组将GPE状态寄存器 中对应GPIO的某位置为
1(GPEx_STS.yy)。
- 固定硬件方案 :芯片组将PM1状态寄存器 中的
- SCI中断触发 :
- 固定硬件方案 :芯片组检查对应的PM1使能寄存器 中
SLPBTN_EN使能位(PM1x_EN.SLPBTN_EN) - Control Method方案 :芯片组检查对应的GPE使能寄存器 中
GPE enable使能位(GPEx_EN.yy)
- 固定硬件方案 :芯片组检查对应的PM1使能寄存器 中
如果使能位为1,芯片组拉高SCI中断线,通知CPU有电源管理事件需要处理。
PM1控制寄存器中的SCI_EN=0 时,事件路由到 SMI中断(固件处理);SCI_EN=1 时,事件路由到 SCI中断(操作系统处理)。
- 在ACPI/Legacy系统中,如果操作系统尚未设置 SCI_EN 位,你按下休眠按钮,只会触发SMI,系统可能根本不会进入你预期的S3状态。
- 在仅支持ACPI的系统中,由于 SCI_EN 始终为1,你的按钮事件会直接通过SCI通知操作系统,操作系统便会执行后续进入S3状态流程。
第二阶段:OSPM中断处理(内核层)
- CPU响应SCI:CPU执行中断向量,进入ACPI驱动注册的中断处理程序。
- 中断处理程序轮询 :
- 首先读取PM1状态寄存器 ,发现
SLPBTN_STS位为1,确认是固定事件中的休眠按钮。 - (如果是Control Method方案,则会在GPE状态寄存器中找到对应位,然后执行AML中的
_Lxx或_Exx方法,该方法通常包含Notify(***, 0x80)来通知操作系统)。
- 首先读取PM1状态寄存器 ,发现
- 执行固定事件处理逻辑 :
- 调用内核中预定义的休眠按钮处理函数(如Linux中的
acpi_button_notify)。 - 向PM1状态寄存器的
SLPBTN_STS位写入1,清除该状态位,表示事件已接收。
- 调用内核中预定义的休眠按钮处理函数(如Linux中的
- 生成输入事件 :内核通过输入子系统向用户空间报告
KEY_SLEEP事件,唤醒用户空间的电源管理服务(如systemd-logind或acpid)。
第三阶段:软件准备(用户层 → 内核层)
- 用户空间策略决策:电源管理服务根据系统配置决定进入哪个休眠状态(通常是S3 mem)。
- 触发内核休眠 :用户空间将
"mem"写入/sys/power/state文件。 - 内核开始休眠流程 :调用
state_store()→enter_state()→suspend_prepare():- 同步文件系统
- 冻结用户空间进程
- 调用设备驱动的suspend回调,逐个挂起设备
第四阶段:ACPI控制方法执行(AML层)
经过通用电源管理框架(enter_state → suspend_prepare → suspend_devices_and_enter),最终通过平台钩子进入ACPI驱动层,由 acpi_sleep_prepare 调用BIOS提供的 _PTS 控制方法,完成了从"用户层"到"内核通用框架"再到"AML层"的完整交接。
- 调用_PTS方法 :ACPI驱动执行ACPI控制方法
\_PTS(Prepare To Sleep),并将目标S状态(如3)作为参数传入。此方法由BIOS提供 ,通常用于:- 通知嵌入式控制器系统即将睡眠
- 保存某些平台特定的硬件状态
- 调用SMM代码进行底层配置
- 调用_GTS方法 (可选):如果存在,执行
\_GTS(Going To Sleep)方法,进一步进行平台特定的睡眠准备。 - 禁用/使能GPE:内核禁用所有非唤醒源的GPE,仅保留标记为可唤醒设备的GPE。
每个GPE对应两个寄存器:
- 状态(STS):在 GPE0_STS 或 GPE1_STS 中。当事件发生时硬件置1,软件写1清除。
- 使能(EN):在 GPE0_EN 或 GPE1_EN 中。软件写1允许该GPE触发中断,写0则屏蔽。
如果不加选择地使能所有GPE,那么任何风吹草动(比如电源噪声)都可能唤醒系统。所以操作系统必须只使能那些被用户或策略允许唤醒的设备对应的GPE。
软件需要提前告诉硬件:哪些GPE是允许唤醒的,并且确保它们被正确使能。
第五阶段:最终硬件操作(寄存器写入)
- 获取SLP_TYP值 :内核从FADT表中获取PM1a/PM1b控制寄存器 的地址,并从
\_S3对象中获取该S状态对应的SLP_TYP值。 - 设置唤醒向量 :内核将唤醒时执行的代码地址,写入Firmware Waking Vector(位于FADT表中FIRMWARE_CTRL指向的FACS结构中),供BIOS在唤醒后跳转。
- 写入PM1控制寄存器 :
- 先将
SLP_TYP值写入PM1a和PM1b控制寄存器的对应位。 - 刷新CPU缓存。
- 最后将
SLP_TYP和SLP_EN位同时写入PM1a和PM1b控制寄存器。
- 先将
- 硬件进入休眠 :芯片组检测到
SLP_EN位被设置,开始控制电源时序:- 停止CPU时钟
- 切断除内存外的多数设备电源
- 内存进入自刷新模式保持数据
- 系统进入S3状态
第六阶段:唤醒路径(简述)
当用户再次按下电源按钮唤醒时:
- 芯片组恢复电源,CPU从重置向量开始执行
- BIOS检测到是从S3唤醒,跳转到Firmware Waking Vector指向的地址
- 内核恢复代码执行,调用
\_WAK方法 - 恢复设备驱动,解冻进程,返回用户空间
3 完整流程图
下面是根据上述流程,绘制的完整流程图:


4 关键寄存器与控制方法速查
为方便理解软硬件,以下是流程中涉及的寄存器/方法及其作用:
| 名称 | 类型 | 作用 | 所在位置 |
|---|---|---|---|
SLPBTN_STS |
PM1状态寄存器位 | 休眠按钮状态,硬件置1,软件写1清除 | FADT中PM1a_EVT_BLK |
SLPBTN_EN |
PM1使能寄存器位 | 使能休眠按钮的SCI中断 | FADT中PM1a_EVT_BLK |
SLP_EN |
PM1控制寄存器位 | 写入该位触发硬件进入休眠 | FADT中PM1a_CNT_BLK |
SLP_TYP |
PM1控制寄存器位 | 指定要进入的S状态(值从_Sx对象获取) | FADT中PM1a_CNT_BLK |
_PTS |
控制方法 | 准备进入睡眠,S状态作为参数传入 | DSDT |
_GTS |
控制方法 | Going To Sleep,可选 | DSDT |
_S3 |
控制方法 | 返回S3状态对应的SLP_TYP值 | DSDT |
_WAK |
控制方法 | 唤醒后执行 | DSDT |
_Lxx/_Exx |
GPE控制方法 | 对应GPE位的处理程序,Control Method方案中调用 | DSDT的_GPE作用域 |