Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1942369
  • 博文数量: 185
  • 博客积分: 10707
  • 博客等级: 上将
  • 技术积分: 1777
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-19 17:31
文章分类

全部博文(185)

文章存档

2014年(1)

2012年(6)

2011年(27)

2010年(13)

2009年(75)

2008年(63)

分类: LINUX

2008-12-18 20:44:57

--------------------------------------------
本文系本站原创,欢迎转载!
转载请注明出处:http://zhiqiang0071.cublog.cn
--------------------------------------------



本文分析了DM644x平台arm926ejs中断的流程,参考了网上一些分析arm中断流程的文章。

1.arm硬件中断向量表建立及中断响应都在linux/arch/arm/kernel/entry-armv.S中,故从该文件
开始分析。
linux/arch/arm/kernel/entry-armv.S:
__stubs_end:

        .equ    __real_stubs_start, .LCvectors + 0x200

.LCvectors:    
        swi    SYS_ERROR0
        b    __real_stubs_start + (vector_und - __stubs_start)
        ldr    pc, __real_stubs_start + (.LCvswi - __stubs_start)
        b    __real_stubs_start + (vector_pabt - __stubs_start)
        b    __real_stubs_start + (vector_dabt - __stubs_start)
        b    __real_stubs_start + (vector_addrexcptn - __stubs_start)
        b    __real_stubs_start + (vector_irq - __stubs_start)
        b    __real_stubs_start + (vector_fiq - __stubs_start)

ENTRY(__trap_init)
        stmfd     {r4 - r6, lr}

        mov    r0, #0xff000000
        orr    r0, r0, #0x00ff0000        @ high vectors position
        adr    r1, .LCvectors            @ set up the vectors
        ldmia    r1, {r1, r2, r3, r4, r5, r6, ip, lr}
        stmia    r0, {r1, r2, r3, r4, r5, r6, ip, lr}

        add    r2, r0, #0x200
        adr    r1, __stubs_start        @ copy stubs to 0x200
        adr    r4, __stubs_end
1:        ldr    r3, [r1], #4
        str    r3, [r2], #4
        cmp    r1, r4
        blo    1b

        add    r2, r0, #0x1000            @ top of high vector page
        adr    r4, __kuser_helper_end        @ user helpers to top of page
        adr    r1, __kuser_helper_start    @ going downwards.
