一次触摸屏中断调试引发的深入探究
大家好,我叫张昺华,中间那个字念“饼”,首先非常感谢陈莉君老师的指点,题目名字也是陈老师起的,也很荣幸此文章能在蜗窝上发表一次,感谢郭大侠给的机会
如下为本人原创,在解决问题的过程中的一点心得,如果有描述不准确的地方还请各位指出,非常感谢
Linux内核版本:linux-4.9.18
曾有一次调试触摸屏的时候遇到如下的问题
/startup/modules
[ 233.370296] irq 44: nobody cared (try booting with the “irqpoll” option)
[ 233.376983] CPU: 0 PID: 0 Comm: swapper Tainted: G O 4.9.18 #8
[ 233.383912] Hardware name: Broadcom Cygnus SoC
[
233.388378] [
[
233.396103] [
[ 233.403821]
[
[ 233.412052]
[
[ 233.420715]
[
[ 233.429550]
[
[ 233.437868]
[
[ 233.446366]
[
[ 233.455376]
[
[ 233.464297]
[
[ 233.472959]
[
[
233.481275] [
[ 233.488723] Exception stack(0xc0901f60 to 0xc0901fa8)
[ 233.493754] 1f60: c0112900 c0717028 c0901fb8 00000000 c093af4c 00000000 00000335 c0826220
[ 233.501896] 1f80: 00000001 414fc091 df9eab80 00000000 c0900038 c0901fb0 c010843c c0108440
[ 233.510034] 1fa0: 60000013 ffffffff
[
233.513514] [
[
233.520887] [
[
233.528956] [
[ 233.537097] handlers:
[ 233.539363]
[
[ 233.549300] Disabling IRQ #44
首先我们顺着错误跟踪linux内核来看下
kernel/irq/spurious.c
因此有提示的log信息可以看出,是走的else的分支,bad_action_ret(action_ret)返回为0
通过此函数的dump_stack的信息,可以追溯到调用者
drivers/pinctrl/bcm/pinctrl-iproc-gpio.c
kernel/irq/chip.c
handle_level_irq
===> handle_irq_event(kernel/irq/handle.c)
===> handle_irq_event_percpu(kernel/irq/handle.c)
===>__handle_irq_event_percpu(kernel/irq/handle.c)
根据log,我们可以在下图看到note_interrupt,即说明noirqdebug=0
Kernel/irq/handle.c
因为上面我们已经分析过bad_action_ret(action_ret)返回为0
因此在note_interrupt函数里面只会从如下分支进去
Kernel/irq/spurious.c
从上图可以看出,如果想出现那样的错误,必须满足条件
desc->irqs_unhandled > 99900为真
如要要满足如上条件的话,那么只有如下地方会让irqs_unhandled++
Kernel/irq/spurious.c
通过上图,我们可以看到,必须满足条件:
action_ret == IRQ_NONE为真
再继续看回如下图,action_ret就是retval
res即为action_ret
而 action->handler****的回调函数是:
request_threaded_irq线程化注册中断的第2个参数
kernel/irq/manage.c
因为handler为NULL,所以handler = irq_default_primary_handler
即action_ret = IRQ_WAKE_THREAD
Kernel/irq/spurious.c
经过如上图,我们可以发现action_ret = IRQ_NONE
那么我们接下来看看到底是怎么被调用到这里的,一个中断的产生又是怎样的?
首先handle_level_irq这个函数是在这里注册到kernel中的
drivers/pinctrl/bcm/pinctrl-iproc-gpio.c
static int iproc_gpio_probe(struct platform_device *pdev)
===> gpiochip_irqchip_add
Include/linux/gpio/driver.h
typedefvoid (*irq_flow_handler_t)(struct irq_desc *desc);
这里即gpiochip->irq_handler = handle_level_irq
struct irqaction *action;
一个中断开始的时候
arch/arm/kernel/entry-armv.S
这里有一个全局的handle_arch_irq
这个全局的handle_arch_irq会在如下地方被赋值
arch/arm/kernel/setup.c
void __init setup_arch(char **cmdline_p)
===> handle_arch_irq被赋值
那么接下来我们就要找到mdesc->handle_irq又是在哪里被赋值了呢?
drivers/irqchip/irq-gic.c
这里有这样的函数set_handle_irq
接下来我们看下这个函数的实现就知道了
arch/arm/kernel/irq.c
那么这个set_handle_irq又是在哪里被调用的呢?
针对内核版本Linux-4.9.18
drivers/irqchip/irq-gic.c
gic_of_init
===>__gic_init_bases
===>set_handle_irq
Include/linux/irqchip.h
Include/linux/of.h
Include/linux/of.h
因此我们得出一个结论:
handle_arch_irq = gic_handle_irq
一个中断开始后,从entry-armv.S****中进入
handle_domain_irq
===> __handle_domain_irq
===>generic_handle_irq
===>generic_handle_irq_desc
这里的desc->handle_irq 其实就是handle_level_irq
这里是如何转换过去的呢?
drivers/pinctrl/bcm/pinctrl-iproc-gpio.c
gpiochip_set_chained_irqchip
===> irq_set_chained_handler_and_data
===> __irq_do_set_handler
Kernel/irq/chip.c
回归到最初的问题,之前我们分析出如下的结论:
如果想出现log那样的错误,必须满足条件
desc->irqs_unhandled > 99900为真
如要要满足如上条件的话,那么只有让irqs_unhandled++
那么满足这个条件就必须action_ret == IRQ_NONE
#define SPURIOUS_DEFERRED0x80000000
如下图:
也就是必须要满足handled != desc->threads_handled_last为假
这里handled = threads_handled
而desc->threads_handled_last会在如下位置设置为SPURIOUS_DEFERRED
再看下图
Kernel/irq/manage.c
Irq_thread
这里会一直将threads_handled++ ,这里handled = threads_handled
直到满足handled != desc->threads_handled_last为假
那么为什么这个threads_handled会一直++呢?
因为这里:
上图是正确的修改,如果gpiochip_irqchip_add的第四个参数是handle_simple_irq的话,
那么就会出现threads_handled会一直++的情况,从而产生本文最开头的错误
[ 233.370296] irq 44: nobody cared (try booting with the “irqpoll” option)
…
[ 233.549300] Disabling IRQ #44
这里我们就要对handle_simple_irq 与handle_level_irq做个分析了,具体的分析大家可以网上看蜗窝的资料以及csdn上很多对这块有详细的描述,我这里简单叙述下我个人的理解
首先上代码:
大家可以看出来,handle_simple_irq做的事情很简单,而handle_level_irq却做了这个动作
mask_ack_irq(desc); 因为是电平中断,如果不做mask中断的动作的话,会因为中断电平一直
是有效电平导致中断控制器会源源不断地给cpu发中断
而handle_simple_irq就是非常简单的处理中断,没有mask中断,原本代码是写的handle_simple_irq,
而触摸屏的中断是设置为线程化的,并且为电平触发方式,那么如果没有mask该中断,
那么当一次线程化中断处理函数还未执行完成的时候,又会有源源不断地中断一直进来,
那么就会出现threads_handled会一直++的情况,从而产生本文最开头的错误
到此这个问题就已经分析完了
如下只是个小记录:
这个函数的作用是检查是否有中断嵌套
【作者】张昺华
【博客园】 http://www.cnblogs.com/sky-heaven/
【新浪博客】 http://blog.sina.com.cn/u/2049150530
【知乎】 http://www.zhihu.com/people/zhang-bing-hua
【我的作品—旋转倒立摆】 http://v.youku.com/v_show/id_XODM5NDAzNjQw.html?spm=a2hzp.8253869.0.0&from=y1.7-2
【我的作品—自平衡自动循迹车】 http://v.youku.com/v_show/id_XODM5MzYyNTIw.html?spm=a2hzp.8253869.0.0&from=y1.7-2
【新浪微博】 张昺华–sky
参考:
/irq_subsystem/request_threaded_irq.html
/irq_subsystem/interrupt_descriptor.html
https://blog.csdn.net/tiantao2012/article/details/78062621
https://blog.csdn.net/tiantao2012/article/details/78094691
https://blog.csdn.net/zhao2272062978/article/details/70599978
https://blog.csdn.net/droidphone/article/details/7467436
https://blog.csdn.net/droidphone/article/details/7445825
https://blog.csdn.net/droidphone/article/category/1118447
https://blog.csdn.net/phenix_lord/article/details/45116259
继续讨论
新讨论请用 GitHub 账号在下方发言——评论会保存在本仓库的 Discussions 里,与站点同寿; 再下方的「历史评论」是原站的只读存档。
评论(12 条,只读存档)
以下为原站的历史评论(只读存档);继续讨论请用上方的评论区。

smcdef
heaven
heaven
smcdef
heaven
sharetech
mumu
慢慢想
cheng
cheng
cheng
cheng