中断处理过程

来源:互联网 发布:现货交易软件开发 编辑:程序博客网 时间:2024/06/09 14:43

本文的初衷是搞清楚:当中断发生后,硬件会关闭中断,但何时会打开?

为这个问题,从中断发生到从中断返回遍历了一遍。

1.异常矢量表

/*****************************************************************/

entry-armv.S

当异常发生时跳转到矢量表处,这里只是一个跳转指令:
    .globl    __vectors_start
__vectors_start:
 ARM(    swi    SYS_ERROR0    )
    W(b)    vector_und + stubs_offset
    W(ldr)    pc, .LCvswi + stubs_offset
    W(b)    vector_pabt + stubs_offset
    W(b)    vector_dabt + stubs_offset
    W(b)    vector_addrexcptn + stubs_offset
    W(b)    vector_irq + stubs_offset
    W(b)    vector_fiq + stubs_offset

    .globl    __vectors_end


2.中断处理的整体框架

以中断异常为例:vector_irq是宏vector_stub irq, IRQ_MODE, 4展开后的结果。
宏vector_stub    irq, IRQ_MODE, 4 展开后就是这个样子:

vector_irq:
    .if \correction[4]/*根据中断模式修改 lr*/
    sub    lr, lr, #\correction[4]
    .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 | PSR_ISETSTATE)
    msr    spsr_cxsf, r0

    @
    @ the branch table must immediately follow this code
    @

    /*当发生异常时,CPU会根据异常的类型进入某个工作模式,但是很快vector_stub宏又会
     *强制CPU进行管理模式,在管理模式下进行后续处理,这种方法简化了程序的设计,使得
     *异常发生后的工作模式要么是用户模式,要么是管理模式
     */
    and    lr, lr, #0x0f
    mov    r0, sp
    ldr    lr, [pc, lr, lsl #2]
    movs    pc, lr            @ branch to handler in SVC mode

    .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

/*根据异常模式【SVC or user只有这两种,非user都按照svc处理】*/

假设进入svc模式:
__irq_svc:
    svc_entry
    irq_handler
    svc_exit r5 @ return from exception
__irq_svc分为3部分:
svc_entry保存寄存器的值,最后的结果就是对变量pt_regs赋值
    struct pt_regs {
        unsigned long uregs[18];
    };

    #define ARM_cpsr    uregs[16]
    #define ARM_pc        uregs[15]
    #define ARM_lr        uregs[14]
    #define ARM_sp        uregs[13]
    #define ARM_ip        uregs[12]
    #define ARM_fp        uregs[11]
    #define ARM_r10        uregs[10]
    #define ARM_r9        uregs[9]
    #define ARM_r8        uregs[8]
    #define ARM_r7        uregs[7]
    #define ARM_r6        uregs[6]
    #define ARM_r5        uregs[5]
    #define ARM_r4        uregs[4]
    #define ARM_r3        uregs[3]
    #define ARM_r2        uregs[2]
    #define ARM_r1        uregs[1]
    #define ARM_r0        uregs[0]
    #define ARM_ORIG_r0    uregs[17]

      BLANK();
      DEFINE(S_R0,            offsetof(struct pt_regs, ARM_r0));
      DEFINE(S_R1,            offsetof(struct pt_regs, ARM_r1));
      DEFINE(S_R2,            offsetof(struct pt_regs, ARM_r2));
      DEFINE(S_R3,            offsetof(struct pt_regs, ARM_r3));
      DEFINE(S_R4,            offsetof(struct pt_regs, ARM_r4));
      DEFINE(S_R5,            offsetof(struct pt_regs, ARM_r5));
      DEFINE(S_R6,            offsetof(struct pt_regs, ARM_r6));
      DEFINE(S_R7,            offsetof(struct pt_regs, ARM_r7));
      DEFINE(S_R8,            offsetof(struct pt_regs, ARM_r8));
      DEFINE(S_R9,            offsetof(struct pt_regs, ARM_r9));
      DEFINE(S_R10,            offsetof(struct pt_regs, ARM_r10));
      DEFINE(S_FP,            offsetof(struct pt_regs, ARM_fp));
      DEFINE(S_IP,            offsetof(struct pt_regs, ARM_ip));
      DEFINE(S_SP,            offsetof(struct pt_regs, ARM_sp));
      DEFINE(S_LR,            offsetof(struct pt_regs, ARM_lr));
      DEFINE(S_PC,            offsetof(struct pt_regs, ARM_pc));
      DEFINE(S_PSR,            offsetof(struct pt_regs, ARM_cpsr));
      DEFINE(S_OLD_R0,        offsetof(struct pt_regs, ARM_ORIG_r0));
      DEFINE(S_FRAME_SIZE,        sizeof(struct pt_regs));
      BLANK();

先说下svc_exit,就是从中断返回,没有做其他事情
svc_exit r5 @ return from exception
.macro    svc_exit, rpsr
    msr    spsr_cxsf, \rpsr
    clrex                    @ clear the exclusive monitor
    ldmia    sp, {r0 - pc}^            @ load r0 - pc, cpsr
.endm


3.中断处理框架的irq_handle

arch/arm/kernel/setup.c

这里引入一个简单的抽象层 handle_arch_irq,被平台具体的 interrupt controller赋值

handle_arch_irq = mdesc->handle_irq;

DT_MACHINE_START(TL7689_PAD_TEST,"NUFRONT-TL7689-PAD-AURORA")
    .map_io        = tl7689_map_io,
    .init_irq    = of_pad_test_gic_init,
    .timer        = &tl7689_timer,
    .handle_irq    = gic_handle_irq,
    .init_machine    = tl7689_dt_init,
    .init_early    = tl7689_init_early,
    .dt_compat    = tl7689_dt_board_compat,
MACHINE_END

最后看下macro irq_handler

    .macro    irq_handler
    ldr    r1, =handle_arch_irq
    mov    r0, sp
    adr    lr, BSYM(9997f)/*这里是设置返回地址到lr*/
    ldr    pc, [r1]
9997:
    .endm


3.1 irq_handle的C入口点

找到中断处理的入口点:gic_handle_irq
crash> handle_arch_irq
handle_arch_irq = $1 =
 {<text variable, no debug info>} 0xc074284c <handle_arch_irq>

crash> rd 0xc074284c
c074284c:  c0008458

crash> dis c0008458

0xc0008458 <gic_handle_irq>:    mov     r12, sp

arch/arm/common/gic.c

asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
    u32 irqstat, irqnr;
    struct gic_chip_data *gic = &gic_data[0];
    void __iomem *cpu_base = gic_data_cpu_base(gic);

    do {
        irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
        irqnr = irqstat & ~0x1c00;

        if (likely(irqnr > 15 && irqnr < 1021)) {
            irqnr = irq_find_mapping(gic->domain, irqnr);
            handle_IRQ(irqnr, regs);
            continue;
        }
        if (irqnr < 16) {
            writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
            handle_IPI(irqnr, regs);
#endif
            continue;
        }
        break;
    } while (1);
}

