4.4.2. linux中断初始化和基本调用流程

这里介绍的是按照ARM平台整理的,ARM64暂时未做整理

4.4.2.1. irq_usr

正常情况下有两种情况会进入irq模式,一种是usr模式,另一种是svc模式

__irq_usr:
    usr_entry   //保存中断上下文
    irq_handler //中断处理
    get_thread_info tsk //获取保存当前task的地址
    mov     why, #0
    b       ret_to_user_from_irq
 UNWIND(.fnend              )
ENDPROC(__irq_usr)

__irq_svc:
    svc_entry
    irq_handler

#ifdef CONFIG_PREEMPT
    ldr     r8, [tsk, #TI_PREEMPT]          @ get preempt count
    ldr     r0, [tsk, #TI_FLAGS]            @ get flags
    teq     r8, #0                          @ if preempt count != 0
    movne   r0, #0                          @ force flags to 0
    tst     r0, #_TIF_NEED_RESCHED
    blne    svc_preempt
#endif

    svc_exit r5, irq = 1                    @ return from exception
 UNWIND(.fnend              )
ENDPROC(__irq_svc)
  • irq_handler

/*
 * Interrupt handling.
 */
    .macro  irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
    ldr     r1, =handle_arch_irq    //处理器相关中断处理函数地址
    mov     r0, sp                  //sp栈保存了中断上下文
    badr    lr, 9997f           //把9997标号的地址存入lr,因为irq_handler是一个宏,所以最终的irq_handler的下一条指令地址,即__irq_usr中
                                //的get_thread_info tsk指令
    ldr     pc, [r1]                //跳转到irq中断处理函数
#else
    arch_irq_handler_default
#endif
9997:
    .endm

4.4.2.2. irq_handler

irq_handler的处理有两种配置,一种是配置了CONFIG_MULTI_IRQ_HANDLER,这种情况下linux kernel允许run time设定irq handler,如果我们是一个linux kernel image支持多个平台,这时 就需要配置这个选项。另一种是传统的linux做法,irq_handler实际上就是arch_irq_handler_default

4.4.2.2.1. 第一种方式

第一种方式是直接跳转到handle_arch_irq的地址处执行,而这个地址就是C函数的地址,并且是可以动态设置的,由此就进入C代码阶段进行处理了

void __init setup_arch(char **cmdline_p)
{
    const struct machine_desc *mdesc;

    setup_processor();
    mdesc = setup_machine_fdt(__atags_pointer);
    if (!mdesc)
       mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
    ......
    #ifdef CONFIG_MULTI_IRQ_HANDLER
        handle_arch_irq = mdesc->handle_irq;   /* 初始化阶段设置 */
    #endif
    ......
}



#ifdef CONFIG_MULTI_IRQ_HANDLER
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
    if (handle_arch_irq)
            return;

    handle_arch_irq = handle_irq;        /* 调用函数动态设置 */

}
#endif

4.4.2.2.2. 第二种方式

arch_irq_handler_default在arch/arm/include/asm/entry-macro-multi.S定义

/*
 * 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 *
    @
    badrne  lr, 1b
    bne     asm_do_IRQ

#ifdef CONFIG_SMP
    /*
     * XXX
     *
     * this macro assumes that irqstat (r2) and base (r6) are
     * preserved from get_irqnr_and_base above
     */
    ALT_SMP(test_for_ipi r0, r2, r6, lr)
    ALT_UP_B(9997f)
    movne   r1, sp
    badrne  lr, 1b
    bne     do_IPI
#endif
9997:
    .endm

    .macro  arch_irq_handler, symbol_name
    .align  5
    .global \symbol_name
\symbol_name:
    mov     r8, lr
    arch_irq_handler_default
    ret     r8
    .endm

这种方法是通过asm_do_IRQ函数来进入到C代码中的

//__handle_domain_irq在kernel/kernel/irq/irqdesc.c中实现
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
            bool lookup, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    unsigned int irq = hwirq;
    int ret = 0;

    irq_enter();

#ifdef CONFIG_IRQ_DOMAIN
    if (lookup)
        irq = irq_find_mapping(domain, hwirq);
#endif

    /*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (unlikely(!irq || irq >= nr_irqs)) {
        ack_bad_irq(irq);
        ret = -EINVAL;
    } else {
        generic_handle_irq(irq);    //由此进入通用的中断处理函数
    }

    irq_exit();
    set_irq_regs(old_regs);
    return ret;
}
/*
 * handle_IRQ handles all hardware IRQ's.  Decoded IRQs should
 * not come via this function.  Instead, they should provide their
 * own 'handler'.  Used by platform code implementing C-based 1st
 * level decoding.
 */
void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
    __handle_domain_irq(NULL, irq, false, regs);
}

