说明:此文以linux2.6.22.6内核为平台分析
//内核中断向量表如下:/arch/arm/kernel/entry-armv.S
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start //???
.globl __vectors_start
__vectors_start:
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pabt + stubs_offset
b vector_dabt + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset
b vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
//这个向量表定义了ARM处理器支持的7中异常发生时的中断向量,就是一些跳转指令,发生异常PC指针立即指向相应异常的向量地址,CPU执行跳转指令,跳到中断的处理函数。对于上面的向量还可以看到跳转时加了一个stubs_offset这是为什么?先往下看。
下面是内核中定义的针对各种异常的处理也就是上面的向量要跳到的地址 也在 /arch/arm/kernel/entry-armv.S中
.globl __stubs_start
__stubs_start:
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32) //用户模式下irq的处理用户模式下的IRQ中断处理
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)//管理模式下的IRQ中断处理
.long __irq_invalid @ 4
... ...
.long __irq_invalid @ e
.long __irq_invalid @ f
... ...
.globl __stubs_end
__stubs_end:
其中:vector_stub是一个宏在 /arch/arm/kernel/entry-armv.S中.macro vector_stub, name, mode, correction=0
我们的参数:vector_stub irq, IRQ_MODE, 4将其展开就是下面的
vector_irq:
.if 4
lr, lr, #4 //计算返回地址
.endif
@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr //将r0 lr保存到sp指向的堆栈
mrs lr, spsr //读取spsr到lr中
str lr, [sp, #8] @ save spsr //也将这个值保存到堆栈中
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(IRQ_MODE ^ SVC_MODE)
msr spsr_cxsf, r0 //为进入管理模式做准备
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f //@进入中断前的mode的后4位
mov r0, sp
ldr lr, [pc, lr, lsl #2] //如果进入中断前是usr,则取出PC+4*0的内容,即__irq_usr
//如果进入中断前是svc,则取出PC+4*3的内容,即__irq_svc
movs pc, lr //当指令的目标寄存器是PC,且指令以S结束,
//它会把 spsr的值恢复给cpsr 这句执行完毕会返回执行跳转
.endm
//上面的处理又是一个函数指针,不同模式下发生的异常要进入不同的处理函数处理,由于ARM用4个二进制位表示处理器的模式,所以理论上有16钟即0~f,但是目前ARM只有7钟工作模式,余下的没用到的模式及7钟模式中不支持的某种异常都用“__irq_invalid”处理错误,不同的跳转分支,只在入口处有差别,例如保存中断发生时的寄存器,但是后续的处理是一样的。
//内核中断向量处理函数如下: /arch/arm/kernel/traps.S
void __init trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE;
... ...
21. memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
22. memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
... ...
}
1. CONFIG_VECTORS_BASE是向量表的基地址,在ARM V4及以后的处理器中向量表可以有两个位置:一个是0一个是0xffff0000 .这可以通过CP15协处理器中C1寄存器V位选择对应关系是:
V = 0 -> 0x00000000 ~ 0x0000001c
V = 0 -> 0xffff0000 ~ 0xffff001c
CONFIG_VECTORS_BASE的值一般在配置文件 /arch/arm/configs/s3c2410_defconfig中定义,在这个文件22行可以找到如下代码:
CONFIG_VECTORS_BASE=0xffff0000,现在我们知道了rap_init函数第21行就是把中断向量表拷贝到定义的向量表基地址,0xffff0000。第22行中__stubs_start, __stubs_end上文已经说过,这里也是搬移到0xffff0000+200处。
到这里可以解释一下上面提到的疑问,就是为什么中断向量表那跳转时要加一个stubs_offset = __vectors_start + 0x200 - __stubs_start这个值又是怎么得到的?原因如下:
经过trap_init函数处理后中断向量表和向量表指向的具体处理的代码已经搬移到其他的地方,其中__vectors_start搬移到0xffff0000,__stubs_start搬移到0xffff0000+200,
这两个搬移对程序的运行影响最大的就是__stubs_start的搬移,因为虽然中断向量表也搬移了,但是不论搬到哪只要声明了向量表地址,当发生异常时PC指针会自动指向相应异常的向量地址,这是硬件自动完成的,但是具体的跳转是由软件设置的见下行:
b vector_irq + stubs_offset即跳到哪是由程序员指定的。所以处理异常的代码经搬移后,如果不加处理还是跳到原来的地方如b vector_irq肯定是不行的,那么该如何特殊处理一下呢?
以irq为例说明:有必要了解一下跳转指令b的执行机理:当汇编器看到B指令后会把要跳转的标签转化为相对于当前PC的偏移量(±32M)写入指令码。
当发生IRQ中断时,PC指针自动指向IRQ中断的向量地址在此记作IRQ_PC,它在中断向量表的偏移就是:
IRQ_PC - __vectors_start (这个__vectors_start搬移后的地址是确定的0xffff0000),vector_irq在stubs_start中的偏移就是:
vector_irq - __stubs_start,
这两个偏移是恒定的,不会因为代码搬移改变,搬移后__vectors_start在0xffff0000,__stubs_start在0xffff0000+0x200。
现在可以得到 vector_irq相对于中断向量表起始地址的偏移 vector_irq - __stubs_start+0x200就是图中的h1
, 再减去IRQ_PC距中断向量表的偏移h2,就得到了相对于当前指针的偏移,如下
vector_irq - __stubs_start+0x200 - (IRQ_PC - __vectors_start)
= (vector_irq + __vectors_start + 0x200 - __stubs_start) - IRQ_PC
可见括号里面的值也就是B指令跳转的地址可以看出确实是相对于当前的PC指针的,
而__vectors_start + 0x200 - __stubs_start也正好和内核中定义的stubs_offset一样。*/
回过头继续内核对中断的处理流程,还是以用户模式下进入IRQ_中断为例:
1)发生中断PC指针指向IRQ中断向量,执行b vector_irq + stubs_offset 跳到此处预处理
2)在vector_irq里,根据进入中断的cpu模式进入不同函数处理,(以用户模式为例)
就进入 __irq_usr
__irq_usr:
usr_entry
... ...
irq_handler
... ...
//irq_handler 是一个宏如下
.macro irq_handler
get_irqnr_preamble r5, lr
1: get_irqnr_and_base r0, r6, r5, lr //调用获取中断号函数
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, 1b
bne asm_do_IRQ //将获取的中断号和一个结构体作为参数调用asm_do_IRQ
//cpu对硬件发生中断请求的中断号判断过程:
//get_irqnr_and_base是一个宏在include/asm/arch-s3c2410/entry-macro.s
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
// 分别对应 r0, r6, r5, lr
// 功能 存放 中断号 中断状态 基址 临时的判断
//现将源代码展开替换成实参
mov r5, #S3C24XX_VA_IRQ //获取中断寄存器基地址(虚拟地址)
@@ try the interrupt offset register, since it is there
ldr r6, [ r5, #INTPND ] //获取INTPND值存入R6
teq r6, #0 //测试是否为0
beq 1002f //为0没发生中断跳到1002f
ldr r0, [ r5, #INTOFFSET ]//否则把INTOFFSET的值放入r0就得到具体的中断号
... ...
1001:
adds r0, r0, #IRQ_EINT0//@加上中断号的基准数值16,得到最终的中断号
1002:
@@ exit here, Z flag unset if IRQ
//关于中断号 /include/asm-arm/arch-s3c2410/irqs.h
#define S3C2410_CPUIRQ_OFFSET (16)
//为什么要从十六开始因为前15个留给软中断用,也解释了上文在获取中断号时也加上个基准数16,可见完全一致
#define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET)
#define IRQ_EINT0 S3C2410_IRQ(0) /* 16 */
#define IRQ_EINT1 S3C2410_IRQ(1)
#define IRQ_EINT2 S3C2410_IRQ(2)
#define IRQ_EINT3 S3C2410_IRQ(3)
#define IRQ_EINT4t7 S3C2410_IRQ(4) /* 20 */
#define IRQ_EINT8t23 S3C2410_IRQ(5)
... ...
#define IRQ_ADCPARENT S3C2410_IRQ(31)共有32个
//asm_do_IRQ /arch/arm/kernelirq.c
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct irq_desc *desc = irq_desc + irq;//根据中断号找到对应的irq对应的desc数组项
... ...
desc_handle_irq(irq, desc); //根据中断号和desc进行处理
... ...
}
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc); //调用 desc->handle_irq成员进行最终的处理
}
//中断处理的核心的三个结构体 //都在include/asm/arch/irq.h中定义
结构体1: irq_desc[]是一个指向irq_desc结构的数组
extern struct irq_desc irq_desc[NR_IRQS];
//其中NR_IRQS表示最大的中断号。
struct irq_desc {
irq_flow_handler_t handle_irq; /*中断处理函数入口*/
struct irq_chip *chip; /*底层硬件操作*/
... ...
struct irqaction *action; /* 中断处理函数链表*/
unsigned int status; /* IRQ 状态 */
... ...
const char *name; /* 中断名称 */
};
结构体2:irq_chip底层硬件操作的函数结构体
struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq); //启动中断缺省为enable
void (*shutdown)(unsigned int irq); //关闭中断缺省为disable
void (*enable)(unsigned int irq); //使能中断
void (*disable)(unsigned int irq); //禁止中断
void (*ack)(unsigned int irq); //响应中断通常清楚当前中断则
//可以接收下一个中断
void (*mask)(unsigned int irq); //屏蔽一个中断源
void (*mask_ack)(unsigned int irq); //响应和屏蔽一个中断源
void (*unmask)(unsigned int irq); //开启中断源
... ...
};
结构体3: irqaction用户注册的每个中断函数用一个此结构表示
truct irqaction {
irq_handler_t handler; //中断处理函数,注册时提供
unsigned long flags; //中断标志,注册时提供
cpumask_t mask; //中断掩码
const char *name; //中断名称
void *dev_id; //设备id
struct irqaction *next; //如果有中断共享,则继续执行,
int irq; //中断号,注册时提供
struct proc_dir_entry *dir; //指向IRQn相关的/proc/irq/n目录的描述符
};
/*##########中断注册的方法################*/
在linux内核中用于申请中断的函数是request_irq(),函数原型在Kernel/irq/manage.c中定义:
int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *devid)
参 数:irq是要申请的硬件中断号
handler是向系统注册的中断处理函数,是一个回调函数
中断发生时,系统调用这个函数.dev_id参数将被传递给它
irqflags是中断处理的属性有如下值:
IRQF_DISABLED:表示中断处理程序是快速处理程序, 快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽
IRQF_SHARED ,则表示多个设备共享中断
IRQF_SAMPLE_RANDOM,表示对系统熵有贡献,对系统获取随机数有好处
dev_id在中断共享时会用到,一般设置为这个设备的设备结构体,他是必须的。
devname设置中断名称,在cat /proc/interrupts中可以看到此名称
返回值:返回0:表示成功 -INVAL表示中断号无效或处理函数指针为NULL -EBUSY表示中断已经被占用且不能共享
dev_id参数为什么必须的,而且是必须唯一的?
发生中断时,内核并不判断究竟是共享中断线上的哪个设备产生了中断,它会循环执行所有该中断线上注册的中断处理函数(即irqaction->handler函数)。因此irqaction->handler函数有责任识别出是否是自己的硬件设备产生了中断, 然后再执行该中断处理函数。通常是通过读取该硬件设备提供的中断flag标志位进行判断。那既然kernel循环执行该中断线上注册的所有irqaction->handler函数,把识别究竟是哪个硬件设备产生了中断这件事交给中断处理函数本身去做, 那request_irq的dev_id参数究竟是做什么用的?
很多资料中都建议将设备结构指针作为dev_id参数。在中断到来时,迅速地根据硬件寄存器中的信息比照传入的dev_id参数判断是否是本设备的中断,若不是,应迅速返回。另外,当调用free_irq注销中断处理函数时(通常卸载驱动时其中断处理函数也会被注销掉),因为dev_id是唯一的,所以可以通过它来判断从共享中断线上的多个中断处理程序中删除指定的一个。如果没有这个参数,那么kernel不可能知道给定的中断线上到底要删除哪一个处理程序。 注销函数定义在Kernel/irq/manage.c中定义:
void free_irq(unsigned int irq, void *dev_id)。
继续看中断申请函数:
int request_irq(......)
{
action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
// request_irq首先使用4个参数构造一个irqaction结构
action->handler = handler;
action->flags = irqflags;
cpus_clear(action->mask);
action->name = devname;
action->next = NULL;
action->dev_id = dev_id;
... ...
retval = setup_irq(irq, action);
// 调用setup_irq函数将用户注册的具体处理函数链入链表
return retval;
}
/*###########kernel启动过程中的IRQ的初始化 ##############*/
//在内核启动的第二阶段执行的start_kernel函数(定义在init/main.c)中调用了init_IRQ()函数
//对涉及的irq中断做了初始化,具体过程为:
1.init_IRQ(); /*init/main.c*/
void __init init_IRQ(void)
{
int irq;
for (irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
//初始化irq_desc结构数组中每一项中断状态
init_arch_irq();
//调用架构相关的中断初始化函数
}
2.init_arch_irq();/*arch/arm/kernel/setup.c*/
init_arch_irq = mdesc->init_irq;
3.init_irq /* arch/arm/mach-s3c2440/mach-smdk2440.c*/
/* 说明:内核中对于每种支持的开发板都会使用MACHINE_START和MACHINE_END
定义一个machine_desc结构,这对移植过内核的朋友是非常熟悉的,它定义了开发板相关的属性及函数,例如。机器类型ID、起始IO物理地址、u-boot传入的参数列表起始地址、中断初始化函数、I/O映射函数等这对移植linux内核是至关重要的,如下*/
MACHINE_START(SMDK2440, "SMDK2440")
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq = s3c24xx_init_irq,//定义了中断初始化函数
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END
4.s3c24xx_init_irq /*arch/arm/plat-s3c24xx/irq.c*/
//这个函数为所有2440支持的中断设置了:
//1.操作底层硬件的函数结构体irq_desc[irq_no].irq_chip
//2.入口函数irq_desc[irq_no].handle_irq
void __init s3c24xx_init_irq(void)
{
/* setup the cascade irq handlers */
// 为具有子中断的中断源设置入口函数,主要功能获取子中断号,注意在初始化阶段不会进入
set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
//外部中断4-7共用的主中断(中断号IRQ_EINT4t7)的入口函数
//irq_desc[IRQ_EINT4t7].handle_irq = s3c_irq_demux_extint4t7
set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
//外部中8-23共用主中断(中断号IRQ_EINT8t23)的入口函数为,s3c_irq_demux_extint8
//irq_desc[IRQ_EINT8t23].handle_irq = s3c_irq_demux_extint8
/*下面是对串口总中断和ADC中断的设置*/
set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);
/* external interrupts */
/*外部中断0-3的入口函数及irq_chip设置*/
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
irqdbf("registering irq %d (ext int) ", irqno);
set_irq_chip(irqno, &s3c_irq_eint0t4);
//irq_desc[irq_no].irq_chip = &s3c_irq_eint0t4
set_irq_handler(irqno, handle_edge_irq);
//irq_desc[irq_no].handle_irq = handle_edge_irq
set_irq_flags(irqno, IRQF_VALID);
//irq_desc[irq_no].status = IRQF_VALID
}
/*外部中断4-23这些具体子中断的入口函数的设置*/
for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
irqdbf("registering irq %d (extended s3c irq) ", irqno);
set_irq_chip(irqno, &s3c_irqext_chip);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
/* register the uart interrupts */
/*串口0 1 2的各子中断源的入口函数设置*/
irqdbf("s3c2410: registering external interrupts ");
for (irqno = IRQ_S3CUART_RX0; irqno <= IRQ_S3CUART_ERR0; irqno++) {
irqdbf("registering irq %d (s3c uart0 irq) ", irqno);
set_irq_chip(irqno, &s3c_irq_uart0);
set_irq_handler(irqno, 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) ", irqno);
set_irq_chip(irqno, &s3c_irq_uart1);
set_irq_handler(irqno, 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) ", irqno);
set_irq_chip(irqno, &s3c_irq_uart2);
set_irq_handler(irqno, handle_level_irq);
set_irq_flags(irqno, IRQF_VALID);
}
/*ADC中断的入口函数设置*/
for (irqno = IRQ_TC; irqno <= IRQ_ADC; irqno++) {
irqdbf("registering irq %d (s3c adc irq) ", irqno);
set_irq_chip(irqno, &s3c_irq_adc);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
/* IRQ_UART0
IRQ_UART1:
IRQ_UART2:
IRQ_ADCPARENT:
用的是电平触发:handle_level_irq
其他的全是边沿触发:handle_edge_irq。
*/
irqdbf("s3c2410: registered interrupt handlers ");
}
//可见以上的初始化为2440支持的所有的中断设置了处理函数的入口对于具有子中断的中断源也做了特殊的设置见下文,使我们可以直接使用子中断。
//现以以IRQ_EINT8t23这个具有子中断的主中断为例
s3c_irq_demux_extint8(unsigned int irq,
struct irq_desc *desc)
{
unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
//EINT8~23发生时相应的位被置1
unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
//外部中断屏蔽寄存器
eintpnd &= ~eintmsk;//清除被屏蔽的位
eintpnd &= ~0xff; /* ignore lower irqs */
/* we may as well handle all the pending IRQs here */
while (eintpnd) {//循环执行所有置位的子中断
irq = __ffs(eintpnd); //算出第一个不为0的位
eintpnd &= ~(1<<irq); //清除相应的位
irq += (IRQ_EINT4 - 4);//计算子中断的中断号
desc_handle_irq(irq, irq_desc + irq);
//调用初始化时设置的各个子中断的处理函数进行处理
}
}
4.电平触发的中断处理函数handle_edge_irq
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
... ...
action_ret = handle_IRQ_event(irq, action);
//核心就是调用用户注册的action链表中的函数进行最终的处理
... ...
}
5.handle_IRQ_event函数
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
do {
ret = action->handler(irq, action->dev_id);
//执行用户注册在链表中的断处理函数
if (ret == IRQ_HANDLED)
status |= action->flags;
retval |= ret;
action = action->next;//把注册的函数都执行完毕
} while (action); //直到链表的末端
return retval;
}
S3C2440子中断的注册:
前面提到了主中断的注册,那么对于INTPND中的EINT4_7、EINT8_23、INT_UART0、INT_ADC等带有子中断的向量,INTOFFSET无法判断出具体的中断号。平台留给我们的注册方法如下:在include/asm/arch/irqs.h中有类似如下定义:
/* interrupts generated from the external interrupts sources */
#define IRQ_EINT4 S3C2410_IRQ(32) /* 48 */
#define IRQ_EINT5 S3C2410_IRQ(33)
#define IRQ_EINT6 S3C2410_IRQ(34)
#define IRQ_EINT7 S3C2410_IRQ(35)
#define IRQ_EINT8 S3C2410_IRQ(36)
#define IRQ_EINT9 S3C2410_IRQ(37)
#define IRQ_EINT10 S3C2410_IRQ(38)
#define IRQ_EINT11 S3C2410_IRQ(39)
#define IRQ_EINT12 S3C2410_IRQ(40)
#define IRQ_EINT13 S3C2410_IRQ(41)
#define IRQ_EINT14 S3C2410_IRQ(42)
#define IRQ_EINT15 S3C2410_IRQ(43)
#define IRQ_EINT16 S3C2410_IRQ(44)
#define IRQ_EINT17 S3C2410_IRQ(45)
#define IRQ_EINT18 S3C2410_IRQ(46)
#define IRQ_EINT19 S3C2410_IRQ(47)
#define IRQ_EINT20 S3C2410_IRQ(48) /* 64 */
#define IRQ_EINT21 S3C2410_IRQ(49)
#define IRQ_EINT22 S3C2410_IRQ(50)
#define IRQ_EINT23 S3C2410_IRQ(51)
可以看到平台为每种子中断都定义了中断号,
如果你想实现EINT8的中断注册,直接按照IRQ_EINT8这个中断号注册都可以了,
通过上文的叙述可知发生子中断时,在初始化时注册的处理函数如3c_irq_demux_extint8
就会计算出子中断号之后调用格子中断的处理函数处理。
下面总结一下:外部中断发生时的内核处理流程:
以mini2440开发板上key1按键的处理为例,key1对应//GPG0-->EINT8 外部中断8
1.按键之后PC指针自动指向
b vector_irq + stubs_offset
__irq_usr
irq_handler
get_irqnr_and_base r0, r6, r5, lr //查询中断号
bne asm_do_IRQ//将查询到中断号IRQ_EINT8t23传入asm_do_IRQ函数
desc_handle_irq(IRQ_EINT8t23, desc[IRQ_EINT8t23]);
desc[IRQ_EINT8t23]->handle_irq(IRQ_EINT8t23, desc[IRQ_EINT8t23]);
//调用初始化设置的中断号为IRQ_EINT8t23函数入口,见下一行:
//set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
s3c_irq_demux_extint8(IRQ_EINT8t23,desc[IRQ_EINT8t23])
irq += (IRQ_EINT4 - 4);//计算出具体份子中断号IRQ_EINT8
desc_handle_irq(IRQ_EINT8,desc[IRQ_EINT8]);
desc->handle_irq(IRQ_EINT8, desc[IRQ_EINT8]);
//调用初始化时为IRQ_EINT8子中断设置的处理函数
//set_irq_handler(IRQ_EINT8, handle_edge_irq);
handle_edge_irq(IRQ_EINT8,desc[IRQ_EINT8]);
handle_IRQ_event(IRQ_EIINT8,desc[IRQ_EINT8]->action);
action->handler(IRQ_EINT8,desc[IRQ_EINT8]->action->dev_id);
//最终调用用户注册到action链表中的中断处理函数执行
如有错误欢迎指正!谢谢!