前段时间调试一款芯片的时候,碰到一个奇怪的问题:只要在板卡上插入一个PS2键盘,启动内核时系统就可能会进入串口中断函数去执行,过一会系统就panic不往下继续执行。后来经过分析出现问题时的panic的堆栈,借助EJTAG工具,读到这个时候的串口的中断状态位,竟然发现串口并没有真正产生中断。那么,串口本身没有中断,内核怎么又会跑到串口的中断服务函数去执行呢?
我们知道Linux的中断可以分为I/O 中断 、时钟中断和处理器核间中断。其中I/O中断是Linux 系统响应外部IO事件的重要方式。尽管不同的平台和体系结构实现的方法不一样,但是都支持不同的设备共享同一个中断向量 。例如在PCI的总线结构中,几个设备可以共享同一个IRQ线,也即它们共享一个中断号,各自的中断服务例程挂在这个中断号上。当CPU响应这个IRQ时,会逐一检查挂在这个中断向量上的例程,并且执行那些真正有中断的例程。但是串口明显不属于PCI,不可能用到PCI的中断线和中断引脚寄存器,那又是什么原因导致kernel panic呢?
带着上面的这些问题,和硬件工程师和芯片设计的同事深入讨论了各种可能性,最后结合芯片板卡原理图,发现这个板子上的低速设备都集成在芯片内部,这些低速设备包括串口、LPC只是控制器、SPI控制、I2C控制器、NAND控制器等,其中CPU UART和LPC的中断信号都路由到同一个CPU的中断引脚。 具体的连接图如下所显:
通过上图最左侧的部分可以看到,CPU UART和CPU LPC控制器的中断请求信号经过逻辑或后,输出到了CPU 芯片内Interrupt Pending Bit2。一旦CPU检测到任何一个Interrupt Pending位被设置起来,它就会根据约定的顺序逐一查询各个Interrupt Pending Bit,并且执行中断路由到这个bit上的设备驱动的中断服务例程。
在老版本的板卡设计中,CPU LPC上没有接任何设备,当时没有碰到前面提到的问题。后来,由于客户的需求,板卡上CPU LPC端口上可以连接PS2 键盘和鼠标,问题也随之暴露。因此,我猜测问题的根源是由于没有对共享IP2的连接到LPC上的设备的中断进行处理。按照这一思路,检查了之前的中断分发处理函数,发现果然在处理IP2时,只调用了do_irq(58)。参考内核中串口驱动的代码可以知道,第一个CPU UART的中断号默认是58。因此在这种情况下一旦接入的PS2键盘或鼠标发出中断信号,现有的代码就会不分青红皂白地去执行do_irq(58)。显然,这不是期望的行为,需要查询各自的中断状态位来检测到底该响应那个设备的中断。在最新的Linux 4.2内核主干分支里,我们仍然可以找到这部分有待完善的代码:arch/mips/loongson64/loongson-3/irq.c:
void mach_irq_dispatch(unsigned int pending)
{
if (pending & CAUSEF_IP7)
do_IRQ(LOONGSON_TIMER_IRQ);
#if defined(CONFIG_SMP)
else if (pending & CAUSEF_IP6)
loongson3_ipi_interrupt(NULL);
#endif
else if (pending & CAUSEF_IP3)
ht_irqdispatch();
else if (pending & CAUSEF_IP2)
do_IRQ(LOONGSON_UART_IRQ);
else {
pr_err("%s : spurious interrupt\n", __func__);
spurious_interrupt();
}
}
另外,该芯片中LPC上的中断信号是电平触发,根据LPC配置寄存器的定义,当中断完成之后,需要清除LPC SIRQ,这就需要添加响应的中断ack和eoi等函数。与此相反的是,兼容NS16550A的串口上的中断是边沿触发,不需要程序去清除:当传输保存寄存器为空时,串口的ISR的bit 1会被设置起来,一旦写数据到传输保存寄存器,这个bit就会被清掉;当接收FIFO中字符的个数达到trigger的水平时,ISR的bit 2会被设置起来,直到程序读接收FIFO。
根据上面的两点思路,修改了响应的代码,重新编译内核,重新做了大量测试,没有再复现前文提到的问题。通过这个例子,我们可以看到:除了PCI中断这种共享中断向量的机制之外,还有不同物理设备的中断信号路由到同一个中断引脚但使用不同中断号的情况。针对这种情况。工程师需要综合芯片结构、硬件连接、中断路由及内核中断处理流程,全面考虑各种情况,给出完善、健壮的解决方案。但比较这两者的异同之后,不难发现其实本质还是一样的:不管中断号是否一样,任何路由到CPU内部中断或者外部中断控制器上的设备,都应享用被服务的机会,不能遗漏。