4. Wakeup源中断处理:IRQ与Wakeup中断的关系

好,咱们今天聊聊中断。准确地说,是聊聊那些能把系统从睡梦中叫醒的中断。

我记得刚接触Android电源管理那会儿,有个问题困扰了我很久:同样是中断,为什么有的能唤醒系统,有的就不行?后来我才明白,这背后其实是IRQ和Wakeup中断的微妙关系。

4.1 IRQ与Wakeup中断:到底有啥区别?

先说IRQ。IRQ是硬件中断请求,是外设跟CPU打招呼的方式。比如你按一下电源键,按键控制器就会触发一个IRQ,告诉CPU“嘿,有人按我了”。

但Wakeup中断呢?它其实是IRQ的一种特殊形态。说白了,就是那些被标记为“可以在系统休眠时把我叫醒”的中断。

我打个比方:

  • IRQ 就像你家的门铃。你在家的时候,门铃响了你就去开门。
  • Wakeup中断 就像你睡觉时,门铃还能把你吵醒。如果门铃没接电源,你睡得再死也听不见。

在Linux内核里,每个IRQ都可以通过一个开关,决定它是否具备唤醒能力。这个开关,就是我们要讲的 enable_irq_wakedisable_irq_wake

核心要点:Wakeup中断 = IRQ + 唤醒标记。没有标记的IRQ,在系统休眠时会被屏蔽,根本不会触发。

4.2 enable_irq_wake / disable_irq_wake 函数

这两个函数,是电源管理驱动里最常用的API之一。我几乎在每个设备的驱动里都会看到它们的身影。

先看原型:

int enable_irq_wake(unsigned int irq);
int disable_irq_wake(unsigned int irq);

参数就是一个IRQ号。返回值0表示成功,负数表示出错。

它们做了什么?

  • enable_irq_wake:告诉内核,这个IRQ在系统休眠时不要被屏蔽,并且要能唤醒系统。
  • disable_irq_wake:取消这个标记,恢复普通IRQ的行为。

嗯,这里要注意:这两个函数必须成对调用。你启用了唤醒,就一定要在不需要时禁用。否则系统可能会莫名其妙地被唤醒,电池耗得飞快。

我曾经遇到过一个问题:某个触控屏驱动在suspend时调用了enable_irq_wake,但在resume时忘了调用disable_irq_wake。结果系统每次休眠后,稍微碰一下屏幕就醒了。排查了好久才发现是引用计数没配对。

内核里是怎么实现的?其实很简单,就是维护一个引用计数:

int enable_irq_wake(unsigned int irq) {
    struct irq_desc *desc = irq_to_desc(irq);
    if (!desc)
        return -EINVAL;
    
    desc->wake_depth++;
    if (desc->wake_depth == 1) {
        // 第一次启用,设置唤醒标记
        irq_set_wake(irq, 1);
    }
    return 0;
}

int disable_irq_wake(unsigned int irq) {
    struct irq_desc *desc = irq_to_desc(irq);
    if (!desc)
        return -EINVAL;
    
    if (WARN_ON(desc->wake_depth == 0))
        return -EINVAL;
    
    desc->wake_depth--;
    if (desc->wake_depth == 0) {
        // 最后一次禁用,清除唤醒标记
        irq_set_wake(irq, 0);
    }
    return 0;
}

看到没?wake_depth 这个字段很关键。它允许多个驱动模块同时对一个IRQ启用唤醒,只有所有人都禁用了,才会真正关闭唤醒能力。

4.3 中断唤醒流程分析

好,现在咱们把整个流程串起来。系统从休眠到被唤醒,到底经历了什么?

我画了一张图,帮你理清思路:

中断唤醒完整流程 外设触发中断 中断控制器(GIC) 是否唤醒中断? (wake_depth > 0) 唤醒CPU 中断被屏蔽 执行中断处理函数 恢复后继续处理 硬件动作 软件判断 唤醒路径

流程其实不复杂,我拆解一下:

  1. 外设触发中断:比如你按了电源键,按键控制器拉高了一个GPIO电平。
  2. 中断控制器接收:GIC(Generic Interrupt Controller)收到信号,检查这个中断的配置。
  3. 判断是否为唤醒中断:内核检查这个IRQ的 wake_depth 是否大于0。如果是,就走唤醒路径;否则,直接屏蔽。
  4. 唤醒CPU:如果被标记为唤醒中断,GIC会向CPU发送一个唤醒信号,把CPU从休眠状态拉回来。
  5. 执行中断处理函数:CPU恢复后,继续执行这个中断对应的处理函数。

我个人习惯在写驱动时,把enable_irq_wake放在suspend回调里,把disable_irq_wake放在resume回调里。这样逻辑清晰,也不容易漏掉。

4.4 实际项目中的坑

说到坑,我踩过的还真不少。挑两个典型的说说:

问题 现象 原因 解决
唤醒中断不配对 系统频繁被唤醒 enable/disable调用次数不匹配 检查wake_depth引用计数
中断号冲突 设备无法唤醒 多个设备共享同一个IRQ,但只有部分启用了唤醒 使用request_threaded_irq时指定IRQF_NO_SUSPEND
中断处理耗时过长 系统唤醒后卡顿 中断处理函数里做了太多事情 使用 threaded IRQ,把耗时操作放到线程里

我记得有一次,一个客户反馈说他们的平板在休眠后,每隔几秒就自动亮屏。我远程抓了wakeup source的日志,发现是触摸屏的IRQ一直在触发。后来一查,是触摸屏的固件在休眠后还在发中断,而驱动里又没做防抖处理。

你想想看,这种问题如果不了解中断唤醒的机制,光靠猜,得排查到什么时候?

4.5 调试技巧

最后分享几个调试命令,平时排查问题很有用:

# 查看所有中断的唤醒状态
cat /proc/interrupts | grep -i wake

# 查看当前注册的wakeup source
cat /sys/kernel/debug/wakeup_sources

# 查看某个IRQ的详细信息
cat /proc/irq/123/spurious

嗯,这些命令能帮你快速定位问题。比如你用 cat /proc/interrupts 看到某个IRQ的计数在休眠期间还在增长,那基本可以确定是这个中断导致的唤醒。

总结一下:IRQ是基础,Wakeup中断是IRQ的增强版。enable_irq_wake就是给IRQ贴上“允许唤醒”的标签。整个唤醒流程从外设触发,到中断控制器判断,再到CPU恢复执行,每一步都有对应的内核机制在支撑。

搞懂了这些,你再去看电源管理相关的驱动代码,会发现清晰很多。说白了,就是一层窗户纸,捅破了就通了。


公众号:蓝海资料掘金营,微信deep3321