linux 中断管理(三)

一、中断过程

1、中断的发生

当发生中断时,CPU会跳到异常向量表,处理相应的中断
异常向量表在 arch\arm\kernel\entry-armv.S 文件里面定义

    .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


         /* 中断入口地址。发生中断时会跳到这里,执行这句跳转语句,
          *+ stubs_offset 是因为异常向量表使用高地址0xffff0000,异常向量copy0xffff0000处 */
    b   vector_irq + stubs_offset   

 
    b   vector_fiq + stubs_offset

    .globl  __vectors_end
__vectors_end:

如果发生irq中断,就会执行b vector_irq + stubs_offset 这条语,会跳转到下面的代码块
对于vector_stub这个宏,在linux异常处理体系结构有简单的介绍

    /* vector_stub 是汇编下定义的一个宏, 里面是一块代码块,主要用于计算返回地址,计算下一步要跳转的入口地址
    * irq这个宏的名字, 
    * #define IRQ_MODE 0x00000012  
    * 4 这个是用于计算返回地址的
    */
    vector_stub irq, IRQ_MODE, 4     
    /* 下面要跳转到那个入口,是在 vector_stub 宏里面计算并且跳转的*/
    .long   __irq_usr           @  0  (USR_26 / USR_32)    /* 用户模式下发生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           @  5
    .long   __irq_invalid           @  6
    .long   __irq_invalid           @  7
    .long   __irq_invalid           @  8
    .long   __irq_invalid           @  9
    .long   __irq_invalid           @  a
    .long   __irq_invalid           @  b
    .long   __irq_invalid           @  c
    .long   __irq_invalid           @  d
    .long   __irq_invalid           @  e
    .long   __irq_invalid           @  f

之前在linux异常处理体系结构介绍到,不管是用户模式下发生中断,还是管理模式下发生中断,中断服务函数都是相同的(只是前面需要)。
接下来就以 __irq_usr 分析。

__irq_usr:
    /* usr_entry 也是一个宏,里面的代码块作用是保存 r0、r1 等一些寄存器 */
    usr_entry

#ifdef CONFIG_TRACE_IRQFLAGS      
    bl  trace_hardirqs_off
#endif
    get_thread_info tsk
