RK3576 唤醒机制实战:GPIO0、IRQ Wake 与 BL31 日志怎么读

嵌入式开发板网络配置避坑

RK3576 Android14 有线网卡静态 IP 配置全攻略,附数据实操


📺 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 层这类信号能否进入 PMURKPM_GPIO_WKUP_ENRKPM_CPU0_WKUP_EN
Linux 层具体哪个设备、哪个 IRQ 被允许唤醒wakeup-sourcedevice_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-PMTRK3576 内部 GMACeth_wake_irq,走 GIC
PHY-PMEB外部 RTL8211FPHY_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->irqirq nobody cared、中断风暴PMEB 使用独立 wakeup-gpios,不要重新绑定成 phylib 链路 IRQ
只在 .suspend() 中武装enable_irq_wake() 没有执行把 WoL 配置放到 .set_wol()
只调用 device_set_wakeup_capable()wakeup 统计不完整使用 device_init_wakeup()
低电平 IRQ 没有正确 ackISR 持续触发先确认 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
...

这个例子很容易被误读。

012987654fedcba3210 是流程标记

它们是 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/PMUwake up status、GPIO bank 状态
Linux PMwakeup_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 唤醒,驱动没有 ISRIRQ 恢复后未使能、触发类型不匹配、状态提前释放
驱动有 ISR,但 BL31 没显示对应 GPIOISR 可能发生在系统恢复之后,不一定是最初唤醒源
wake up status: 0x0不直接归因,联合分析 BL31、wakeup_sources 和驱动日志
irq nobody caredIRQ 未正确 ack,或普通 PHY IRQ 与 PMEB 复用冲突
gpioget 报 busyGPIO 已被驱动占用,查看 /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 和其他外设唤醒,都可以用同一套方法分析。

嵌入式开发板网络配置避坑

RK3576 Android14 有线网卡静态 IP 配置全攻略,附数据实操

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值