3. Wakeup源驱动框架:Linux内核的Wakeup源驱动模型

好,我们直接进入正题。Wakeup源驱动框架,说白了就是内核里一套管理「谁能唤醒系统」的机制。你想想看,手机待机时,为什么按一下电源键就能亮屏?为什么插上充电器也能唤醒?这些背后都是Wakeup源在起作用。

我个人习惯把Wakeup源驱动模型理解成「门卫登记系统」。每个能唤醒系统的硬件,都得在门卫(内核)那里登记一下。系统要休眠时,门卫会检查:还有没有人(Wakeup源)在活动?如果有,就不让睡。这套机制,就是今天我们要聊的核心。

3.1 struct wakeup_source结构体详解

先看最核心的数据结构。内核里用 struct wakeup_source 来描述一个Wakeup源。我当年第一次看这个结构体时,觉得它挺简单,后来踩过坑才发现,细节都在字段里。

struct wakeup_source {
    const char      *name;          // 名字,调试时靠它认人
    struct list_head    entry;       // 链表节点,所有ws挂在一个全局链表上
    spinlock_t      lock;           // 自旋锁,保护内部状态
    struct wake_irq    *wakeirq;     // 关联的硬件中断(可选)
    struct timer_list   timer;       // 定时器,用于自动超时
    unsigned long       timer_expires; // 定时器到期时间
    ktime_t         last_time;      // 上次状态变更时间
    ktime_t         start_prevent_time; // 开始阻止休眠的时间
    ktime_t         prevent_sleep_time; // 累计阻止休眠的总时长
    unsigned long       event_count;    // 事件计数
    unsigned long       active_count;   // 活跃状态计数
    bool            active:1;       // 当前是否活跃
    bool            autosleep_enabled:1; // 是否启用自动休眠
};

嗯,这里要注意 active 这个字段。它是个布尔值,表示当前这个Wakeup源是不是「醒着」的。如果 active = true,系统就不能进入深度休眠。我曾经在项目里遇到过一个bug,某个外设的驱动忘了把 active 置回false,结果手机一晚上掉电30%。排查了半天,最后发现是某个传感器在休眠前没释放唤醒锁。

event_countactive_count 这两个计数器也很有用。它们记录了Wakeup源被触发的次数和活跃的次数。调试时,我经常通过 /sys/kernel/debug/wakeup_sources 看这些计数,来判断某个外设是不是异常频繁地唤醒系统。

核心要点:struct wakeup_source 就是内核里每个唤醒源的「身份证」。它记录了名字、状态、统计信息。驱动开发者需要正确维护 active 状态,否则系统休眠会出问题。

3.2 wakeup_source_register/unregister API

有了结构体,接下来就是怎么注册和注销。内核提供了两个核心API:wakeup_source_registerwakeup_source_unregister

先看注册函数:

struct wakeup_source *wakeup_source_register(struct device *dev,
                         const char *name)
{
    struct wakeup_source *ws;
    int ret;

    ws = kzalloc(sizeof(*ws), GFP_KERNEL);
    if (!ws)
        return NULL;

    ws->name = name;
    ws->active = false;
    spin_lock_init(&ws->lock);
    timer_setup(&ws->timer, wakeup_source_deactivate_timer, 0);

    ret = wakeup_source_add(ws);
    if (ret) {
        kfree(ws);
        return NULL;
    }

    device_init_wakeup(dev, true);
    dev->power.wakeup->ws = ws;

    return ws;
}
EXPORT_SYMBOL_GPL(wakeup_source_register);

这段代码做了几件事:

  1. 分配内存,初始化结构体字段
  2. 初始化自旋锁和定时器
  3. 调用 wakeup_source_add 把ws挂到全局链表
  4. 通过 device_init_wakeup 把设备和Wakeup源关联起来

我建议你在写驱动时,尽量在 probe 函数里调用 wakeup_source_register。这样设备一加载,Wakeup源就注册好了。我曾经见过有人把注册放在 open 函数里,结果设备文件没打开时,系统就无法识别这个唤醒源,导致休眠异常。

再看注销函数:

void wakeup_source_unregister(struct wakeup_source *ws)
{
    if (ws)
        __wakeup_source_remove(ws);
}
EXPORT_SYMBOL_GPL(wakeup_source_unregister);

这个就简单多了。它会从全局链表里移除ws,并释放相关资源。注意,调用前要确保没有其他线程还在使用这个ws。我习惯在 remove 函数里调用它,顺序是:先关中断,再注销Wakeup源,最后释放其他资源。

避坑指南:我曾经在项目里犯过一个错误——在注销Wakeup源后,中断处理函数还在尝试访问它。结果内核直接panic。正确的做法是:先禁用设备中断,确保不会再产生唤醒事件,然后再注销Wakeup源。

3.3 Wakeup源驱动模型的核心逻辑

好了,结构体和API都讲完了。我们来看看整个驱动模型是怎么串起来的。下面这张图是我自己画的,能帮你快速理解整体框架。

Wakeup源驱动模型框架 硬件设备 GPIO / RTC / 充电器 ... 中断处理函数 __pm_wakeup_event() Wakeup源管理层 struct wakeup_source wakeup_source_register() wakeup_source_unregister() __pm_stay_awake() __pm_relax() 电源管理核心 suspend/resume流程 wakeup_count检查 pm_wakeup_pending() suspend_blocker 用户空间 /sys/devices/.../power/wakeup 硬件产生中断 → 驱动调用API更新ws状态 → 内核判断是否阻止休眠 → 用户空间可查询

从这张图你能看到,整个模型分四层:

  • 硬件层:各种外设,比如GPIO按键、RTC闹钟、充电器插入检测
  • 中断处理:硬件触发中断后,驱动在中断上下文调用 __pm_wakeup_event()
  • Wakeup源管理层:维护每个ws的状态,提供注册/注销/激活/去激活接口
  • 电源管理核心:在suspend流程中检查是否有活跃的ws,决定是否继续休眠

这里有个关键点:中断处理函数里不能直接调用 wakeup_source_register。为什么?因为注册函数可能会分配内存,而中断上下文不允许睡眠。正确的做法是:在 probe 时注册好,中断里只调用 __pm_wakeup_event 来标记事件。

个人经验:调试Wakeup源问题时,我经常用这个命令:cat /sys/kernel/debug/wakeup_sources。它会列出所有注册的Wakeup源,以及它们的活跃次数、总阻止休眠时间等。如果某个设备的 active_count 一直在涨,但系统却无法休眠,那八成就是它的问题。

3.4 驱动开发中的最佳实践

最后,我总结几条实战经验,希望能帮你少走弯路:

场景 推荐做法 常见坑
设备初始化 在probe中调用wakeup_source_register 在open或中断中注册,导致竞态
中断处理 调用__pm_wakeup_event,不直接操作ws 在中断里调用register或unregister
设备移除 先禁中断,再unregister 顺序反了,导致use-after-free
调试排查 查看debugfs中的wakeup_sources 只靠printk,效率太低

嗯,说到调试,我再分享一个技巧。如果你发现系统无法休眠,可以先检查 /sys/power/wakeup_count 这个文件。如果它一直在变化,说明有Wakeup源在不断产生事件。然后去 debugfs 里看具体是哪个设备在搞鬼。这套排查流程,我在项目里用过无数次,屡试不爽。

好了,关于Wakeup源驱动框架,我们就聊到这里。记住三个核心:struct wakeup_source 是身份证,register/unregister 是登记注销,__pm_wakeup_event 是事件上报。把这三点搞明白,你就能驾驭大部分Wakeup源的开发工作了。