arch/arm/kernel/irq.c

void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);

    irq_enter();

    /*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (unlikely(irq >= nr_irqs)) {
        if (printk_ratelimit())
            printk(KERN_WARNING "Bad IRQ%u\n", irq);
        ack_bad_irq(irq);
    } else {
        generic_handle_irq(irq);
    }

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

    irq_exit();
    set_irq_regs(old_regs);
}

kernel/irq/irqdesc.c

int generic_handle_irq(unsigned int irq)
{
    struct irq_desc *desc = irq_to_desc(irq);

    if (!desc)
        return -EINVAL;
    generic_handle_irq_desc(irq, desc);
    return 0;
}

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


现在的文档多出都说asm_do_IRQ是irq处理的C语言入口点,为什么这里看到是gic_handle_irq?
是因为CONFIG_MULTI_IRQ_HANDLER开关没有使用arch_irq_handler_default
    .macro    irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
    ldr    r1, =handle_arch_irq
    mov    r0, sp
    adr    lr, BSYM(9997f)
    ldr    pc, [r1]
#else
    arch_irq_handler_default
#endif
9997:
    .endm

/*
 * Interrupt handling.  Preserves r7, r8, r9
 */
    .macro    arch_irq_handler_default
    get_irqnr_preamble r6, lr
