最近在工作中使用irq时遇到如下问题,根据log显示应该是什么所谓的不平横问题,先前也没有仔细研究这个问题,只是定位到是enable_irq函数调用所致。
因为在项目中使用的中断是gpio中断,该中断在项目中的实现方式为多个gpio中断共享一个真实的物理中断,因此当这个真实的物理中断发生后由系统(就是另一个哥们写的irq驱动)查询到底是连接到这个物理中断上的哪一个具体的gpio产生的了中断(通过gpio的寄存器位)。这个过程可以认为是一次底层的中断回调过程。在这次回调过程中,这哥们的驱动会首先屏蔽掉(disable)这个实际的物理中断,然后开始查询共享这个物理中断的所有gpio,当找到后便会调用在这个虚拟的gpio中断上用户注册的中断回调函数,这个回调函数可以理解为上层回调,当这个回调函数完毕后便返回到底层回调函数中,然后重新enable那个唯一的实际物理中断。
至此针对这个gpio的中断回调处理过程全部完成。由此我们也可以发现,对于某个使用gpio中断的驱动而言,在进出中断回调函数过程中完全可以不去关心中断的屏蔽和打开,因为驱动的回调函数(相对认为是上层的)是由底层的那个物理中断的回调函数调用的,而这个底层的中断回调函数在进出的过程中关闭和打开这个物理中断。所以我在使用过程中没有在中断处理函数的开始调用disable_irq,也没有在中断退出时调用enable_irq相关函数。程序完全可以正确执行。
测试中一个偶然的错误让我发现了一个新的知识,一次调试一个电容屏的驱动时,由于移植中没有仔细检查,发现该驱动的probe调用后总是出现如下警告,但是并不影响使用。后来检查过程中发现,是因为在probe的最后调用了一次enable_irq所致。由于我很清楚上述gpio中断的实现原理,所以放心删掉那个enable_irq动作解决了问题。
------------[ cut here ]------------
WARNING: at kernel/irq/manage.c:225 __enable_irq+0x3b/0x57()
Unbalanced enable for IRQ 4
Modules linked in: svsknfdrvr [last unloaded: osal_linux]
Pid: 634, comm: ash Tainted: G W 2.6.28 #1
Call Trace:
[<c011a7f9>] warn_slowpath+0x76/0x8d
[<c012fac8>] profile_tick+0x2d/0x57
[<c011ed72>] irq_exit+0x32/0x34
[<c010f22c>] smp_apic_timer_interrupt+0x41/0x71
[<c01039ec>] apic_timer_interrupt+0x28/0x30
[<c011b2b4>] vprintk+0x1d3/0x300
[<c013a2af>] __setup_irq+0x11c/0x1f2
[<c013a177>] __enable_irq+0x3b/0x57
[<c013a506>] enable_irq+0x37/0x54
[<c68c9156>] svsknfdrvr_open+0x5e/0x65 [svsknfdrvr]
[<c016440a>] chrdev_open+0xce/0x1a4
[<c016433c>] chrdev_open+0x0/0x1a4
[<c01602f7>] __dentry_open+0xcc/0x23a
[<c016049a>] nameidata_to_filp+0x35/0x3f
[<c016b3c5>] do_filp_open+0x16f/0x6ef
[<c0278fd5>] tty_write+0x1a2/0x1c9
[<c0160128>] do_sys_open+0x42/0xcb
[<c0160201>] sys_open+0x23/0x2a
[<c0102e71>] sysenter_do_call+0x12/0x25
---[ end trace 4eaa2a86a8e2da22 ]---
最近针对这个问题我仔细研究了以下,又学到了新东西!
中断的enable和disable一定要成对使用,否则机会被kernel检测到unbalance而发出上述警告!
也就是调用一个enable之前一定曾经调用过至少一次disable!
可以想象在disable调用后会有一个计数器被加1,而在enable调用后这个计数器会减1.从而当计数器为0的时候若调用那个enable时就会导致上述警告发生。测试中我针对某个中断连续调用相关的disable函数数次,然后再开始调用enable函数,发现当调用到大于diable次数时上述警告便会出现。这说明我的想法是对的。
由于太相信这个gpio中断在使用中可以不去主动disable和enable。也导致我犯了一个严重错误,而且费了很大劲才解决。还是要多思考啊。。。
在设计这个电容屏的驱动时,我的设计为在其suspend函数中直接切断电容屏的电源以省电,然后在resume函数中再从新上电。中断触发方式为下降沿触发方式。测试发现,当在suspend中调用断电操作后总会导致一个I2C读取动作发生,而这个读取动作是我在中断处理函数的底半部中实现的动作。仅仅是电容屏的一个断电动作怎么会导致这个动作发生呢?
想了好久,我笑了。。。。
下降沿触发嘛~~ 当电容屏断电后当然没有谁去保持那个中断引脚为高电平了,它当然会恢复为默认的低电平了(该平台的中断默认为电平)。显然这个过程会导致一个下降沿发生,这便会引起中断处理函数被调用,从而引起底半部的工作队列被执行,然后I2C读取动作便发生了。可是这时候电容屏都断电了,读取当然要出错了!
好了,问题已经找到,现在的问题就是如何让这个下降沿不要发生,我靠这怎么可能?????
那就只能想办法让系统不要理会这个下降沿。咋办?
简单,断电前先disable中断,resume完成后重新enable这个中断就好了。。。哈哈
问题解决。
看来无论中断的底层怎么做,我们尽量不要图省事而减少一些必要的动作才对!
当然了,我们在自己的代码中屏掉某个gpio中断后,如果这个对应的gpio中断再次产生,底层的那个实际物理中断所注册的中断处理函数依然会运行,也依然会查询所有的gpio中断来判断到底是哪个gpio发出的中断,但是在找到这个具体的gpio后到底要不要执行这个gpio中断上注册的中断回调函数,这还要看这个gpio中断有没有被用户屏蔽掉,如果被用户屏蔽了,那么,这个底层的物理中断的回调函数就会直接返回而不会调用对应的gpio中断上注册的回调函数。基于这个原因,我在上面提到的思路才得以正确验证。
disable_irq_nosync(irq_num);
enable_irq(irq_num);
我的I2C驱动可能还有些问题,比如当用i2c_transfer函数进行传输时,如果mesgs参数包含多个消息组,而第一组消息发送时如果发生no slaver responed时,我应该放弃本次传输,并且释放总线,但是现在看来似乎没有释放总线,而且后续的传输也没有结束。这会引起一些错误,并引起延时。这个还需要进一步优化,或者找出真正的问题。