龙芯中断初探(一)
这个很容易理解,中断初始化肯定在内核启动中,因为内核非常需要时钟等中断。那就先从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,那前面的中断号干嘛去了?前面的中断号是故意留下来,具体原因先卖个关子 😉