linux外部中断过程

内核在linux-2.6.22.6\init\main.c:start_kernel函数中调用trap_init、init_IRQ 两个函数来设置异常的处理函数。

asmlinkage void __init start_kernel(void)
{
    ...
    trap_init();
    ...
    init_IRQ();
    ...
}

1.linux-2.6.22.6\arch\arm\kernel\traps.c\:trap_init函数分析
trap_init函数被用来设置各种异常的处理向量,所谓向量,就是一些被安放在固定位置的代码,当发生异常时,CPU会自动执行这些固定位置上相应代码段,arm架构linux内核异常向量基址地0xffff0000,trap_init函数将异常向量复制到0ffff0000处:

void __init trap_init(void)
{
    ...
    unsigned long vectors = CONFIG_VECTORS_BASE;     //CONFIG_VECTORS_BASE=0xffff0000
    extern char __stubs_start[], __stubs_end[];
    extern char __vectors_start[], __vectors_end[];
    ...

    /*
     * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
     * into the vector page, mapped at 0xffff0000, and ensure these
     * are visible to the instruction stream.
     */
    memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
    memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
    ...
}

vectors等于0xffff0000是异常向量的基址,memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);将从__vectors_start地址开始的,大小为__vectors_end - __vectors_start的代码复制到0xffff0000地址上,__vectors_start~-_vectors_end之间的代码就是异常向量,在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
    b   vector_irq + stubs_offset
    b   vector_fiq + stubs_offset
    .globl  __vectors_end
__vectors_end:

    .data

    .globl  cr_alignment
    .globl  cr_no_alignment

其中的vector_und、vector_pabt、vector_irq等表示要跳转去执行的代码,以vector_irq为例,它在arch/arm/kernel/entry-armv.S中,通过宏指令来定义:

vector_stub irq, IRQ_MODE, 4

.long   __irq_usr           @  0  (USR_26 / USR_32)
.long   __irq_invalid           @  1  (FIQ_26 / FIQ_32)
.long   __irq_invalid           @  2  (IRQ_26 / IRQ_32)
.long   __irq_svc           @  3  (SVC_26 / SVC_32)
.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

vector_stub是一个带参宏指令,它根据后面的参数定义了以“vector_irq”为标号的一段代码,具体是:

.macro  vector_stub, name, mode, correction=0
.align  5

    vector_\name:
        .if \correction
        sub lr, lr, #\correction
        .endif

        @
        @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
        @ (parent CPSR)
        @
        stmia   sp, {r0, lr}        @ save r0, lr
        mrs lr, spsr
        str lr, [sp, #8] @ save spsr

        @
        @ Prepare for SVC32 mode.  IRQs remain disabled.
        @
        mrs r0, cpsr
        eor r0, r0, #(\mode ^ SVC_MODE)
        msr spsr_cxsf, r0

        @
        @ the branch table must immediately follow this code
        @
        and lr, lr, #0x0f
        mov r0, sp
        ldr lr, [pc, lr, lsl #2]
        movs    pc, lr          @ branch to handler in SVC mode
.endm

异常向量的代码很简单,它们只是一些跳转指令。发生异常时,CPU自动执行这些指令,跳转去执行更复杂的代码,比如保存被中断程序的执行环境,调用异常处理函数,恢复被中断程序的执行环境并重新运行。这些“更复杂的代码”在地址__stubs_start~__stubs_end之间,被复制到了vectors + 0x200地址处,它们在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)
    .long   __irq_invalid           @  1  (FIQ_26 / FIQ_32)
    .long   __irq_invalid           @  2  (IRQ_26 / IRQ_32)
    .long   __irq_svc           @  3  (SVC_26 / SVC_32)
    .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

/* * Data abort dispatcher * Enter in ABT mode, spsr = USR CPSR, lr = USR PC */
    vector_stub dabt, ABT_MODE, 8

    .long   __dabt_usr          @  0  (USR_26 / USR_32)
    .long   __dabt_invalid          @  1  (FIQ_26 / FIQ_32)
    .long   __dabt_invalid          @  2  (IRQ_26 / IRQ_32)
    .long   __dabt_svc          @  3  (SVC_26 / SVC_32)
    .long   __dabt_invalid          @  4
    .long   __dabt_invalid          @  5
    .long   __dabt_invalid          @  6
    .long   __dabt_invalid          @  7
    .long   __dabt_invalid          @  8
    .long   __dabt_invalid          @  9
    .long   __dabt_invalid          @  a
    .long   __dabt_invalid          @  b
    .long   __dabt_invalid          @  c
    .long   __dabt_invalid          @  d
    .long   __dabt_invalid          @  e
    .long   __dabt_invalid          @  f

/* * Prefetch abort dispatcher * Enter in ABT mode, spsr = USR CPSR, lr = USR PC */
    vector_stub pabt, ABT_MODE, 4

    .long   __pabt_usr          @  0 (USR_26 / USR_32)
    .long   __pabt_invalid          @  1 (FIQ_26 / FIQ_32)
    .long   __pabt_invalid          @  2 (IRQ_26 / IRQ_32)
    .long   __pabt_svc          @  3 (SVC_26 / SVC_32)
    .long   __pabt_invalid          @  4
    .long   __pabt_invalid          @  5
    .long   __pabt_invalid          @  6
    .long   __pabt_invalid          @  7
    .long   __pabt_invalid          @  8
    .long   __pabt_invalid          @  9
    .long   __pabt_invalid          @  a
    .long   __pabt_invalid          @  b
    .long   __pabt_invalid          @  c
    .long   __pabt_invalid          @  d
    .long   __pabt_invalid          @  e
    .long   __pabt_invalid          @  f

/* * Undef instr entry dispatcher * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC */
    vector_stub und, UND_MODE

    .long   __und_usr           @  0 (USR_26 / USR_32)
    .long   __und_invalid           @  1 (FIQ_26 / FIQ_32)
    .long   __und_invalid           @  2 (IRQ_26 / IRQ_32)
    .long   __und_svc           @  3 (SVC_26 / SVC_32)
    .long   __und_invalid           @  4
    .long   __und_invalid           @  5
    .long   __und_invalid           @  6
    .long   __und_invalid           @  7
    .long   __und_invalid           @  8
    .long   __und_invalid           @  9
    .long   __und_invalid           @  a
    .long   __und_invalid           @  b
    .long   __und_invalid           @  c
    .long   __und_invalid           @  d
    .long   __und_invalid           @  e
    .long   __und_invalid           @  f

    .align  5
    .globl  __stubs_end
__stubs_end:

    .equ    stubs_offset, __vectors_start + 0x200 - __stubs_start
__irq_usr:
    usr_entry

    ...

    irq_handler

    ...

    .ltorg
.macro  irq_handler
    ...

        bne asm_do_IRQ

    ...
.endm

arch/arm/kernel/entry-armv.S/irq_handler☞☞☞linux-2.6.22.6\arch\arm\kernel\irq.c:asm_do_IRQ

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    ...

    struct irq_desc *desc = irq_desc + irq;

    ...

    desc_handle_irq(irq, desc);

    ...
}

linux-2.6.22.6\arch\arm\kernel\irq.c:asm_do_IRQ☞☞☞linux-2.6.22.6\include\asm-arm\mach\irq.h:desc_handle_irq

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

handle_irq是指向中断处理函数的指针,由语句desc->handle_irq(irq, desc);跳转到中断处理函数,handle_irq指向irq中断的处理函数handle_edge_irq,通过由以下过程指定:

由以下初始化过程指定最终给handle_irq赋值的handle的值:

linux-2.6.22.6\arch\arm\plat-s3c24xx\irq.c:
void __init s3c24xx_init_irq(void)
{
    unsigned long pend;
    unsigned long last;
    int irqno;
    int i;

    ...

    /* register the main interrupts */
    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:
        set_irq_chip(irqno, &s3c_irq_level_chip);
        set_irq_handler(irqno, handle_level_irq);
        break;

    case IRQ_RESERVED6:
    case IRQ_RESERVED24:
        /* no IRQ here */
        break;

    default:
        //irqdbf("registering irq %d (s3c irq)\n", irqno);
        set_irq_chip(irqno, &s3c_irq_chip);
        set_irq_handler(irqno, handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }
}

    ...

    /* external interrupts */

    for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
        irqdbf("registering irq %d (ext int)\n", irqno);
        set_irq_chip(irqno, &s3c_irq_eint0t4);
        set_irq_handler(irqno, 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);
        set_irq_chip(irqno, &s3c_irqext_chip);
        set_irq_handler(irqno, 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);
        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);
    }

    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);
    }
}