1:        ldr    r3, [r4, #-4]!
        str    r3, [r2, #-4]!
        cmp    r4, r1
        bhi    1b

        LOADREGS(fd, {r4 - r6, pc})
        
内核解压启动完成一些初始化后,执行init/main.c中的start_kernel()函数,然后到过程便是,
start_kernel()-->setup_arch()-->early_trap_init()-->__trap_init()
__trap_init()例程首先將7个异常向量从.LCvectors处拷贝到0xffff0000(arm的高向量地址)
然后再把__stubs_start到__stubs_end之间到代码也就是具体的异常处理程序拷贝到相对0xffff0000
偏移0x200处.

现在假设在usr mode发生一个irq,arm硬件将保存当前的pc到lr_irq和当前状态寄存器cpsr到
spsr_irq,经0xffff0018处的中断异常向量跳转至vector_IRQ处的处理程序:
    .macro    vector_stub, name, sym, correction=0
    .align    5

vector_\name:
    ldr    r13, .LCs\sym
    .if \correction
    sub    lr, lr, #\correction
    .endif
    str    lr, [r13]            @ save lr_IRQ
    mrs    lr, spsr
    str    lr, [r13, #4]            @ save spsr_IRQ
    @
    @ now branch to the relevant MODE handling routine
    @
    mrs    r13, cpsr
    bic    r13, r13, #MODE_MASK
    orr    r13, r13, #MODE_SVC
    msr    spsr_cxsf, r13            @ switch to SVC_32 mode

    /*
     lr&15的值是表明发生irq之前cpu所在的空间,
     0:在用户空间发生了irq中断
     3:在内核空间发生了irq中断
     lr<<2=lr*4也就是pc+0和pc+12
     分别对应LCtab_irq的.word __irq_usr域和.word __irq_svc域
     */

    and    lr, lr, #15
    ldr    lr, [pc, lr, lsl #2]
    movs    pc, lr                @ Changes mode and branches
    .endm

__stubs_start:
/*
 * Interrupt dispatcher
 */

    vector_stub    irq, irq, 4        // 这就是vector_irq


    .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
这段程序在前面已经被拷贝到0xffff0000+0x200后面的地方.这段代码运行时cpu为irq mode,
最后movs    pc, lr一句根据cpu被中断时到运行模式跳转入__irq_usr的同时cpu还跳转到到svc mode模式,
所以先临时保存影子寄存器lr_IRQ和spsr_IRQ,也就是pc_usr和cpsr_usr。
从上面的程序可以看出,除了usr和svc mode外,在其他运行模式下发生的irq无效,即不处理.

__irq_usr:    
        sub    sp, sp, #S_FRAME_SIZE
        stmia    sp, {r0 - r12}            @ save r0 - r12
        ldr    r4, .LCirq
        add    r8, sp, #S_PC
        ldmia    r4, {r5 - r7}            @ get saved PC, SPSR
#if __LINUX_ARM_ARCH__ < 6
        @ make sure our user space atomic helper is aborted
        cmp    r5, #VIRT_OFFSET
        bichs    r6, r6, #PSR_Z_BIT
#endif
        stmia    r8, {r5 - r7}            @ save pc, psr, old_r0
        stmdb    r8, {sp, lr}^
        alignment_trap r4, r7, __temp_irq
        zero_fp
#ifdef CONFIG_PREEMPT
        get_thread_info r8
        ldr    r9, [r8, #TI_PREEMPT]        @ get preempt count
        // 增加抢占计数器的值,preempt_count为正数时,禁止抢占

        add    r7, r9, #1            @ increment it
        str    r7, [r8, #TI_PREEMPT]
#endif
1:        get_irqnr_and_base r0, r6, r5, lr
        movne    r1, sp
        adrsvc    ne, lr, 1b
        @
        @ routine called with r0 = irq number, r1 = struct pt_regs *
        @
        bne    asm_do_IRQ
#ifdef CONFIG_PREEMPT
        ldr    r0, [r8, #TI_PREEMPT]
        teq    r0, r7
        str    r9, [r8, #TI_PREEMPT]    // 恢复原值

        strne    r0, [r0, -r0]
        mov    tsk, r8
#else
        get_thread_info tsk
#endif
        mov    why, #0
        b    ret_to_user
        
dm644x arm cpu中的硬件中断靠中断请求寄存器的位来区分,但由于其有跳转表寄存器(IRQENTRY),
故通过(IRQENTRY/4) - 1获得中断号,get_irqnr_and_base宏代码就是实现这个功能。中断号保存到
r0,sp保存到r1,r0与r1随后作为参数传送给asm_do_IRQ()例程,其中sp已经被压入了中断上现场的各个
寄存器值,也就是pt_reg结构:

.macro    get_irqnr_and_base, irqnr, irqstat, base, tmp
    /* GIVEN:
     * EABASE = 0 ... so IRQNR = (IRQENTRY/4) - 1
      * RETURN:
     * irqnr: Interrupt number. Zero corresponds
     * to bit 0 of the status register
     * irqstat, base, and tmp may be considered
     * as scratch registers
     * Z conditions means no outstanding interrupt
     */

    ldr \base, =IO_ADDRESS(DAVINCI_ARM_INTC_BASE)
    ldr \tmp, [\base, #0x14]
    mov \tmp, \tmp, lsr #2
    sub \irqnr, \tmp, #1
    cmp \tmp, #0    // 用于判断是否真的有中断发生

.endm

struct pt_regs {
long uregs[17];
};
#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[16] -----除了SWI调用,其值一直是-1。

值得注意一下的是adrsvc ne, lr, 1b一句,通过设置asm_do_IRQ()的返回地址lr,实现了对中断的
串行处理。

2.进入asm_do_IRQ()例程。
linux/arch/arm/kernel/irq.c:

asmlinkage notrace void
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    struct irqdesc *desc = irq_desc + irq;

    /*
     如果打开了追踪内核事件的开关,则
     记录硬中断事件,!(user_mode(regs))用于判断是否在内核中。
    */

    ltt_ev_irq_entry(irq, !(user_mode(regs)));    

    /*
     * Some hardware gives randomly wrong interrupts. Rather
     * than crashing, do something sensible.
     */

    if (irq >= NR_IRQS)
        desc = &bad_irq_desc;
        
    interrupt_overhead_start();
    
    // 增加抢占计数器的值

    irq_enter();

    spin_lock(&irq_controller_lock);
    
    /*
     该handle在arch/arm/mach-davinci/irq.c中注册,在dm644x平台中为do_edge_IRQ()或
     do_level_IRQ(),这两个例程中会调用用户注册的中断例程。
    */

    desc->handle(irq, desc, regs);

    /*
     * Now re-run any pending interrupts.
     */

     // 检查是否有的等待处理到中断请求

    if (!list_empty(&irq_pending))
        do_pending_irqs(regs);    // 处理延后的中断请求


    spin_unlock(&irq_controller_lock);
    
    // 退出中断上下文并检查是否有软中断发生

    irq_exit();
    
    /*
     检查延迟中断并记录
     */

    latency_check();
    // 记录irq进入事件,其实在该平台什么也没做。

    ltt_ev_irq_exit();
}

在上面的asm_do_IRQ例程中,调用的desc->handle(irq, desc, regs)在dm644x平台中有两种类型,
do_edge_IRQ()和do_level_IRQ()。下面的分析都是基于asm_do_IRQ例程中的代码。

3.进入desc->handle(),这里假设指向do_edge_IRQ()例程:
linux/arch/arm/kernel/irq.c:

/*
 * Most edge-triggered IRQ implementations seem to take a broken
 * approach to this. Hence the complexity.
 */

void
do_edge_IRQ(unsigned int irq, struct irqdesc *desc, struct pt_regs *regs)
{
    const unsigned int cpu = smp_processor_id();

    desc->triggered = 1;    

    /*
     * If we're currently running this IRQ, or its disabled,
     * we shouldn't process the IRQ. Instead, turn on the
     * hardware masks.
    */

    /*
     desc->disable_depth为正数时,表明中断被禁止,操作该变量的例程是disable_irq()和
     enable_irq(),前者增加disable_depth的值,后者减少disable_depth的值。
    */

    if (unlikely(desc->running || desc->disable_depth))
        goto running;
    
    /*
     * Acknowledge and clear the IRQ, but don't mask it.
     */

    // 该ack()在arch/arm/mach-davinci/irq.c中注册,用来清除中断请求寄存器的相应位。

    desc->chip->ack(irq);

    /*
     * Mark the IRQ currently in progress.
     */

    desc->running = 1;    // 中断正在被处理


    kstat_cpu(cpu).irqs[irq]++;    //统计irq线发生中断的次数


#ifdef CONFIG_PREEMPT_HARDIRQS
    if (desc->action) desc->status |= IRQ_INPROGRESS;
    if (redirect_hardirq(desc))
        return;
#endif

    do {
        struct irqaction *action;

        action = desc->action;
        if (!action)    // 检查用户是否注册了中断例程

            break;

        /*
         如果允许打开中断,则调用unmask()例程打开中断(向中断使能寄存器写1),
         unmask()例程在arch/arm/mach-davinci/irq.c中注册。
        */

        if (desc->pending && !desc->disable_depth) {
            desc->pending = 0;
            desc->chip->unmask(irq);
        }

        // 执行用户注册的中断例程,其包含在action结构体的handler中

        __do_irq(irq, action, regs);    
    } while (desc->pending && !desc->disable_depth);//如果又有中断发生,继续执行


    desc->status &= ~IRQ_INPROGRESS;
    desc->running = 0;

    /*
     * If we were disabled or freed, shut down the handler.
     */

    if (likely(desc->action && !check_irq_lock(desc, irq, regs)))
        return;

 running:
    /*
     * We got another IRQ while this one was masked or
     * currently running. Delay it.
     */

    desc->pending = 1;
    desc->status |= IRQ_PENDING;
    desc->chip->mask(irq);
    desc->chip->ack(irq);
}

4.进入__do_irq例程,其运行用户注册的中断例程.
linux/arch/arm/kernel/irq.c:

static int
__do_irq(unsigned int irq, struct irqaction *action, struct pt_regs *regs)
{
    unsigned int status;
    int ret, retval = 0;

    spin_unlock(&irq_controller_lock);
    
    // 如果硬中断没有嵌套且该中断不是快速中断的话则打开中断,以允许中断嵌套

    if (!hardirq_count() || !(action->flags & SA_INTERRUPT))
        local_irq_enable();
    interrupt_overhead_stop();

    status = 0;
    
    // 执行用户注册到中断例程,一个中断线可以被共享,也就是可以注册多个用户中断例程

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

    if (status & SA_SAMPLE_RANDOM)
        add_interrupt_randomness(irq);

#ifdef CONFIG_NO_IDLE_HZ
    if (timer_update.function && irq != timer_update.skip)
        timer_update.function(irq, 0, regs);
#endif

    spin_lock_irq(&irq_controller_lock);

    return retval;
}

5.进入do_pending_irqs()例程,处理延后的中断请求。
linux/arch/arm/kernel/irq.c:

static void do_pending_irqs(struct pt_regs *regs)
{
    struct list_head head, *l, *n;

    do {
        struct irqdesc *desc;

        /*
         * First, take the pending interrupts off the list.
         * The act of calling the handlers may add some IRQs
         * back onto the list.
         */

        head = irq_pending;        // 获取延后中断列表

        INIT_LIST_HEAD(&irq_pending);    // 清空延后中断列表

        head.next->prev = &head;
        head.prev->next = &head;

        /*
         * Now run each entry. We must delete it from our
         * list before calling the handler.
         */

         // 获取所有延后的中断描述符结构体指针,并调用中断处理例程desc->handle()

        list_for_each_safe(l, n, &head) {
            desc = list_entry(l, struct irqdesc, pend);
            list_del_init(&desc->pend);
            desc->handle(desc - irq_desc, desc, regs);
        }

        /*
         * The list must be empty.
         */

        BUG_ON(!list_empty(&head));
    } while (!list_empty(&irq_pending));// 该例程在运行的时候可能又产生了新的延后中断

}

6.进入irq_exit()例程,该例程会检查是否有软中断发生。
linux/arch/arm/kernel/irq.c:

// kernel/softirq.c

void irq_exit(void)
{
    // 减少硬中断计数器的值

    sub_preempt_count(IRQ_EXIT_OFFSET);
    // 判断是否在硬中断或软中断上下文中,是否有软中断发生

    if (!in_interrupt() && local_softirq_pending())
        invoke_softirq();    // 处理软中断

    // 减少抢占计数器的值

    preempt_enable_no_resched();
}

因为软中断是穿串行处理的,所以不允许发生在软中断上下文中。

7.进入软中断处理例程__do_softirq(),其其实就是invoke_softirq()#define定义:
linux/kernel/softirq.c:

# define invoke_softirq()    __do_softirq()

/*
 * We restart softirq processing MAX_SOFTIRQ_RESTART times,
 * and we fall back to softirqd after that.
 *
 * This number has been established via experimentation.
 * The two things to balance is latency against fairness -
 * we want to handle softirqs as soon as possible, but they
 * should not be able to lock up the box.
 */

#define MAX_SOFTIRQ_RESTART 10
asmlinkage void ___do_softirq(void)
{
    struct softirq_action *h;
    __u32 pending;
    int max_restart = MAX_SOFTIRQ_RESTART;
    int cpu;

    // 把本地cpu软中断的位掩码复制到局部变量pending中

    pending = local_softirq_pending();

    cpu = smp_processor_id();
restart:
    /* Reset the pending bitmask before enabling irqs */
    local_softirq_pending() = 0;

    local_irq_enable();    // 开中断,允许硬中断


    h = softirq_vec;    // 获取软中断结构体数组


    do {
        if (pending & 1) {    // 数组下标越小,软中断优先级越高

            {
                u32 preempt_count = preempt_count();
                // 如果打开了追踪内核事件的开关,记录软中断事件

                ltt_ev_soft_irq(LTT_EV_SOFT_IRQ_SOFT_IRQ, (h - softirq_vec));
                // 执行注册的软中断

                h->action(h);
                if (preempt_count != preempt_count()) {
                    print_symbol("softirq preempt bug: exited %s with wrong
                                 preemption count!\n"
, (unsigned long) h->action);
                    printk("entered with %08x, exited with %08x.\n",
                            preempt_count, preempt_count());
                    preempt_count() = preempt_count;
                }
            }
            rcu_bh_qsctr_inc(cpu);
            cond_resched_all();    // 检查是否需要调度程序

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

    local_irq_disable();

    pending = local_softirq_pending();
    /*
     如果pending不为0,且循环没有达到指定到10次,则继续执行软中断
    */

    if (pending && --max_restart)    
        goto restart;

    if (pending)    // 如果循环超过了10次,则唤醒内核线程wakeup_softirqd()延后执行

        wakeup_softirqd();
}

从asm_do_IRQ()中返回后再用get_irqnr_and_base宏检查是否又有新的中断发生(这就是前面讲到串行
处理),否則在get_thread_info tsk宏中通过sp得到task_struct的地址,并跳到
ret_to_user:
    get_thread_info tsk
    mov    why, #0
    b    ret_to_user

从以上能看出,linux系统的中断处理过程比较完善,对于一个操作系统来说是比较适合的。但是就是因为
完善,所以整个中断过程比较长,耗费的时间比裸跑的中断多很多,正所谓有利有弊,各取所需。

阅读(2078) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~