蜗窝科技

一次触摸屏中断调试引发的深入探究

heaven · · Linux内核分析 · 阅读 19126 · 2018年4月存档

大家好,我叫张昺华,中间那个字念“饼”,首先非常感谢陈莉君老师的指点,题目名字也是陈老师起的,也很荣幸此文章能在蜗窝上发表一次,感谢郭大侠给的机会

如下为本人原创,在解决问题的过程中的一点心得,如果有描述不准确的地方还请各位指出,非常感谢

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] [] (unwind_backtrace) from [] (show_stack+0x10/0x14)

[ 233.396103] [] (show_stack) from [] (__report_bad_irq+0x24/0xa4)

[ 233.403821]

[] (__report_bad_irq) from [] (note_interrupt+0x1c8/0x274)

[ 233.412052]

[] (note_interrupt) from [] (handle_irq_event_percpu+0x44/0x50)

[ 233.420715]

[] (handle_irq_event_percpu) from [] (handle_irq_event+0x28/0x3c)

[ 233.429550]

[] (handle_irq_event) from [] (handle_simple_irq+0x70/0x78)

[ 233.437868]

[] (handle_simple_irq) from [] (generic_handle_irq+0x18/0x28)

[ 233.446366]

[] (generic_handle_irq) from [] (iproc_gpio_irq_handler+0xd0/0x11c)

[ 233.455376]

[] (iproc_gpio_irq_handler) from [] (generic_handle_irq+0x18/0x28)

[ 233.464297]

[] (generic_handle_irq) from [] (__handle_domain_irq+0x80/0xa4)

[ 233.472959]

[] (__handle_domain_irq) from [] (gic_handle_irq+0x50/0x84)

[ 233.481275] [] (gic_handle_irq) from [] (__irq_svc+0x6c/0x90)

[ 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] [] (__irq_svc) from [] (arch_cpu_idle+0x2c/0x38)

[ 233.520887] [] (arch_cpu_idle) from [] (cpu_startup_entry+0x50/0xc0)

[ 233.528956] [] (cpu_startup_entry) from [] (start_kernel+0x414/0x4b0)

[ 233.537097] handlers:

[ 233.539363]

[] irq_default_primary_handler threaded [] synaptics_rmi4_irq [synaptics_dsx]

[ 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

https://blog.csdn.net/phenix_lord/article/details/45116595

https://blog.csdn.net/phenix_lord/article/details/45116689

标签:中断

继续讨论

新讨论请用 GitHub 账号在下方发言——评论会保存在本仓库的 Discussions 里,与站点同寿; 再下方的「历史评论」是原站的只读存档。

评论(12 条,只读存档)

以下为原站的历史评论(只读存档);继续讨论请用上方的评论区。

  • smcdef

    其实我觉得这么修改并不是很好。把gpiochip_irqchip_add(gc, &iproc_gpio_irq_chip, 0,handle_simple_irq, IRQ_TYPE_NONE)中的handle_simple_irq改成handle_level_irq感觉并不是根本原因。我去看了你说的drivers/pinctrl/bcm/pinctrl-iproc-gpio.c这个驱动。如果按照你说的方法修改的话,如果某个gpio设置是边沿触发的话,那么处理函数也是handle_level_irq。处理边沿中断当时handle_edge_irq。因此,我建议的修改的方法是修改pinctrl-iproc-gpio.c文件的iproc_gpio_irq_set_type函数。在这个函数中根据中断类型,修改中断处理函数是handle_level_irq还是handle_edge_irq或许才更加合理。修改添加类似下面的代码。 if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) irq_set_handler_locked(d, handle_level_irq); else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) irq_set_handler_locked(d, handle_edge_irq);
    • heaven

      @smcdef:这么一说确实,你这样的修改比较合理,否则像你说的一样,我那种修改方式仅仅只是针对电平触发处理,所以是应该根据中断类型,来去选择是handle_edge_irq还是handle_level_irq,非常感谢指出
    • heaven

      @smcdef:我今天再仔细看了下代码,流程应该是这样: 中断控制器怎么判断哪个是哪个handle_level_irq? 中断控制器流程: gic_handle_irq ===>handle_domain_irq ===>__handle_domain_irq ===>irq_find_mapping ===>generic_handle_irq irq_find_mapping这个函数来找之前irq_create_mapping的irq映射 找到返回中断号后,再去执行handle_level_irq request_threaded_irq ===>__setup_irq ==>__irq_set_trigger ===>irq_set_type 所以可以这样改,驱动注册的时候可以通过这个函数来设置自己的中断控制器需要走哪种流控处理 内核也有其他驱动有类似的改法 drivers/gpio/gpio-aspeed.c static int aspeed_gpio_set_type(struct irq_data *d, unsigned int type) { u32 type0 = 0; u32 type1 = 0; u32 type2 = 0; u32 bit, reg; const struct aspeed_gpio_bank *bank; irq_flow_handler_t handler; struct aspeed_gpio *gpio; unsigned long flags; void __iomem *addr; int rc; rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit); if (rc) return -EINVAL; switch (type & IRQ_TYPE_SENSE_MASK) { case IRQ_TYPE_EDGE_BOTH: type2 |= bit; case IRQ_TYPE_EDGE_RISING: type0 |= bit; case IRQ_TYPE_EDGE_FALLING: handler = handle_edge_irq; break; case IRQ_TYPE_LEVEL_HIGH: type0 |= bit; case IRQ_TYPE_LEVEL_LOW: type1 |= bit; handler = handle_level_irq; break; default: return -EINVAL; } spin_lock_irqsave(&gpio->lock, flags); addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE0); reg = ioread32(addr); reg = (reg & ~bit) | type0; iowrite32(reg, addr); addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE1); reg = ioread32(addr); reg = (reg & ~bit) | type1; iowrite32(reg, addr); addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE2); reg = ioread32(addr); reg = (reg & ~bit) | type2; iowrite32(reg, addr); spin_unlock_irqrestore(&gpio->lock, flags); irq_set_handler_locked(d, handler); return 0; }
      • smcdef

        @heaven:你可以详细看一下我发内容,我说修改的函数iproc_gpio_irq_set_type,实际上就是你整理的irq_set_type函数最后需要调用的接口函数。你所表述的修改方法其实就是我一开始说的。
        • heaven

          @smcdef:是的,我有仔细看的,我就是看了你的说的才去又细看了下你说的那块代码,我就是写出来流程而已,也方便自己下次回看,非常感谢你的改进方法
  • sharetech

    是的,赞同smcdef的观点
  • mumu

    这几天在RK 平台上也遇到这个bad_IRQ_report 然后dump_stack. 咨询一下怎么中断号 查询中断寄存器的值
  • 慢慢想

    @heaven 申请触摸屏中断的时候加上IRQF_ONESHOT是否可以解决问题?
    • cheng

      @慢慢想:可以解决
  • cheng

    你好: desc->handle_irq应该是iproc_gpio_irq_handler吧?我是不明白怎么会是handle_level_irq。
    • cheng

      @cheng:真正让desc->handle_irq=handle_level_irq的路径应该是: gpiochip_irq_map irq_set_chip_and_handler irq_set_chip_and_handler_name __irq_set_handler __irq_do_set_handler
  • cheng

    根据软中断号—>硬件终端号,在查询datasheet。