linux-2.6.22.6\arch\arm\plat-s3c24xx\irq.c:s3c24xx_init_irq☞☞☞linux-2.6.22.6\include\linux\irq.h:set_irq_handler

static inline void set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
{
    __set_irq_handler(irq, handle, 0, NULL);
}

handle_irq的值由以下linux-2.6.22.6\kernel\irq\chip.c:__set_irq_handler函数指定,而handle的值指向handle_edge_irq函数,因此handle_irq也指向handle_edge_irq函数:

void __set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained, const char *name)
{
    struct irq_desc *desc;
    unsigned long flags;

    ...

    desc = irq_desc + irq;

    ...

    desc->handle_irq = handle;

    ...
}

linux-2.6.22.6\kernel\irq\chip.c:handle_edge_irq中断处理函数主要做以下两方面工作:

  • 清中断
    desc->chip->ack(irq); //Start handling the irq

  • 处理中断
    action_ret = handle_IRQ_event(irq, action);

linux-2.6.22.6\kernel\irq\handle.c:handle_IRQ_event函数取出action链表中的成员,一一执行它们的用户中断处理程序action->handler

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
    irqreturn_t ret, retval = IRQ_NONE;
    unsigned int status = 0;

    ...

    do {
        ret = action->handler(irq, action->dev_id);
        if (ret == IRQ_HANDLED)
            status |= action->flags;
        retval |= ret;
        action = action->next;
    } while (action);

    ...
}

我们希望的执行的中断处理程序通过linux-2.6.22.6\kernel\irq\manage.c:request_irq函数注册进action链表

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
{
    struct irqaction *action;
    int retval;

    ...

    retval = setup_irq(irq, action);

    ...
}

linux-2.6.22.6\kernel\irq\manage.c:request_irq☞☞☞linux-2.6.22.6\kernel\irq\manage.c:setup_irq,进入irq建立函数,将新的中断项加入链表,设置中断引脚类型,并使能中断

int setup_irq(unsigned int irq, struct irqaction *new)
{
    struct irq_desc *desc = irq_desc + irq;
    struct irqaction *old, **p;
    const char *old_name = NULL;
    unsigned long flags;
    int shared = 0; 

    ...

    if (old) {

        ...

        /* add new interrupt at end of irq queue */
        do {
            p = &old->next;
            old = *p;
        } while (old);
        shared = 1;
    }

    *p = new;

    ...

    if (!shared) {

        ...

        /* Setup the type (level, edge polarity) if configured: */
        if (new->flags & IRQF_TRIGGER_MASK) {
            if (desc->chip && desc->chip->set_type)
                desc->chip->set_type(irq,
                        new->flags & IRQF_TRIGGER_MASK);
            else
                printk(KERN_WARNING "No IRQF_TRIGGER set_type "
                       "function for IRQ %d (%s)\n", irq,
                       desc->chip ? desc->chip->name :
                       "unknown");
        } else
            compat_irq_chip_set_default_handler(desc);

        ...

        if (!(desc->status & IRQ_NOAUTOEN)) {
            desc->depth = 0;
            desc->status &= ~IRQ_DISABLED;
            if (desc->chip->startup)
                desc->chip->startup(irq);
            else
                desc->chip->enable(irq);
        } else
            /* Undo nested disables: */
            desc->depth = 1;
    }
}

解除中断用linux-2.6.22.6\kernel\irq\manage.c:free_irq函数
2.linux-2.6.22.6\arch\arm\kernel\irq.c:init_IRQ函数分析
init_IRQ函数被用来初始化中断的处理框架,设置各种中断的默认处理函数。当发生中断时,中断总入口函数asm_do_IRQ就可以调用这些函数作进一步处理

你可能感兴趣的:(linux,内核)