7、Wakeup源实战案例:GPIO按键唤醒、RTC定时唤醒、网络数据包唤醒

好,咱们今天聊点实在的。

前面讲了那么多wakeup源的理论,什么irq、abort、wakeup event,说实话,光看概念容易晕。我个人习惯是,直接上手调几个典型场景,比看十遍文档都管用。

这一章,我就拿三个最常见的唤醒源来拆解:GPIO按键唤醒RTC定时唤醒网络数据包唤醒。这三个场景覆盖了90%的嵌入式Android设备需求。你想想看,手机、平板、IoT盒子,哪个不需要按键开机?哪个不需要定时上报?哪个不需要网络保持连接?

7.1 GPIO按键唤醒:最基础的物理唤醒

先讲最简单的。GPIO按键唤醒,说白了就是按一下物理按键,把系统从suspend状态拉回来。

我在项目中遇到过一个问题:按键能唤醒,但唤醒后触摸屏没反应。查了半天,发现是GPIO的wakeup能力没配好,中断虽然触发了,但对应的power domain没起来。嗯,这里要注意。

7.1.1 硬件层面:GPIO必须支持唤醒

不是所有GPIO都能当wakeup源。你得确认这颗GPIO在SoC的wakeup controller里。比如高通平台,只有TLMM_GPIO里标记了WAKEUP属性的引脚才行。

我曾经踩过一个坑:选了个普通GPIO,结果suspend后电平变化根本触发不了中断。后来查datasheet才发现,这颗引脚没有wakeup能力。

7.1.2 软件配置:三步走

  1. 设备树里配好wakeup-source属性必须加。
  2. 驱动里注册irq时设flagIRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND
  3. 用户空间验证:读/sys/kernel/debug/wakeup_sources确认。

核心代码示例(设备树)

gpio_keys {
    compatible = "gpio-keys";
    pinctrl-names = "default";
    pinctrl-0 = <&key_pwr_default>;

    power_key {
        label = "Power Key";
        gpios = <&tlmm 42 GPIO_ACTIVE_LOW>;
        linux,code = <KEY_POWER>;
        gpio-key,wakeup;  // 关键:标记为wakeup源
        debounce-interval = <10>;
    };
};

避坑指南:我曾经在某个项目里忘了加gpio-key,wakeup属性,结果按键能响应,但suspend后按了没反应。查了两天才发现是设备树漏了。记住,设备树里必须显式声明

7.2 RTC定时唤醒:让设备自己醒来

RTC唤醒,说白了就是定个闹钟。系统进入suspend后,RTC硬件还在跑,到点就触发中断,把系统拉起来。

这个场景在IoT设备里特别常见。比如智能门锁,每天凌晨3点上报一次状态,然后继续睡。你想想看,如果不用RTC唤醒,就得让CPU一直跑着,功耗直接翻倍。

7.2.1 RTC驱动要点

Android里RTC驱动一般走alarmtimer框架。用户空间通过/dev/alarm或者timerfd来设置唤醒时间。

我建议你直接看drivers/rtc/rtc-pm8xxx.c这个驱动,高通平台的RTC实现很典型。

7.2.2 用户空间设置示例

// 使用alarmtimer设置10秒后唤醒
#include <linux/alarmtimer.h>

struct itimerspec spec;
spec.it_value.tv_sec = 10;
spec.it_value.tv_nsec = 0;
spec.it_interval.tv_sec = 0;
spec.it_interval.tv_nsec = 0;

timerfd_settime(fd, TFD_TIMER_ABSTIME, &spec, NULL);

注意:RTC唤醒时间不能设得太短。我见过有人设了1秒唤醒,结果系统刚suspend就被唤醒,根本睡不下去。建议至少设5秒以上,给系统留足进入suspend的时间。

7.3 网络数据包唤醒:最难搞的唤醒源

网络唤醒,这是三个里面最复杂的。为什么?因为网络协议栈太深了,从物理层到应用层,每一层都可能丢包或者误唤醒。

我记得有一次做智能音箱项目,要求WiFi待机时能收到唤醒包。结果发现,路由器发的ARP包都能把设备唤醒,一晚上被唤醒了上百次。这功耗,直接崩了。

7.3.1 WoWLAN(Wake on Wireless LAN)

WiFi芯片一般支持WoWLAN功能。说白了,就是网卡在suspend时还保持部分电路工作,收到特定模式的数据包就触发中断。

配置要点:

  • 网卡驱动必须支持iw phy0 wowlan enable magic-packet
  • Android框架层WifiManager.setWifiEnabled(true) 并保持WIFI_MODE_FULL_HIGH_PERF
  • 内核配置CONFIG_PM_WAKELOCKSCONFIG_WLAN 要打开

7.3.2 实战:只允许特定UDP包唤醒

我建议的做法是,在网卡驱动里注册一个ndo_set_wakeup_filter回调,只匹配特定端口和IP的数据包。

static int my_ndo_set_wakeup_filter(struct net_device *dev,
                                     struct ethtool_wolinfo *wol)
{
    // 只允许UDP 1234端口的数据包唤醒
    if (wol->wolopts & WAKE_UCAST) {
        // 设置硬件过滤规则
        set_wakeup_filter(dev, UDP_PORT, 1234);
    }
    return 0;
}

核心原则:网络唤醒的过滤规则越严格越好。我曾经见过一个产品,因为过滤规则太宽,被广播风暴活活唤醒到没电。最后改成只匹配特定MAC+特定端口,才解决问题。

7.4 三种唤醒源对比

唤醒源 延迟 功耗影响 调试难度 典型场景
GPIO按键 <10ms 极低(uA级) 物理按键、传感器触发
RTC定时 <50ms 低(uA级) 定时上报、闹钟
网络数据包 100ms~1s 中(mA级) 远程唤醒、推送服务

7.5 调试工具与技巧

最后,分享几个我常用的调试命令。这些命令能帮你快速定位唤醒源问题。

  • 查看当前wakeup源统计cat /sys/kernel/debug/wakeup_sources
  • 查看最后一次唤醒原因cat /sys/power/wakeup_last_time
  • 查看suspend失败原因dmesg | grep "PM: suspend"
  • 查看RTC唤醒设置cat /proc/driver/rtc

个人经验:我调试网络唤醒时,最喜欢用tcpdump抓包配合wakeup_sources一起看。先确认有没有收到包,再确认包有没有触发唤醒。很多时候,问题出在驱动过滤规则上,而不是硬件。

Wakeup源实战:三种唤醒路径 GPIO按键唤醒 RTC定时唤醒 网络数据包唤醒 中断控制器(GIC) PM核心层 wakeup_source框架 系统唤醒 三种唤醒源最终都通过wakeup_source框架通知PM核心层,触发系统唤醒流程

好了,这三种唤醒源,你只要动手调一遍,基本就能摸清Android电源管理的门道。GPIO最简单,RTC最稳定,网络最折腾。我个人建议,先从GPIO入手,再试RTC,最后啃网络唤醒。这样循序渐进,不容易被劝退。