/*
*注意:本文仅用于学习记录和交流,若有错,望指正,谢谢
*/
--------------------------------------------------分割线------------------------------------------------------------
一、中断类型?中断源?中断控制器?中断大致处理流程?
中断的分类(注意与快中断区分,Linux中也没有用到快中断异常):外部中断、内部中断。
中断源决定了中断的类型,比如:按键等中断源是外部中断,定时器中断,系统调用(软中断)等为内部中断。
中断控制器:PIC(可编程中断控制器)在SOC内部集成,并挂载在APB总线上,使用HCLK时钟。
另:ARM多核处理器中常用的是GIC,支持三种类型的中断:
SGI:软件产生的中断,多用于核间通信。
PPI:某个CPU私有外设的中断,只能发给指定的CPU。
SPI:共享外设的中断,在后面源码中可以看到flag。
中断大致处理流程:
单片机怎么做的:中断发生(如按键中断)->CPU到中断向量地址去执行跳转->跳转到中断处理子程序->计算返回地址,保存现场,执行具体的处理,恢复现场->回到了被中断前的状态继续运行。
Linux怎么做的:其实和单片机大同小异,详情见文章:Linux异常处理框架。重点是异常处理框架到asm_do_IRQ就进入具体的中断处理了。那么怎么问题来了,Linux中一个中断发生了,比如调用了open函数(软中断),Linux是怎么知道的?怎么也要有PIC的初始化和中断源的相关配置吧,并且,asm_do_IRQ。。。最终执行的是我们自己的程序,他是怎么知道的?难道不需要去告知Linux一下吗?
二、asm_do_IRQ分析(带着以上问题)
注意这里asm_do_IRQ是带参数的C函数,具体的参数是:r0 = irq(中断源编号) r1 = 一个reg的地址(应该是sp的地址吧)
再往下看:irq.c
再往下看:irq.c
核心的generic_handle_irq:
到这里,中断处理的框架似乎就分析完了,但仍然不能解决我的困惑,一个新问题:desc->handle_irq是什么?它似乎指向了我们BSP定义的处理函数。
三、Linux的IRQ初始化
在Linux中有对IRQ的初始化,完成desc项的填充,下面来看看:
在start_kernel(main.c):
先看early_irq_init():注意这里是有分支选择
以上主要就是定义desc数组和填充 一些提前能填充的项吧,各种info。
下面看重点的init_IRQ():
在老一点的内核这个函数直接就是做主体工作了,然而在Linux-3.4.2中又封装了一层。。。。找了很久才在单板相关的的mach-xxx.c中找到,如mach-mini2440.c:
至于MACHINE_START的执行点在:start_kernel->setup_arch->执行。。。
那么重点就在s3c24xx_init_irq函数了:在irq.c
/* s3c24xx_init_irq
* Initialise S3C2410 IRQ system
*/
void __init s3c24xx_init_irq(void)
{
unsigned long pend;
unsigned long last;
int irqno;
int i;
#ifdef CONFIG_FIQ
init_FIQ();
#endif
irqdbf("s3c2410_init_irq: clearing interrupt status flags\n");
/* first, clear all interrupts pending... */
last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C24XX_EINTPEND);
if (pend == 0 || pend == last)
break;
__raw_writel(pend, S3C24XX_EINTPEND);
printk("irq: clearing pending ext status %08x\n", (int)pend);
last = pend;
}
last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C2410_INTPND);
if (pend == 0 || pend == last)
break;
__raw_writel(pend, S3C2410_SRCPND);
__raw_writel(pend, S3C2410_INTPND);
printk("irq: clearing pending status %08x\n", (int)pend);
last = pend;
}
last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C2410_SUBSRCPND);
if (pend == 0 || pend == last)
break;
printk("irq: clearing subpending status %08x\n", (int)pend);
__raw_writel(pend, S3C2410_SUBSRCPND);
last = pend;
}
/* register the main interrupts */
irqdbf("s3c2410_init_irq: registering s3c2410 interrupt handlers\n");
for (irqno = IRQ_EINT4t7; irqno <= IRQ_ADCPARENT; irqno++) {
/* set all the s3c2410 internal irqs */
switch (irqno) {
/* deal with the special IRQs (cascaded) */
case IRQ_EINT4t7:
case IRQ_EINT8t23:
case IRQ_UART0:
case IRQ_UART1:
case IRQ_UART2:
case IRQ_ADCPARENT:
irq_set_chip_and_handler(irqno, &s3c_irq_level_chip,
handle_level_irq);
break;
case IRQ_RESERVED6:
case IRQ_RESERVED24:
/* no IRQ here */
break;
default:
//irqdbf("registering irq %d (s3c irq)\n", irqno);
irq_set_chip_and_handler(irqno, &s3c_irq_chip,
handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
}
/* setup the cascade irq handlers */
irq_set_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
irq_set_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
irq_set_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
irq_set_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
irq_set_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
irq_set_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);
/* external interrupts */以外部中断为例分析
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
irqdbf("registering irq %d (ext int)\n", irqno);
irq_set_chip_and_handler(irqno, &s3c_irq_eint0t4,//s3c_irq_eint0这是一个初始化的结构体
handle_edge_irq);//handle_edge_irq是一个函数,和重要的
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
irqdbf("registering irq %d (extended s3c irq)\n", irqno);
irq_set_chip_and_handler(irqno, &s3c_irqext_chip,
handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
/* register the uart interrupts */
irqdbf("s3c2410: registering external interrupts\n");
for (irqno = IRQ_S3CUART_RX0; irqno <= IRQ_S3CUART_ERR0; irqno++) {
irqdbf("registering irq %d (s3c uart0 irq)\n", irqno);
irq_set_chip_and_handler(irqno, &s3c_irq_uart0,
handle_level_irq);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++) {
irqdbf("registering irq %d (s3c uart1 irq)\n", irqno);
irq_set_chip_and_handler(irqno, &s3c_irq_uart1,
handle_level_irq);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_S3CUART_RX2; irqno <= IRQ_S3CUART_ERR2; irqno++) {
irqdbf("registering irq %d (s3c uart2 irq)\n", irqno);
irq_set_chip_and_handler(irqno, &s3c_irq_uart2,
handle_level_irq);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_TC; irqno <= IRQ_ADC; irqno++) {
irqdbf("registering irq %d (s3c adc irq)\n", irqno);
irq_set_chip_and_handler(irqno, &s3c_irq_adc, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
irqdbf("s3c2410: registered interrupt handlers\n");
}
重点分析:
irq_set_chip_and_handler(irqno, &s3c_irq_eint0t4,//s3c_irq_eint0这是一个初始化的结构体
handle_edge_irq);//handle_edge_irq是一个函数,和重要的
先看一下s3c_irq_eint0:
这些函数的作用实际就是中断控制器的初始化
那么handle_edge_irq呢?截取
再看handle_irq_event:
现在似乎明朗了不少,中断控制器的初始化(底层函数)在irq_chip结构体中定义;而自定义的处理函数以desc为索引,在action链表中。
所以需要一种注册机制,将这些结构体联系起来。
四、相关结构体
第一,struct irq_desc
第二,struct irq_chip
第三,action结构体链表
五、注册和注销
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev);
void free_irq(unsigned int irq, void *dev_id)
六、Linux中断的顶半部和低半部机制(重点)
理论上,中断做的是紧急的事,但是不能长时间占用,因为CPU不是为你一家开的,还有很多任务等着调度呢!
那么Linux中断就引入了顶半部和低半部机制:
在使用request_irq注册的handler就是顶半部函数,会屏蔽中断,做一些特别紧急,且开销很小的事,如设置flag,读写IO寄存器等;在 request_irq里面调度一个底半部的函数去做几乎中断需要做的所有事,且中断是打开的,可以让顶半部继续接收中断,处理更多的中断事件,或者调度其他的任务。
在低半部机制机制中主要有:tasklet、工作队列、软中断和线程。应用都比较简单。