1:    get_irqnr_and_base r0, r2, r6, lr
    movne    r1, sp
    @
    @ routine called with r0 = irq number, r1 = struct pt_regs *
    @
    adrne    lr, BSYM(1b)
    bne    asm_do_IRQ
/*函数asm_do_IRQ的两个参数是在汇编里得到的, 而gic_handle_irq只有一个参数
 *中断号是在函数本身得到的,但最终调用的都是handle_IRQ(irqnr, regs);
 */
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    handle_IRQ(irq, regs);
}


3.2 每个中断号对应中断处理函数的框架

void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);

    irq_enter();

    /*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (unlikely(irq >= nr_irqs)) {
        if (printk_ratelimit())
            printk(KERN_WARNING "Bad IRQ%u\n", irq);
        ack_bad_irq(irq);
    } else {
        generic_handle_irq(irq);
    }

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

    irq_exit();
    set_irq_regs(old_regs);
}
/*到开始进入gic_handle_irq为止,中断是关闭的,何时打开的呀?接着看 handle_IRQ*/

handle_IRQ -> generic_handle_irq(irq) -> generic_handle_irq_desc(irq, desc)
                        -> desc->handle_irq(irq, desc);

没个中断号对应的不同的handle:以handle_level_irq为例。

3.2.1 中断号具体的中断处理函数

/***********************************************************************************/
void
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
    raw_spin_lock(&desc->lock);
    /*调用具体的chip相关的函数mask and ack*/
    mask_ack_irq(desc);
    /*如果另一个CPU在执行该中断即状态为IRQD_IRQ_INPROGRESS
         *则让另一个中断处理该中断,直接退出该中断处理
     *
/
    if (unlikely(irqd_irq_inprogress(&desc->irq_data)))
        if (!irq_check_poll(desc))
            goto out_unlock;

    desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
    kstat_incr_irqs_this_cpu(irq, desc);

    /*
     * If its disabled or no action available
     * keep it masked and get out of here
     */
    if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data)))
        goto out_unlock;
    /*处理该中断*/
    handle_irq_event(desc);

    cond_unmask_irq(desc);

out_unlock:
    raw_spin_unlock(&desc->lock);
}


3.2.1.1 调用注册的中断处理函数

/*
 *这里关联到irqaction
 */
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
    struct irqaction *action = desc->action;
    irqreturn_t ret;

    desc->istate &= ~IRQS_PENDING;
    irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    raw_spin_unlock(&desc->lock);

    ret = handle_irq_event_percpu(desc, action);

    raw_spin_lock(&desc->lock);
    irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    return ret;
}

irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
    irqreturn_t retval = IRQ_NONE;
    do{
        action->handler(irq, action->dev_id);
        switch (res) {
        case IRQ_WAKE_THREAD:
            irq_wake_thread(desc, action);
            /* Fall through to add to randomness */
        case IRQ_HANDLED:
            random |= action->flags;
            break;

        default:
            break;
        action = action->next;

    }while(action);

    if (random & IRQF_SAMPLE_RANDOM)/*中断源作为随机数的种子*/
        add_interrupt_randomness(irq);
}

/*到这里完成了注册的中断函数的调用,是在关中断的条件下完成的,中断处理还有一部分*/


3.2.2 irq_exit

/*
 * Exit an interrupt context. Process softirqs if needed and possible:
 */
void irq_exit(void)
{
    account_system_vtime(current);
    trace_hardirq_exit();
    sub_preempt_count(IRQ_EXIT_OFFSET);
    if (!in_interrupt() && local_softirq_pending())
        invoke_softirq();

    rcu_irq_exit();
    sched_preempt_enable_no_resched();
}


