说明:linux内核版本:linux-3.4
arch/arm/kernel/entry-armv.s中:
- __irq_usr:
- /*下面两个宏分别进行栈的准备*/
- usr_entry
- kuser_cmpxchg_check
-
- irq_handler /*进一步处理中断*/
- get_thread_info tsk
- mov why, #0
- b ret_to_user_from_irq
- UNWIND(.fnend )
- ENDPROC(__irq_usr)
- ....
- __irq_svc:
- svc_entry /*进行栈准备*/
- irq_handler /*进一步处理中断*/
汇编代码会根据中断发生前的模式最终调用__irq_usr 或 __irq_svc;分别对应了中断是打断了应用层的代码还是内核层的代码;
上面知道了调用了irq_handle; 而irq_handle与handle_arch_irq相关联;
- /*arch\arm\include\asm\mach/irq.h中定义函数指针*/
- #ifdef CONFIG_MULTI_IRQ_HANDLER
- extern void (*handle_arch_irq)(struct pt_regs *);
- #endif
-
- /*在arch\arm\kernel\setup.c中有定义*/
- setup_arch
- {
- mdesc = setup_machine_fdt(__atags_pointer); ->调用了
- for_each_machine_desc(mdesc)
-
- #ifdef CONFIG_MULTI_IRQ_HANDLER
- handle_arch_irq = mdesc->handle_irq;
- #endif
- }
而for_each_machine_desc函数定义在arch\arm\include\asm\mach\arch.h中;内容如下:
- extern struct machine_desc __arch_info_begin[], __arch_info_end[];
- #define for_each_machine_desc(p) \
- for (p = __arch_info_begin; p < __arch_info_end; p++)
-
- /*
- * Set of macros to define architecture features. This is built into
- * a table by the linker.
- 用于定义体系结构特征的宏集。链接器将其内置到表中。
- */
- #define MACHINE_START(_type,_name) \
- static const struct machine_desc __mach_desc_##_type \
- __used \
- __attribute__((__section__(".arch.info.init"))) = { \
- .nr = MACH_TYPE_##_type, \
- .name = _name,
-
- #define MACHINE_END \
- };
其中的MACHINE_START宏很关键;不同开发板通过该宏将对应的handle_irq赋值后添加到表中;通过for_each_machine_desc查找后返回,并最终回调给handle_irq赋值的函数;
如在arch\arm\mach-hi3516a\core.c中:
- MACHINE_START(HI3516A, "hi3516a")
- .atag_offset = 0x100,
- .map_io = hi3516a_map_io,
- .init_early = hi3516a_init_early,
- .init_irq = hi3516a_gic_init_irq,
- .handle_irq = gic_handle_irq,
- .timer = &hi3516a_sys_timer,
- .init_machine = hi3516a_init,
- .reserve = hi3516a_reserve,
- .restart = hi3516a_restart,
- MACHINE_END
handle_irq被赋值为gic_handle_irq;
又列如在arch\arm\mach-hi3518ev200中,被赋值为vic_handle_irq;GIC与VIC的区别自行百度;
- MACHINE_START(HI3518EV200, "hi3518ev200")
- .atag_offset = 0x100,
- .map_io = hi3518ev200_map_io,
- .init_early = hi3518ev200_init_early,
- .init_irq = hi3518ev200_vic_init_irq,
- .handle_irq = vic_handle_irq,
- .timer = &hi3518ev200_sys_timer,
- .init_machine = hi3518ev200_init,
- .reserve = hi3518ev200_reserve,
- .restart = hi3518ev200_restart,
- MACHINE_END
以通用的gic_handle_irq函数分析: 源码如下:
- /*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 {
- //得到硬件中断线的IRQ号
- irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
- irqnr = irqstat & ~0x1c00;
-
- if (likely(irqnr > 15 && irqnr < 1021)) {
- //转换为内核内部的IRQ号;
- 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);
- }
handle_IRQ函数中调用了irq_enter()函数对内核抢占计数器值进行了操作;然后又调用了generic_handle_irq函数;
- // 在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);
- }
函数generic_handle_irq中,有一个结构体类型struct irq_desc 是对系统中的单个中断的抽象;系统有多少个中断源就应该有多少个这样的对象;这些对象可能存在数组中或基数树中,并且是在内核启动过程中构建好的;
- 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;
- }
generic_handle_irq_desc函数中继续调用desc->handle_irq(irq, desc);
handle_irq函数根据中断的触发类型是电平还是边沿,对应调用kernel\irq\chip.c中的函数handle_level_irq 或 handle_edge_irq 函数;
这两个函数都调用了 handle_irq_event(desc); 在该函数中又调用了kernel\irq\handle.c下的 handle_irq_event_percpu(desc, action); 函数,通过do while循环遍历链表中的action, 并调用action->handler(irq, action->dev_id); 而action中的handler所指向的函数指针指向驱动开发者编写的中断处理函数的指针;
其中的action成员为struct irqaction结构类型,定义如下:
- struct irqaction {
- irq_handler_t handler; //指向驱动开发者编写的中断处理函数的指针;
- unsigned long flags;//IRQF_*开头的一组标志;
- void *dev_id; //区别共享中断中的不同设备的ID
- void __percpu *percpu_dev_id;
- struct irqaction *next;//将共享同一IRQ号的struct irqaction对象链接在一起的指针;
- int irq; //中断号
- irq_handler_t thread_fn;//线程中断的中断能处理程序函数;
- struct task_struct *thread;
- unsigned long thread_flags;
- unsigned long thread_mask;
- const char *name;//设备名
- struct proc_dir_entry *dir;
- } ____cacheline_internodealigned_in_smp;
共享中断: 由于中断控制器管脚有限,某些体系结构上有两个或两个以上的设备被接到同一根中断线上,这样这两个设备中的任何一个设备产生了中断;都会触发;
linux内核不能判断新产生的中断属于哪个设备,于是将一根中断线上的中断处理函数通过不同的struct irqaction结构体包装起来,然后通过链表连接;内核得到IRQ号后,就遍历该链表上的每一个struct irqaction结构体对象,找到后调用其handle成员所指向的中断处理函数;