4. Wakeup源中断处理:IRQ与Wakeup中断的关系
好,咱们今天聊聊中断。准确地说,是聊聊那些能把系统从睡梦中叫醒的中断。
我记得刚接触Android电源管理那会儿,有个问题困扰了我很久:同样是中断,为什么有的能唤醒系统,有的就不行?后来我才明白,这背后其实是IRQ和Wakeup中断的微妙关系。
4.1 IRQ与Wakeup中断:到底有啥区别?
先说IRQ。IRQ是硬件中断请求,是外设跟CPU打招呼的方式。比如你按一下电源键,按键控制器就会触发一个IRQ,告诉CPU“嘿,有人按我了”。
但Wakeup中断呢?它其实是IRQ的一种特殊形态。说白了,就是那些被标记为“可以在系统休眠时把我叫醒”的中断。
我打个比方:
- IRQ 就像你家的门铃。你在家的时候,门铃响了你就去开门。
- Wakeup中断 就像你睡觉时,门铃还能把你吵醒。如果门铃没接电源,你睡得再死也听不见。
在Linux内核里,每个IRQ都可以通过一个开关,决定它是否具备唤醒能力。这个开关,就是我们要讲的 enable_irq_wake 和 disable_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 中断唤醒流程分析
好,现在咱们把整个流程串起来。系统从休眠到被唤醒,到底经历了什么?
我画了一张图,帮你理清思路:
流程其实不复杂,我拆解一下:
- 外设触发中断:比如你按了电源键,按键控制器拉高了一个GPIO电平。
- 中断控制器接收:GIC(Generic Interrupt Controller)收到信号,检查这个中断的配置。
- 判断是否为唤醒中断:内核检查这个IRQ的
wake_depth是否大于0。如果是,就走唤醒路径;否则,直接屏蔽。 - 唤醒CPU:如果被标记为唤醒中断,GIC会向CPU发送一个唤醒信号,把CPU从休眠状态拉回来。
- 执行中断处理函数: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