5. 305 unsigned int (*irq_startup)(structirq_data *data); 6.
7. 306 void (*irq_shutdown)(struct irq_data *data); 8.
9. 307 void (*irq_enable)(struct irq_data *data); 10.
11. 308 void (*irq_disable)(struct irq_data *data); 12. 13. 309 14.
15. 310 void (*irq_ack)(struct irq_data *data); 16.
17. 311 void (*irq_mask)(structirq_data *data); 18.
19. 312 void (*irq_mask_ack)(struct irq_data *data); 20.
21. 313 void (*irq_unmask)(struct irq_data *data); 22.
23. 314 void (*irq_eoi)(struct irq_data *data); 24. 25. 315 26.
27. 316 int (*irq_set_affinity)(struct irq_data *data, conststruct cpumask *dest,bool force); 28.
29. 317 int (*irq_retrigger)(struct irq_data *data); 30.
31. 318 int (*irq_set_type)(struct irq_data *data,unsigned int flow_type); 32.
33. 319 int (*irq_set_wake)(struct irq_data *data, unsigned int on); 34.
35. 334};
各个芯片公司会将芯片内部的中断控制器实现为irq_chip驱动的形式。受限于中断控制器硬件的能力,这些成员函数并不一定需要全部实现,有时候只需要实现其中的部分函数即可。譬如drivers/pinctrl/pinctrl-sirf.c驱动中的
[cpp]view plaincopy
1. 2. 3. 4. 5. 6. 7. 8.
1438staticstruct irq_chip sirfsoc_irq_chip = {
1439 .name = \
1440 .irq_ack = sirfsoc_gpio_irq_ack,
1441 .irq_mask = sirfsoc_gpio_irq_mask,
9. 1442 .irq_unmask = sirfsoc_gpio_irq_unmask, 10.
11. 1443 .irq_set_type = sirfsoc_gpio_irq_type, 12.
13. 1444};
我们只实现了其中的ack、mask、unmask和set_type成员函数,ack函数用于清中断,mask、unmask用于中断屏蔽和取消中断屏蔽、set_type则用于配置中断的触发方式,如高电平、低电平、上升沿、下降沿等。至于enable_irq()的时候,虽然没有实现irq_enable成员函数,但是内核会间接调用到irq_unmask成员函数,这点从kernel/irq/chip.c可以看出:
[cpp]view plaincopy
1. 192voidirq_enable(struct irq_desc *desc) 2. 3. 193{ 4.
5. 194 irq_state_clr_disabled(desc); 6.
7. 195 if (desc- 9. 196 desc- 11. 197 else 12. 13. 198 desc- 15. 199 irq_state_clr_masked(desc); 16. 17. 200} 在芯片内部,中断控制器可能不止1个,多个中断控制器之间还很可能是级联的。举个例子,假设芯片内部有一个中断控制器,支持32个中断源,其中有4个来源于GPIO控制器外围的4组GPIO,每组GPIO上又有32个中断(许多芯片的GPIO控制器也同时是一个中断控制器),其关系如下图: 那么,一般来讲,在实际操作中,gpio0_0——gpio0_31这些引脚本身在第1级会使用中断号28,而这些引脚本身的中断号在实现GPIO控制器对应的irq_chip驱动时,我们又会把它映射到Linux系统的32——63号中断。同理,gpio1_0——gpio1_31这些引脚本身在第1级会使用中断号29,而这些引脚本身的中断号在实现GPIO控制器对应的irq_chip驱动时,我们又会把它映射到Linux系统的64——95号中断,以此类推。对于中断号的使用者而言,无需看到这种2级映射关系。如果某设备想申请gpio1_0这个引脚对应的中断,它只需要申请64号中断即可。这个关系图看起来如下: 还是以drivers/pinctrl/pinctrl-sirf.c的irq_chip部分为例,我们对于每组GPIO都透过irq_domain_add_legacy()添加了相应的irq_domain,每组GPIO的中断号开始于SIRFSOC_GPIO_IRQ_START + i *SIRFSOC_GPIO_BANK_SIZE,而每组GPIO本身占用的第1级中断控制器的中断号则为bank- 1689 bank- 1693 if (!bank- 1694 pr_err(\1695 err = -ENOSYS; 1696 goto out; 1697 } 1698 1699 irq_set_chained_handler(bank- 而在sirfsoc_gpio_handle_irq()函数的入口出调用chained_irq_enter()暗示自身进入链式IRQ处理,在函数体内判决具体的GPIO中断,并透过generic_handle_irq()调用到最终的外设驱动中的中断服务程序,最后调用chained_irq_exit()暗示自身退出链式IRQ处理: 1446staticvoid sirfsoc_gpio_handle_irq(unsigned int irq, struct irq_desc *desc) 1447{ 1448 … 1454 chained_irq_enter(chip, desc); 1456 … 1477 generic_handle_irq(first_irq + idx); 1478 … 1484 chained_irq_exit(chip, desc); 1485} 很多中断控制器的寄存器定义呈现出简单的规律,如有一个mask寄存器,其中每1位可屏蔽1个中断等,这种情况下,我们无需实现1个完整的irq_chip驱动,可以使用内核提供的通用irq_chip驱动架构irq_chip_generic,这样只需要实现极少量的代码,如arch/arm/mach-prima2/irq.c中,注册CSRSiRFprimaII内部中断控制器的代码仅为: 26static __init void 27sirfsoc_alloc_gc(void __iomem *base,unsigned int irq_start, unsigned int num) 28{ 29 struct irq_chip_generic *gc; 30 struct irq_chip_type *ct; 31 32 gc = irq_alloc_generic_chip(\ 33 ct = gc- 35 ct- 39 irq_setup_generic_chip(gc, IRQ_MSK(num), IRQ_GC_INIT_MASK_CACHE,IRQ_NOREQUEST, 0); 40} 特别值得一提的是,目前多数主流ARM芯片,内部的一级中断控制器都使用了ARM公司的GIC,我们几乎不需要实现任何代码,只需要在Device Tree中添加相关的结点并将gic_handle_irq()填入MACHINE的handle_irq成员。 如在arch/arm/boot/dts/exynos5250.dtsi即含有: 36 gic:interrupt-controller@10481000 { 37 compatible = \38 #interrupt-cells = <3>; 39 interrupt-controller; 40 reg = <0x104810000x1000>, <0x10482000 x>; 41 }; 而在arch/arm/mach-exynos/mach-exynos5-dt.c中即含有: 95DT_MACHINE_START(EXYNOS5_DT, \96 /* Maintainer: Kukjin Kim */ 97 .init_irq =exynos5_init_irq, 98 .smp =smp_ops(exynos_smp_ops), 99 .map_io = exynos5250_dt_map_io, 100 .handle_irq = gic_handle_irq, 101 .init_machine =exynos5250_dt_machine_init, 102 .init_late =exynos_init_late, 103 .timer =&exynos4_timer, 104 .dt_compat =exynos5250_dt_compat, 105 .restart = exynos5_restart, 106MACHINE_END 4. SMP多核启动以及CPU热插拔驱动 在Linux系统中,对于多核的ARM芯片而言,Bootrom代码中,CPU0会率先起来,引导Bootloader和Linux内核执行,而其他的核则在上电时Bootrom一般将自身置于WFI或者WFE状态,并等待CPU0给其发CPU核间中断(IPI)或事件(一般透过SEV指令)唤醒之。一个典型的启动过程如下图: