龙芯中断初探(一)

这个很容易理解,中断初始化肯定在内核启动中,因为内核非常需要时钟等中断。那就先从start_kernel开始看吧,这个函数执行的功能十分冗杂,现在我们只关注irq相关的部分。差不多翻两页以后,看到下面这个代码块:

 1 if (initcall_debug)
 2            initcall_debug_enable();
 3
 4    context_tracking_init();
 5    /* init some links before init_ISA_irqs() */
 6    early_irq_init();
 7    init_IRQ();
 8    tick_init();
 9    rcu_init_nohz();
10    init_timers();
11    hrtimers_init();
12    softirq_init();
13    timekeeping_init();
14    time_init();
15    printk_safe_init();
16    perf_event_init();
17    profile_init();
18    call_function_init();
19    WARN(!irqs_disabled(), "Interrupts were enabled early\n");
20
21    early_boot_irqs_disabled = false;
22    local_irq_enable();

这是除了start_kenrel一进来就disable_irq之后第一个出现irq字样的代码块了,各种init irq,nice,盘他!先去看看early_init_irq。定义在kernel下。那就是通用的咯,那就应该是初始化前的准备工作,没关系,先进去看看。

 1int __init early_irq_init(void)
 2{
 3    int i, initcnt, node = first_online_node; 
 4    struct irq_desc *desc;
 5    init_irq_default_affinity();
 6    /* Let arch update nr_irqs and return the nr of preallocated irqs */
 7    initcnt = arch_probe_nr_irqs();
 8    printk(KERN_INFO "NR_IRQS: %d, nr_irqs: %d, preallocated irqs: %d\n",
 9           NR_IRQS, nr_irqs, initcnt);
10
11    if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS))
12            nr_irqs = IRQ_BITMAP_BITS;
13
14    if (WARN_ON(initcnt > IRQ_BITMAP_BITS))
15            initcnt = IRQ_BITMAP_BITS;
16
17    if (initcnt > nr_irqs)
18            nr_irqs = initcnt;
19
20    for (i = 0; i < initcnt; i++)
21    {
22            desc = alloc_desc(i, node, 0, NULL, NULL);
23            set_bit(i, allocated_irqs);
24            irq_insert_desc(i, desc);
25    }
26    return arch_early_irq_init();
27}

好,设置irq affinity标志,计算中断数量,分配中断描述符,中断描述符插入DB,然后去arch_early_irq_init,再跳进去看看,恩,kernel下的一个空函数。ok,这时候中断描述符DB已经创建完成了,只是每个desc还是空的。

接下来看init_IRQ——这个看名字就很重要的函数。ctrl+T,arch下的!!开心,一下就过去了。选择arch/mips/下的定义:

 1void __init init_IRQ(void)   
 2{
 3    int i;      
 4    unsigned int order = get_order(IRQ_STACK_SIZE);
 5    for (i = 0; i < NR_IRQS; i++)
 6            irq_set_noprobe(i);
 7    if (cpu_has_veic)
 8            clear_c0_status(ST0_IM);
 9    arch_init_irq();
10    for_each_possible_cpu(i) {
11            void *s = (void *)__get_free_pages(GFP_KERNEL, order);
12            irq_stack[i] = s;
13            pr_debug("CPU%d IRQ stack at 0x%p - 0x%p\n", i,
14                    irq_stack[i], irq_stack[i] + IRQ_STACK_SIZE);
15    }
16}

挨个设置noprobe,判断cpu有没有扩展中断控制模式(external interrupt controller mode, eic),调arch_init_irq,多CPU挨个申请页面作为中断栈。毫无疑问,注册流在arch_init_irq里,就这样一个一个的跳转,最后到这里:

 1void __init mach_init_irq(void)
 2{               
 3    int i;
 4    u64 intenset_addr;
 5    u64 introuter_lpc_addr;
 6    clear_c0_status(ST0_IM | ST0_BEV);
 7    mips_cpu_irq_init();
 8    if (loongson_pch)
 9            loongson_pch->init_irq();
10    /* setup CASCADE irq */
11    setup_irq(LOONGSON_BRIDGE_IRQ, &cascade_irqaction);
12    irq_set_chip_and_handler(LOONGSON_UART_IRQ,
13                    &loongson_irq_chip, handle_level_irq);
14    set_c0_status(STATUSF_IP2 | STATUSF_IP6);
15}

这里可以看到,init是分两个过程的,先是mips架构通用的mips_cpu_irq_init,再是调用桥片自己的init_irq。这就是我一开始说的,注册是分两步完成的。mips_cpu_irq_init函数负责初始cpu直接控制的中断,挨个设置chip和handle_irq.

 1static int mips_cpu_intc_map(struct irq_domain *d, unsigned int irq,
 2                          irq_hw_number_t hw)
 3{
 4    static struct irq_chip *chip;
 5
 6    if (hw < 2 && cpu_has_mipsmt) {
 7            /* Software interrupts are used for MT/CMT IPI */
 8            chip = &mips_mt_cpu_irq_controller;
 9    } else {
10            chip = &mips_cpu_irq_controller;
11    }
12
13    if (cpu_has_vint)
14            set_vi_handler(hw, plat_irq_dispatch);
15
16    irq_set_chip_and_handler(irq, chip, handle_percpu_irq);
17
18    return 0;
19 }

初始化流程还是比较简单,可以看到,在这里给每一个cpu产生的中断设置了chip和handle_irq。CPU检测到中断触发直接调用handle_percpu_irq最后到真正的中断处理函数。接下来看走msi中断的注册,这部分稍微复杂一点:

 1void __init ls7a_init_irq(void)
 2{
 3    writeq(0x0ULL, LS7A_INT_EDGE_REG);
 4    writeq(0x0ULL, LS7A_INT_STATUS_REG);
 5    /* Mask all interrupts except LPC (bit 19) */
 6    writeq(0xfffffffffff7ffffULL, LS7A_INT_MASK_REG);
 7    writeq(0xffffffffffffffffULL, LS7A_INT_CLEAR_REG);
 8
 9    /* Enable the LPC interrupt */
10    writel(0x80000000, LS7A_LPC_INT_CTL);
11    /* Clear all 18-bit interrupt bits */
12    writel(0x3ffff, LS7A_LPC_INT_CLR);
13
14    if (pci_msi_enabled())
15            loongson_pch->irq_dispatch = ls7a_msi_irq_dispatch;
16    ....
17
18    init_7a_irq(LS7A_IOAPIC_LPC_OFFSET          , LS7A_IOAPIC_LPC_IRQ          );
19    init_7a_irq(LS7A_IOAPIC_UART0_OFFSET        , LS7A_IOAPIC_UART0_IRQ        );
20    init_7a_irq(LS7A_IOAPIC_I2C0_OFFSET         , LS7A_IOAPIC_I2C0_IRQ         );
21
22      .........
23    }
24}

设置中断掩码、中断清除等寄存器,设置中断分发函数,调用init_7a_irq为每个外设中断设置handle_irq和chip结构。代码如下:

 1static void init_7a_irq(int dev, int irq) 
 2{
 3    *irq_mask  &= ~(1ULL << dev);
 4    *(volatile unsigned char *)(LS7A_IOAPIC_ROUTE_ENTRY + dev) = USE_7A_INT0;
 5    smp_mb();
 6    irq_set_chip_and_handler(irq, &pch_irq_chip, handle_level_irq);
 7    if(ls3a_msi_enabled) {
 8            *irq_msi_en |= 1ULL << dev;
 9            *(volatile unsigned char *)(irq_msi_vec+dev) = irq;
10            smp_mb();
11    }
12}

这里就是所有中断初始化流程了。看到这里很多人会觉得茫然了,按照上面的流程分析,第一个被初始化到的中断是MIPS_CPU_IRQ_BASE 也就是56,那前面的中断号干嘛去了?前面的中断号是故意留下来,具体原因先卖个关子 😉