📺 B站 嵌入式孙老师:博主个人介绍
📘 博主书籍-京东购买链接:Yocto项目实战教程
📘 加博主微信,进技术交流群: jerrydev
RK3576 唤醒机制实战:GPIO0、IRQ Wake 与 BL31 日志怎么读
前面已经分别总结过 echo mem 的低功耗入口,以及 PoE 上电和 WoL 的区别。这一篇不再展开“系统如何睡下去”,只聚焦一个问题:
RK3576 进入深度睡眠后,一个外部事件怎样穿过硬件、PMU、BL31 和 Linux,最终把系统唤醒?
我在调试 RTL8211F WoL 时发现,很多问题并不在 magic packet 本身,而在于没有把“唤醒链路”分层看清楚。DTS 写了 wakeup-source,不代表一定能醒;驱动调用了 enable_irq_wake(),也不代表深睡后产生中断的硬件还在工作。

一条可靠的唤醒链路,至少要满足四个条件:
外设在睡眠中仍能检测事件
↓
外设能输出有效的 wake 信号
↓
信号能进入 RK3576 的 PMU 唤醒路径
↓
Linux 在休眠前正确武装 wake IRQ
本文以 RTL8211F 的 PHY-PMEB WoL 为例,串起 RK3576 的 SoC 级配置、设备 DTS、驱动接口和 BL31 日志。
1. RK3576 的唤醒链路,不是只有一种
1.1 GPIO0:直接送入 PMU 的首选路径
RK3576 的 GPIO0 位于 PMU/Always-On 区域。GPIO0 上的中断可以直接送入 PMU 状态机,不依赖普通 GIC 路径。
外部器件
│
▼
GPIO0_A/B/C/D
│
▼
PMU wakeup logic
│
▼
BL31 恢复 CPU、DDR、时钟和电源域
对应的 SoC 配置位是:
#define RKPM_GPIO_WKUP_EN BIT(8)
在设备树中通过 rockchip,wakeup-config 打开:
&rockchip_suspend {
rockchip,wakeup-config = <
(0
| RKPM_GPIO_WKUP_EN
)
>;
};
GPIO0 适合连接电源键、PHY PMEB、蓝牙 HOST_WAKE 等关键唤醒信号。硬件设计阶段能放到 GPIO0 的,尽量不要放到普通 GPIO bank。
1.2 普通 IRQ:经过 GIC 和 CPU wake 路径
没有接到 GPIO0 的设备,一般通过普通中断进入 GIC,再由 CPU wake 路径唤醒系统。
外设 IRQ
│
▼
GIC
│
▼
CPU0 wake path
│
▼
PMU / BL31
对应配置位:
#define RKPM_CPU0_WKUP_EN BIT(0)
&rockchip_suspend {
rockchip,wakeup-config = <
(0
| RKPM_CPU0_WKUP_EN
)
>;
};
这条路径覆盖面更广,但约束也更多:中断控制器和相关电源域必须在当前睡眠深度下可用,驱动还必须把 IRQ 设置成 wake IRQ。唤醒源管理分散在各个驱动中,也更容易出现非预期唤醒。
1.3 专用唤醒源要和睡眠深度一起看
RK3576 还提供 USB、UART、PWM、SDIO、ADC、MCU、Timer 等专用唤醒配置:
RKPM_SDMMC_WKUP_EN
RKPM_SDIO_WKUP_EN
RKPM_USB_WKUP_EN
RKPM_UART_WKUP_EN
RKPM_MCU_WKUP_EN
RKPM_TIMER_WKUP_EN
RKPM_PWM_WKUP_EN
RKPM_TSADC_WKUP_EN
RKPM_SARADC_WKUP_EN
这些宏不是“打开就能用”。例如 UART、USB、PWM 唤醒依赖 24 MHz 时钟,如果休眠配置同时关闭 24 MHz 晶振,对应外设即使配置成 wakeup source,也无法在深睡中工作。
1.4 我理解唤醒配置的三层关系
| 层级 | 解决的问题 | 典型配置 |
|---|---|---|
| 外设层 | 谁检测事件、谁输出 wake 信号 | PHY PMEB、BT HOST_WAKE、按键、RTC |
| SoC 层 | 这类信号能否进入 PMU | RKPM_GPIO_WKUP_EN、RKPM_CPU0_WKUP_EN |
| Linux 层 | 具体哪个设备、哪个 IRQ 被允许唤醒 | wakeup-source、device_init_wakeup()、enable_irq_wake() |
少了任何一层,唤醒都可能失败。
2. 从 DTS 到驱动:一个设备怎样成为唤醒源
2.1 SoC 级:rockchip_suspend 打开唤醒通道
RK3576 的系统待机配置主要涉及三个位置:
drivers/soc/rockchip/rockchip_pm_config.c
drivers/firmware/rockchip_sip.c
include/dt-bindings/suspend/rockchip-rk3576.h
以 GPIO0 唤醒为例:
&rockchip_suspend {
status = "okay";
rockchip,sleep-debug-en = <1>;
rockchip,wakeup-config = <
(0
| RKPM_GPIO_WKUP_EN
)
>;
};
这段配置只解决一个问题:
GPIO0 中断是否允许进入 RK3576 的 PMU 状态机。
它并没有指定具体是哪一个 GPIO0 pin,也没有配置该 pin 的触发方式。
2.2 设备级:描述具体的 wake pin
以 RTL8211F 的 PHY_INTB/PMEB 接到 GPIO0_D0 为例:
&gmac0 {
phy-handle = <&rgmii_phy>;
mdio {
rgmii_phy: ethernet-phy@1 {
reg = <1>;
wakeup-gpios = <&gpio0 RK_PD0 GPIO_ACTIVE_LOW>;
wakeup-source;
};
};
};
这里表达了三件事:
gpio0 使用 GPIO0 bank
RK_PD0 对应 GPIO0_D0
GPIO_ACTIVE_LOW 低电平为逻辑有效状态
其中 wakeup-gpios 是驱动自行解析的板级属性,并不是写上以后内核就会自动完成所有操作。驱动仍然需要获取 GPIO、转换成 IRQ,并设置成 wake IRQ。
2.3 wakeup-source 只声明能力
wakeup-source;
它表示设备具备唤醒能力,但不能代替驱动中的硬件配置。
驱动通常需要在 probe 阶段完成:
device_init_wakeup(dev, true);
它主要建立设备的 wakeup 能力和 PM 统计入口。只调用:
device_set_wakeup_capable(dev, true);
虽然也能设置 can_wakeup,但不会完整初始化 wakeup source,调试统计不如 device_init_wakeup() 完整。
2.4 enable_irq_wake() 才是真正武装 IRQ
普通 IRQ 默认只负责系统运行期间的中断。要让它在 system suspend 中具备唤醒能力,需要:
enable_irq_wake(irq);
撤销时调用:
disable_irq_wake(irq);
要把下面三个概念分开:
| 配置 | 含义 |
|---|---|
wakeup-source | 设备具有唤醒能力 |
IRQF_TRIGGER_* | 什么电气条件产生中断 |
enable_irq_wake() | 该 IRQ 在系统休眠期间可以唤醒 CPU |
enable_irq_wake() 不会改变高低电平或边沿类型。
2.5 有效极性不等于中断触发类型
wakeup-gpios = <&gpio0 RK_PD0 GPIO_ACTIVE_LOW>;
GPIO_ACTIVE_LOW 表示“低电平是逻辑有效”,但不代表 IRQ 一定是下降沿触发。真正的触发方式由驱动申请 IRQ 时决定:
IRQF_TRIGGER_FALLING
IRQF_TRIGGER_RISING
IRQF_TRIGGER_LOW
IRQF_TRIGGER_HIGH
如果 PMEB 拉低后会一直保持,直到驱动清状态,那么低电平触发更符合硬件语义;如果硬件只输出短脉冲,则应使用边沿触发。触发方式必须根据器件行为确定,不能仅凭 ACTIVE_LOW 推断。
2.6 pm_wakeup_event() 不是第一次唤醒动作
中断处理函数中常见:
pm_wakeup_event(dev, 0);
它告诉 PM core:设备产生了 wakeup event,避免系统刚恢复就再次进入休眠,并更新唤醒统计。
但 CPU 已经深睡后,必须先由 GPIO/IRQ/PMU 的硬件路径把系统叫醒,ISR 才有机会执行。因此:
pm_wakeup_event()用于记录和维持清醒状态,不能代替底层硬件唤醒路径。
3. 以 RTL8211F WoL 为例看完整实现
3.1 为什么选择 PHY-PMEB,而不是 GMAC-PMT
RK3576 内部 GMAC 自带 PMT,可以检测 magic packet;RTL8211F 内部也有 WoL 匹配逻辑。两条路径的差别在于检测电路的位置:
| 路径 | 检测 magic packet 的位置 | 唤醒信号 |
|---|---|---|
| GMAC-PMT | RK3576 内部 GMAC | eth_wake_irq,走 GIC |
| PHY-PMEB | 外部 RTL8211F | PHY_INTB/PMEB,接 GPIO0_D0 |
当前深睡配置包含 ARMOFF_LOGOFF,VDD_LOGIC 会关闭,内部 GMAC-PMT 因此无法继续检测数据。外部 RTL8211F 只要睡眠期间保持供电,就可以继续监听网络,并通过 PMEB 拉低 GPIO0_D0。
完整链路如下:
Magic Packet
│
▼
RTL8211F 内部 WoL 匹配
│
▼
Pin31 切换为 PMEB,低有效
│
▼
PHY_INTB → GPIO0_D0
│
▼
RKPM_GPIO_WKUP_EN
│
▼
RK3576 PMU / BL31
│
▼
Linux resume
3.2 probe:独立获取 wake GPIO
在 drivers/net/phy/realtek.c 中,可以给 RTL8211F 增加私有数据:
struct rtl821x_priv {
struct gpio_desc *wakeup_gpiod;
int wakeup_irq;
bool wakeup_irq_armed;
};
probe 中获取 wakeup-gpios:
gpiod = devm_gpiod_get_optional(dev, "wakeup", GPIOD_IN);
if (IS_ERR(gpiod))
return PTR_ERR(gpiod);
if (gpiod) {
irq = gpiod_to_irq(gpiod);
if (irq < 0)
return irq;
ret = devm_request_threaded_irq(dev, irq,
NULL,
rtl8211f_wake_isr,
IRQF_ONESHOT |
IRQF_TRIGGER_LOW,
"rtl8211f-wol",
phydev);
if (ret)
return ret;
disable_irq(irq);
priv->wakeup_gpiod = gpiod;
priv->wakeup_irq = irq;
device_init_wakeup(dev, true);
}
如果使用低电平触发,中断处理完成前必须正确清除 PHY 的 PMEB 状态,否则会反复进入 ISR,最终形成中断风暴。
3.3 .set_wol():配置 PHY 并武装 wake IRQ
用户执行:
ethtool -s eth0 wol g
后进入 PHY 驱动的 .set_wol()。核心逻辑可以概括为:
static int rtl8211f_set_wol(struct phy_device *phydev,
struct ethtool_wolinfo *wol)
{
struct rtl821x_priv *priv = phydev->priv;
bool arm = wol->wolopts & WAKE_MAGIC;
int ret;
if (arm) {
/* 1. 写入目标 MAC 地址 */
/* 2. 使能 magic packet 匹配 */
/* 3. 将 pin31 从 INTB 切换为 PMEB */
/* 4. 清除历史状态,确认引脚处于 idle */
enable_irq(priv->wakeup_irq);
ret = enable_irq_wake(priv->wakeup_irq);
if (ret) {
disable_irq(priv->wakeup_irq);
return ret;
}
priv->wakeup_irq_armed = true;
} else if (priv->wakeup_irq_armed) {
disable_irq_wake(priv->wakeup_irq);
disable_irq(priv->wakeup_irq);
priv->wakeup_irq_armed = false;
/* 关闭 magic packet 匹配 */
/* pin31 恢复普通 INTB */
}
return 0;
}
WoL 武装放在 .set_wol(),比只放在 .suspend() 更可靠。因为 PHY 被标记为 wakeup device 后,phylib 的挂起流程可能跳过 PHY 自己的 .suspend()/.resume();如果全部依赖 .suspend(),相关代码可能根本不执行。
3.4 ISR:确认电平并清状态
static irqreturn_t rtl8211f_wake_isr(int irq, void *data)
{
struct phy_device *phydev = data;
struct rtl821x_priv *priv = phydev->priv;
int level;
level = gpiod_get_value_cansleep(priv->wakeup_gpiod);
phydev_info(phydev, "wol: PHY_INTB level=%d\n", level);
rtl8211f_ack_interrupt(phydev);
pm_wakeup_event(&phydev->mdio.dev, 0);
return IRQ_HANDLED;
}
真实顺序是:
PMEB 拉低
↓
PMU 先把系统唤醒
↓
CPU、内核和设备恢复
↓
ISR 才开始执行
因此 ISR 日志只能证明“Linux 恢复后看到了这个 IRQ”,不能单独证明它就是最初的 PMU 唤醒源。最终还要结合 BL31 的 wake up status。
3.5 这类实现最容易踩的坑
| 问题 | 表现 | 处理方式 |
|---|---|---|
PMEB 和普通 PHY IRQ 共用 phydev->irq | irq nobody cared、中断风暴 | PMEB 使用独立 wakeup-gpios,不要重新绑定成 phylib 链路 IRQ |
只在 .suspend() 中武装 | enable_irq_wake() 没有执行 | 把 WoL 配置放到 .set_wol() |
只调用 device_set_wakeup_capable() | wakeup 统计不完整 | 使用 device_init_wakeup() |
| 低电平 IRQ 没有正确 ack | ISR 持续触发 | 先确认 PHY 清状态流程,再使用 level-low |
| 武装时 PMEB 已经为低 | 一进入 suspend 就立即唤醒 | 武装前清旧状态,并打印 pin 电平 |
| PHY 睡眠期间掉电 | magic packet 永远无效 | 先确认 PHY 主电源和 PMEB 上拉电源都保持 |
| 发包接口不在同一广播域 | 抓不到 magic packet | 使用与设备同一交换网络的有线接口发送 |
4. BL31 日志怎么读,wake up status: 0x0 又代表什么
4.1 休眠前的日志是在确认“留了什么”
开启:
rockchip,sleep-debug-en = <1>;
进入深睡时,BL31 会打印类似:
INFO: BL31: ...
INFO: deep
INFO: logoff
INFO: pmualive_32k
INFO: 32k ext
INFO: sleep_pin: 0x3 0x2
INFO: GPIO0_INTEN: ...
INFO: IRQ_EN: 77
INFO: IRQ_EN: 137
...
这些信息主要描述:
- 当前进入了哪种睡眠模式;
- 哪些 sleep pin、GPIO 中断和系统 IRQ 被保留;
- 关键 power domain 和 PMU 寄存器状态。
其中 IRQ_EN: xxx 表示该 IRQ 在休眠配置中处于 enable 状态,它不是“最终触发唤醒的 IRQ 编号”。只看到 IRQ_EN,不能直接归因。
4.2 标准 GPIO0 唤醒日志
GPIO0 唤醒时,常见打印:
INFO: wake up status: 0x100
INFO: GPIO0 interrupt wakeup: 0x1000000
解析如下:
0x100
= BIT(8)
= RKPM_GPIO_WKUP_EN
= 本次由 GPIO0 类唤醒源触发
0x1000000
= BIT(24)
= GPIO0_D0
如果 RTL8211F PMEB 正好接在 GPIO0_D0,这两行就把硬件和软件链路对上了:
PHY PMEB → GPIO0_D0 → PMU → BL31
4.3 实测案例:系统恢复了,但状态是 0x0