/*
 * asm_do_IRQ is the interface to be used from assembly code.
 */
asmlinkage void __exception_irq_entry
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
    handle_IRQ(irq, regs);
}

这种方式是比较旧的一种操作,它处理的硬件中断号和IRQ number之间的关系非常简单,但是实际上ARM平台上的硬件中断系统已经越来越复杂了。 需要引入Interrupt controller级联,irq domain等概念

4.4.2.3. handle_arch_irq

这里详细分析一下第一种中断处理方式

start_kernel
...
    local_irq_disable
    ...
    setup_arch(&command_line)
        paging_init
            devicemaps_init
                early_trap_init(vectors)    //设置异常向量表
        ...
        handle_arch_irq = mdesc->handle_irq     //默认handle_irq还是空
    ...
    trap_init
    ...
    early_irq_init  //初始化irq_desc数组
    init_IRQ //芯片相关的中断初始化
    ...
    local_irq_enable

初始化irq_desc数组空间一些通用的参数

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
    [0 ... NR_IRQS-1] = {
        .handle_irq = handle_bad_irq,   //默认每个中断函数都是错误中断需要我们自己实现注册
        .depth              = 1,
        .lock               = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
    }
};

int __init early_irq_init(void)
{
    int count, i, node = first_online_node;
    struct irq_desc *desc;

    init_irq_default_affinity();

    printk(KERN_INFO "NR_IRQS: %d\n", NR_IRQS);

    desc = irq_desc;
    count = ARRAY_SIZE(irq_desc);

    for (i = 0; i < count; i++) {   //遍历整个lookup table,对每个entry进行初始化
        desc[i].kstat_irqs = alloc_percpu(unsigned int);    //分配per cpu的irq统计信息需要的内存
        alloc_masks(&desc[i], node);        //分配中断描述符中需要的cpu mask内存
        raw_spin_lock_init(&desc[i].lock);  //初始化spin lock
        lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
        mutex_init(&desc[i].request_mutex);
        desc_set_defaults(i, &desc[i], node, NULL, NULL);   //设定default值
    }
    return arch_early_irq_init();   //调用arch相关d的初始化函数(arm体系中此函数为空)
}
void __init init_IRQ(void)
{
    int ret;

    if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
        irqchip_init();
    else
        machine_desc->init_irq();   //此处会调用平台相关的中断初始化函数

    if (IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_CACHE_L2X0) &&
        (machine_desc->l2c_aux_mask || machine_desc->l2c_aux_val)) {
        if (!outer_cache.write_sec)
            outer_cache.write_sec = machine_desc->l2c_write_sec;
        ret = l2x0_of_init(machine_desc->l2c_aux_val,
                   machine_desc->l2c_aux_mask);
        if (ret && ret != -ENODEV)
            pr_err("L2C: failed to init: %d\n", ret);
    }

    uniphier_cache_init();
}

关于machine_desc,这个是在启动阶段通过命令行或者设备树传过来的机器码找到对应的machin. 具体可见setup_arch函数中