近期项目生产过程中出现概率为万分之几异常,此中异常实在是折磨人啊!连续几天的熬夜复现分析,总算是找到异常原因;为加强自己对问题的认知特编辑此文对问题做进一步梳理。
1 问题现象
开机偶见TP失效;
2 问题分析
2.1 先后对测量TP各路信号均正常;
2.2 通过 cat /proc/interrupts 查看开机过程中TP中断有成功注册;
2.3 因为问题发生在开机过程所有按照开机时TP驱动的执行流程对代码进行再次评审,发现可能存在TP中断关闭与开启可能存在不匹配问题;
2.3.1 TP驱动代码异常时的执行流程如下:
在开机TP驱动注册的probe函数中首先会完成TP的中断处理函数注册,再进 入TP固件升级的逻辑判断。如下函数:
s32 MsDrvInterfaceTouchDeviceProbe(struct i2c_client, const structi2c_device_id )
{
.........
DrvPlatformLyrTouchDeviceRegisterFingerTouchInterruptHandler();
.........
DrvIcFwLyrCheckFirmwareUpdateBySwId();
.........
}
在进入固件升级处理逻辑时会进行判断是否需要关闭TP中断,如下代码当判断符合_gInterruptFlag == 1条件但没有执行到关闭中断并没有清除 _gInterruptFlag 标志时,TP有中断产生执行流程转入执行TP的中断处理程序。
void DrvPlatformLyrDisableFingerTouchReport(void)
{
if(_gInterruptFlag == 1)
{
disable_irq(_gIrq);
_gInterruptFlag = 0;
}
}
在进入到中断处理函数后会同样进行中断逻辑判断,即_gInterruptFlag 为1时 就关闭TP中断处理。此异常的执行流程正好满足_gInterruptFlag等于1,所以在 TP中断处理函数会执行关闭TP中断动作并设置_gInterruptFlag为0 ;但是当代码流程返回到TP固件升级的处理,接着之前判断成功_gInterruptFlag ==1之后关闭TP中 断函数继续执行。代码执行到此时TP中断被连续关闭两次。而此后代码的执行流程中不会存在多个进程同时访问同一全局变量数据,所以后续代码的执行流程中对于TP中断的开启和 关闭都是成对的执行,所以前面异常的连续两次的关闭中断只会有一次对应的中断开启处理。
2.3.2 kernel中开/关闭中断代码如下,在开和关中断的处理函数中会对会对开关中断次数进行匹配性计数,关中断时desc->depth 会被加1,而开中断时会对desc->depth进行减一操作。
假设设备代码中开关中断的执行流程如:连续两次的disable ,之后每执行一次enable都会对应的执行一次disable。那么问题就出现了由于之前执行了两次disable动作desc->depth值为2,之后执行enable函数时也之能走到default 流程即给desc->depth数值减一,不会真正的enable中断。
设备驱动中对于中断关和开一定要匹配,进行多少次关中断就需要对应的进行多少次开中断操作才能使能中断处理,否则可能出现中断关闭后不能开启造成设备无法被响应的问题。
void __disable_irq(struct irq_desc *desc)
{
if (!desc->depth++)
irq_disable(desc);
}
void __enable_irq(struct irq_desc *desc)
{
switch (desc->depth) {
case 0:
err_out:
WARN(1, KERN_WARNING "Unbalanced enable for IRQ %d
",
irq_desc_get_irq(desc));
break;
case 1: {
if (desc->istate & IRQS_SUSPENDED)
goto err_out;
/* Prevent probing on this irq: */
irq_settings_set_noprobe(desc);
irq_enable(desc);
check_irq_resend(desc);
/* fall-through */
}
default:
desc->depth--;
}
}
3 问题总结
对于多进程间共享的全局变量没有进行保护,多个进程间同时对该变量进行方式时期间变量数据发生变化造成代码执行 关中断 与 开中断 次数不匹配,虽然设备成功注册但是不能被正常响应。
4 问题修改
对于多进程间共享数进行锁保护,只有获得锁的进行才能访问该共享数据。
spin_lock_irqsave(&_gIrqLock, nIrqFlag);
if(_gInterruptFlag == 1)
{
disable_irq(_gIrq);
_gInterruptFlag = 0;
}
spin_unlock_irqrestore(&_gIrqLock,nIrqFlag);