图中的关键日志是:
012
INFO: wake up status: 0x0
987654fedcba3210
INFO: pd_st:0x3ffffc1, idle_st:0x7ffff
INFO: IRQ_EN: 77
...
这个例子很容易被误读。
012 和 987654fedcba3210 是流程标记
它们是 BL31 在唤醒/恢复流程中的阶段打印,用于确认固件状态机已经向前执行。它们能证明系统发生了恢复流程,但不能指出具体唤醒源。
wake up status: 0x0 只说明当前没有打印出有效的 PMU 唤醒位
更准确的解释是:
BL31 在这个打印点没有读取到、保留到或解码出一个非零的 PMU wake status。
它不能直接证明“没有唤醒事件”,因为系统已经进入恢复流程;也不能直接证明“这一定是普通 GIC IRQ 唤醒”。仅凭 0x0,无法完成归因。
可能的方向包括:
- 唤醒状态没有被当前固件版本正确解码;
- 状态在打印前已经被其他流程清除;
- 串口抓取中存在覆盖、丢字或多轮 suspend 日志混杂;
- 这次并不是稳定进入最终低功耗等待后再被标准 wake source 唤醒;
- 使用了 BL31 日志没有进一步展开的唤醒路径。
IRQ_EN 只能列候选,不能确认元凶
图中出现多个:
IRQ_EN: 77
IRQ_EN: 138
IRQ_EN: 137
...
这些是休眠期间被允许的 IRQ 列表,不表示这些 IRQ 都触发过,也不表示排在第一行的 IRQ 就是唤醒源。
因此,看到 wake up status: 0x0 时,不应从 IRQ_EN 中随便挑一个进行归因,而应继续找 Linux 和设备侧证据。
4.4 我实际使用的定位顺序
第一步:确认是否真的进入 deep
cat /sys/power/mem_sleep
# s2idle [deep]
echo mem > /sys/power/state
串口中应看到:
deep
logoff / armoff_logoff
012...
...3210
如果只看到:
Wakeup pending. Abort CPU freeze
这是 suspend 在冻结阶段被取消,不是“已经深睡后又被唤醒”。
第二步:休眠前检查 wake IRQ 是否已武装
ethtool eth0 | grep -i wake
cat /proc/interrupts | grep -iE "rtl|eth|gpio"
grep -H . /sys/bus/mdio_bus/devices/*/power/wakeup 2>/dev/null
需要确认:
Wake-on: g
对应 MDIO/PHY 设备 power/wakeup 为 enabled
wake IRQ 已经注册
第三步:对比 wakeup_sources
cat /sys/kernel/debug/wakeup_sources > /tmp/wakeup_before
# 执行 suspend / resume
cat /sys/kernel/debug/wakeup_sources > /tmp/wakeup_after
重点关注:
| 字段 | 含义 |
|---|---|
active_count | 唤醒源进入 active 的次数 |
event_count | 记录到的 wakeup event 次数 |
wakeup_count | 真正参与阻止休眠或唤醒的次数 |
active_since | 非 0 表示当前仍然 active |
第四步:把三类证据对在一起
| 证据层 | 看什么 |
|---|---|
| BL31/PMU | wake up status、GPIO bank 状态 |
| Linux PM | wakeup_sources 前后变化、suspend/resume 日志 |
| 设备驱动 | ISR、电平、ack、WoL 寄存器读回 |
最可靠的结论通常需要三层证据一致:
BL31:GPIO0_D0 唤醒
Linux:对应 wakeup source 计数增加
驱动:PHY_INTB ASSERTED,并成功 ack
4.5 常见现象快速判断
| 现象 | 优先检查 |
|---|---|
echo mem 立即返回 | wakeup_sources 是否有 active source |
| 能睡但 WoL 不醒 | PHY 是否有电、PMEB 是否切换、GPIO0 wake 是否使能 |
| 不发包也自己醒 | PMEB 电平、链路切换、历史状态、IRQ 类型 |
| BL31 显示 GPIO0 唤醒,驱动没有 ISR | IRQ 恢复后未使能、触发类型不匹配、状态提前释放 |
| 驱动有 ISR,但 BL31 没显示对应 GPIO | ISR 可能发生在系统恢复之后,不一定是最初唤醒源 |
wake up status: 0x0 | 不直接归因,联合分析 BL31、wakeup_sources 和驱动日志 |
irq nobody cared | IRQ 未正确 ack,或普通 PHY IRQ 与 PMEB 复用冲突 |
gpioget 报 busy | GPIO 已被驱动占用,查看 /sys/kernel/debug/gpio |
4.6 最后总结成一句话
RK3576 的唤醒不是一个配置项,而是一条完整链路:
事件检测器持续供电
↓
外设输出有效 wake 信号
↓
GPIO0 / GIC / 专用唤醒通道进入 PMU
↓
BL31 恢复系统
↓
Linux 处理 IRQ、记录 wakeup event 并恢复设备
以 RTL8211F WoL 为例:
Magic Packet
→ RTL8211F PMEB
→ GPIO0_D0
→ RKPM_GPIO_WKUP_EN
→ PMU / BL31
→ Linux resume
排查时最重要的不是先改寄存器,而是按层确认:检测器有没有电、wake pin 有没有变化、PMU 有没有收到、Linux 有没有武装 IRQ、驱动恢复后有没有正确 ack。
把这条链路理解清楚后,WoL、蓝牙 HOST_WAKE、GPIO 按键、RTC 和其他外设唤醒,都可以用同一套方法分析。
248

被折叠的 条评论
为什么被折叠?



