Linux内核中断处理过程分析-基于arm平台

来源:互联网 发布:程序员情商到底有多低 编辑:程序博客网 时间:2024/05/16 06:10

说明:此文以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                                                //读取spsrlr

       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

      

      

//上面的处理又是一个函数指针,不同模式下发生的异常要进入不同的处理函数处理,由于ARM4个二进制位表示处理器的模式,所以理论上有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行就是把中断向量表拷贝到定义的向量表基地址,0xffff000022行中__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_irqstubs_start中的偏移就是:

vector_irq - __stubs_start

这两个偏移是恒定的,不会因为代码搬移改变,搬移后__vectors_start0xffff0000,__stubs_start0xffff0000+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_irqdev_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_STARTMACHINE_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)\n", 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)\n", 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\n");

       for (irqno = IRQ_S3CUART_RX0; irqno <= IRQ_S3CUART_ERR0; irqno++) {

              irqdbf("registering irq %d (s3c uart0 irq)\n", 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)\n", 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)\n", 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)\n", 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\n");

}

//可见以上的初始化为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_7EINT8_23INT_UART0INT_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_EINT8desc[IRQ_EINT8]);

                                                handle_IRQ_event(IRQ_EIINT8,desc[IRQ_EINT8]->action);

                                                   action->handler(IRQ_EINT8,desc[IRQ_EINT8]->action->dev_id);

                                                  //最终调用用户注册到action链表中的中断处理函数执行

如有错误欢迎指正!谢谢!
原创粉丝点击