看看进入softirq的条件:!in_interrupt()
#define in_interrupt()        (irq_count())
#define irq_count()    (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK))

/*preempt_count是thread_info的一个成员*/
#define preempt_count()    (current_thread_info()->preempt_count)

看看进入softirq的条件:就是没有未处理完的 HARDIRQ and SOFTIRQ,并且软件中断有pending 位;

这里能看出串行的意思:如果当前有 softirq就退出,但是它的action 会在__do_softirq被调用.


另外一个有关串行的地方:tasklet,如果发现tasklet_struct的flag为TASKLET_STATE_RUN【Tasklet is running (SMP only)】

这次就不执行,加入到队尾,下次再尝试执行。 同一个tasklet不会发生竞争。

不同tasklet就可能发生竞争:虽然!in_interrupt()保证,不同tasklet在同一个cpu上是串行的,但是不同的tasklet可以在不同的CPU上运行。

static void tasklet_action(struct softirq_action *a)
{
    struct tasklet_struct *list;

    local_irq_disable();
    list = __this_cpu_read(tasklet_vec.head);
    __this_cpu_write(tasklet_vec.head, NULL);
    __this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head);
    local_irq_enable();

    while (list) {
        struct tasklet_struct *t = list;

        list = list->next;

        if (tasklet_trylock(t)) {
            if (!atomic_read(&t->count)) {
                if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
                    BUG();
                t->func(t->data);
                tasklet_unlock(t);
                continue;
            }
            tasklet_unlock(t);
        }

        local_irq_disable();
        t->next = NULL;
        *__this_cpu_read(tasklet_vec.tail) = t;
        __this_cpu_write(tasklet_vec.tail, &(t->next));
        __raise_softirq_irqoff(TASKLET_SOFTIRQ);
        local_irq_enable();
    }
}



static inline void invoke_softirq(void)
{
    if (!force_irqthreads) {

        do_softirq();
    }
}

asmlinkage void do_softirq(void)
{
    __u32 pending;
    unsigned long flags;

    if (in_interrupt())
        return;

    local_irq_save(flags);

    pending = local_softirq_pending();

    if (pending)
        __do_softirq();

    local_irq_restore(flags);
}

/*在这里终于看到了开中断的地方:local_irq_enable();
 *在处理softirq_action的前后:local_irq_enable() and local_irq_disable();
 *
/
asmlinkage void __do_softirq(void)
{
    struct softirq_action *h;
    pending = local_softirq_pending();

    __local_bh_disable((unsigned long)__builtin_return_address(0),
                SOFTIRQ_OFFSET);
restart:
    local_irq_enable();

    h = softirq_vec;

    do {
        if (pending & 1) {
            unsigned int vec_nr = h - softirq_vec;
            int prev_count = preempt_count();

            kstat_incr_softirqs_this_cpu(vec_nr);

            trace_softirq_entry(vec_nr);
            h->action(h);
            trace_softirq_exit(vec_nr);

        }
        h++;
        pending >>= 1;
    } while (pending);

    local_irq_disable();
    pending = local_softirq_pending();
    if (pending && --max_restart)
        goto restart;

    if (pending)
        wakeup_softirqd();

    lockdep_softirq_exit();

    account_system_vtime(current);
    __local_bh_enable(SOFTIRQ_OFFSET);
}

crash> softirq_vec
softirq_vec = $3 =
 {{
    action = 0xc0030280 <tasklet_hi_action>
  }, {
    action = 0xc00367dc <run_timer_softirq>
  }, {
    action = 0xc041a874 <net_tx_action>
  }, {
    action = 0xc041d69c <net_rx_action>
  }, {
    action = 0xc01f0e0c <blk_done_softirq>
  }, {
    action = 0xc01f1534 <blk_iopoll_softirq>
  }, {
    action = 0xc00303a8 <tasklet_action>
  }, {
    action = 0xc005cf54 <run_rebalance_domains>
  }, {
    action = 0xc004cb5c <run_hrtimer_softirq>
  }, {
    action = 0xc0087068 <rcu_process_callbacks>
  }
}


static void tasklet_action(struct softirq_action *a)
{
    struct tasklet_struct *list;

    local_irq_disable();
    list = __this_cpu_read(tasklet_vec.head);
    __this_cpu_write(tasklet_vec.head, NULL);
    __this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head);
    local_irq_enable();

    while (list) {
        struct tasklet_struct *t = list;

        list = list->next;

        if (tasklet_trylock(t)) {
            if (!atomic_read(&t->count)) {
                if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
                    BUG();
                t->func(t->data);
                tasklet_unlock(t);
                continue;
            }
            tasklet_unlock(t);
        }

        local_irq_disable();
        t->next = NULL;
        *__this_cpu_read(tasklet_vec.tail) = t;
        __this_cpu_write(tasklet_vec.tail, &(t->next));
        __raise_softirq_irqoff(TASKLET_SOFTIRQ);
        local_irq_enable();
    }
}
函数的主要功能是遍历tasklet_vec
crash> tasklet_vec
PER-CPU DATA TYPE:
  struct tasklet_head tasklet_vec;

4. 开关中断的汇编代码

 local_irq_enable()/  local_irq_disable()对应的汇编代码

使用中断内在函数生成用于更改当前抢先优先级的 CPSIE 或 CPSID 指令(请参阅Table 2.10)。 例如,在使用 __disable_irq 内在函数时,编译器会生成 CPSID i 指令,该指令将 PRIMASK 设置为 1。 这将把执行优先级提升为 0,并阻止具有可配置优先级的异常进入。 请参阅《ARMv7-M 体系结构参考手册》。
Table 2.10. 中断内在函数
内在函数    操作码    PRIMASK    FAULTMASK
__enable_irq    CPSIE i    0    
__disable_irq    CPSID i    1    
__enable_fiq    CPSIE f         0
__disable_fiq    CPSID f         1

static inline void arch_local_irq_enable(void)
{
    asm volatile(
        "    cpsie i            @ arch_local_irq_enable"
        :
        :
        : "memory", "cc");
}


5. 如果没有进入softirq,怎样打开中断的?

是在退出中断时,打开的【恢复就是打开啊,原来总是开中断的】

svc_exit r5 @ return from exception
.macro    svc_exit, rpsr
    msr    spsr_cxsf, \rpsr
    clrex                    @ clear the exclusive monitor
    ldmia    sp, {r0 - pc}^            @ load r0 - pc, cpsr
.endm


6 有关的函数类型和关系

有两个函数类型: irq_flow_handle and irq_handle

irq_flow_handle是框架型的,会调有上层的用户注册的 irq_handle,和ack等操作。

typedef    void (*irq_flow_handler_t)(unsigned int irq, struct irq_desc *desc);

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
        const char *name, void *dev);

crash> irq_handler_t
typedef enum irqreturn {IRQ_NONE, IRQ_HANDLED, IRQ_WAKE_THREAD} (*)(int, void *)

irq_flow_handle是具体的interrput controller chip相关的
crash> irq_desc | grep handle_irq
    handle_irq = 0xc007cfa0 <handle_bad_irq>,
    handle_irq = 0xc007f9c0 <handle_percpu_devid_irq>,  
    handle_irq = 0xc007fed8 <handle_level_irq>,
    handle_irq = 0xc007fc3c <handle_edge_irq>,
    handle_irq = 0xc00251b0 <tl7689_gpio_irq_dispatch>,
    handle_irq = 0xc007fdb4 <handle_fasteoi_irq>,

kernel/irq/chip.c
handle_xxx_yyy

kernel/irq/handle.c
void handle_bad_irq(unsigned int irq, struct irq_desc *desc)
{
    print_irq_desc(irq, desc);
    kstat_incr_irqs_this_cpu(irq, desc);
    ack_bad_irq(irq);
}