#ifdef CONFIG_PREEMPT        
    ldr r8, [tsk, #TI_PREEMPT]      @ get preempt count
    add r7, r8, #1          @ increment it
    str r7, [tsk, #TI_PREEMPT]
#endif

    irq_handler       /* irq_handler 是一个宏,这个宏也是一个代码块,中断服务函数,也是在这个宏的代码块里面调用 */
#ifdef CONFIG_PREEMPT     
    ldr r0, [tsk, #TI_PREEMPT]
    str r8, [tsk, #TI_PREEMPT]
    teq r0, r7
    strne   r0, [r0, -r0]
#endif
#ifdef CONFIG_TRACE_IRQFLAGS    
    bl  trace_hardirqs_on
#endif

    mov why, #0
    b   ret_to_user

irq_handler 这个宏也是定义在 arch\arm\kernel\entry-armv.S 文件里面

    .macro  irq_handler
    get_irqnr_preamble r5, lr    /*get_irqnr_preamble 是一个宏,里面什么都没做 */
1:  get_irqnr_and_base r0, r6, r5, lr  /* get_irqnr_and_base 也是一个宏,在里面会计算当前的中断号(中断号通过 INTOFFSET 寄存器的某一位来确定)、中断状态等,和struct pt_regs 结构体压栈。*/
    movne   r1, sp    /* 通过堆栈将struct pt_regs* 传给 asm_do_IRQ 函数 */
    @
    @ routine called with r0 = irq number, r1 = struct pt_regs *
    @
    adrne   lr, 1b
    bne asm_do_IRQ   /* 到这里就是跳转过去执行中断服务函数了 */

2、中断的处理

asm_do_IRQ() 函数就是所有中断的服务函数了。 arch\arm\kernel\Irq.c文件里面定义,代码块如下:

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    /* 通过中断号获取 irq_desc 数组项*/
    struct irq_desc *desc = irq_desc + irq;

    /*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (irq >= NR_IRQS)
        desc = &bad_irq_desc;

    irq_enter();
        
    /* 这里处理中断了 */
    desc_handle_irq(irq, desc);

    /* AT91 specific workaround */
    irq_finish(irq);

    irq_exit();
    set_irq_regs(old_regs);
}

desc_handle_irq 函数里面就是调用用户注册的中断处理函数。 desc_handle_irq函数在 include\asm-arm\mach\Irq.h文件里面定义。代码块如下

static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
    desc->handle_irq(irq, desc);
}

*补充说明:

asm_do_IRQ函数中参数irq的取值的范围是 IRQ_EINT0 ~ 31, 只有32个取值。 asm_do_IRQ 函数中的 irq 参数可能是一个实际的中断号,也可能是一组中断的中断号,这是由s3c2440的芯片决定的.当发生中断时后 INTPND 寄存器的某一位被置1, INTOFFSET 寄存器的值确定irq参数。每一位实际的中断在irq_desc数组中都有一项与它对应, 他们的数目不止。当asm_do_IRQ函数中irq表示的是"一组"中断时,irq_desc[irq].handle_irq成员函数还需要先分辨出这一组中断的哪一个中断(假设中断号是irqno),然后调用irq_desc[irqno].handle_irq来进一步处理。

以外部中断EINT8~EINT23为例:

(1)中断被触发时,INTOFFSET寄存器的值是5, asm_do_IRQ函数中的参数irq的值为(IRQ_EINT0+5),即 IRQ_EINT8t23。然后将调用irq_desc[IRQ_EINT8t23].handle_irq 函数来处理
(2)irq_desc[IRQ_EINT8t23].handle_irq 在初始化的时候,被初始化为 s3c_irq_demux_extint8
(3)s3c_irq_demux_extint8 函数在 arch\arm\plat-s3c24xx\Irq.c 文件里面定义。它先读取 EINTPEND 和 EINTMASK 寄存器的值来确定发生了那些中断;然后重新计算中断号,然后再调用irq_desc数组项中的handle_irq成员函数。

s3c_irq_demux_extint8 函数的代码如下:

static void
s3c_irq_demux_extint8(unsigned int irq, struct irq_desc *desc)
{    
    /* 读取 EINTPEND 寄存器的值,EINT8 ~ EINT23 发生时相应的位被置1 */
    unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
    /* 读取屏蔽寄存器的值 */
    unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
    /* 清除被屏蔽位 */
    eintpnd &= ~eintmsk;
    /* 清除低8位,EINT8对应位8 ... */
    eintpnd &= ~0xff;   /* ignore lower irqs */

    /* we may as well handle all the pending IRQs here */
    /* 循环处理所有的子中断 */
    while (eintpnd) {
        /* 确定 eintpnd 中为1的最高位  */
        irq = __ffs(eintpnd);
       /* 将该位清零 */
        eintpnd &= ~(1<<irq);
        /* 重新计算中断号:前面计算出,如果 irq = __ffs(eintpnd) 算出的是8,则中断号为8 */
            irq += (IRQ_EINT4 - 4);
        /* 调用这个中断真正的处理函数(handle_edge_irq函数)  */
        desc_handle_irq(irq, irq_desc + irq);
    }

}

(4)IRQ_EINT8 ~ IRQ_EINT23 这几个真正的中断处理函数入口,在init_IRQ函数初始化中断的时候被初始化 handle_edge_irq (边缘触发方式)函数。
handle_edge_irq 函数在 kernel\irq\Chip.c 文件里面定义,代码如下:

void fastcall handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
    ...
    /* 统计中断发生的次数 */
    kstat_cpu(cpu).irqs[irq]++;

    /* Start handling the irq */
    /* 调用 chip 底层的操作函数 清除中断 */
    desc->chip->ack(irq);

    /* Mark the IRQ currently in progress.*/
    desc->status |= IRQ_INPROGRESS;

    do {
        /* 依次调用 action 链表里面的处理函数(用户注册的函数都是挂在action链表里面的) */
        action_ret = handle_IRQ_event(irq, action);
        

    } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

}

handle_IRQ_event 依次调用 action 链表里面的处理函数;这个函数在 kernel\irq\Handle.c 文件里面定义,代码块如下

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{

    
    ...
     /* 依次调用 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;
}

注意: 上面分析的是边缘触发的中断过程,对于边缘触发调用的是handle_edge_irq()函数处理,对于电平触发的调用的是 handle_level_irq 函数,处理的方式都是类似的,只是电平触发的还要屏蔽这个中断,处理完之后还要开启这个中断。

总结:

(1) 发生中断时,中断向量调用asm_do_IRQ,传入中断号irq

(2) asm_do_IRQ 函数根据中断号irq调用irq_desc[irq].handle_irq。他是这个中断的处理函数入口。对于电平触发这个函数入口通常是handle_level_irq,对于边缘触发入口函数通常是handle_edge_irq

(3) 入口函数首先清除中断,入口函数是handle_level_irq时还要屏蔽中断。

(4) 逐个调用用户注册在 irq_desc[irq].action 链表中注册的中断处理函数

(5) 入口函数是handle_level_irq时,处理完之后还要开启这个中断。

猜你喜欢

转载自www.cnblogs.com/gulan-zmc/p/12008047.html