宋宝华 Barry Song <[email protected]>
新浪微博: @宋宝华Barry
在Linux内核中,各个设备驱动可以简单地调用request_irq()、enable_irq()、disable_irq()、local_irq_disable()、local_irq_enable()等通用API完成中断申请、使能、禁止等功能。在将Linux移植到新的SoC时,芯片供应商需要提供该部分API的底层支持。
local_irq_disable()、local_irq_enable()的实现与具体中断控制器无关,对于ARMv6以上的体系架构而言,是直接调用CPSID/CPSIE指令进行,而对于ARMv6以前的体系结构,则是透过MRS、MSR指令来读取和设置ARM的CPSR寄存器。由此可见,local_irq_disable()、local_irq_enable()针对的并不是外部的中断控制器,而是直接让CPU本身不响应中断请求。相关的实现位于arch/arm/include/asm/irqflags.h:
11#if __LINUX_ARM_ARCH__ >= 6
12
13static inline unsigned long arch_local_irq_save(void)
14{
15 unsigned long flags;
16
17 asm volatile(
18 " mrs %0, cpsr @ arch_local_irq_save\n"
19 " cpsid i"
20 : "=r" (flags) : : "memory", "cc");
21 return flags;
22}
23
24static inline void arch_local_irq_enable(void)
25{
26 asm volatile(
27 " cpsie i @ arch_local_irq_enable"
28 :
29 :
30 : "memory", "cc");
31}
32
33static inline void arch_local_irq_disable(void)
34{
35 asm volatile(
36 " cpsid i @ arch_local_irq_disable"
37 :
38 :
39 : "memory", "cc");
40}
44#else
45
46/*
47 * Save the current interrupt enable state & disable IRQs
48 */
49static inline unsigned long arch_local_irq_save(void)
50{
51 unsigned long flags, temp;
52
53 asm volatile(
54 " mrs %0, cpsr @ arch_local_irq_save\n"
55 " orr %1, %0, #128\n"
56 " msr cpsr_c, %1"
57 : "=r" (flags), "=r" (temp)
58 :
59 : "memory", "cc");
60 return flags;
61}
62
63/*
64 * Enable IRQs
65 */
66static inline void arch_local_irq_enable(void)
67{
68 unsigned long temp;
69 asm volatile(
70 " mrs %0, cpsr @ arch_local_irq_enable\n"
71 " bic %0, %0, #128\n"
72 " msr cpsr_c, %0"
73 : "=r" (temp)
74 :
75 : "memory", "cc");
76}
77
78/*
79 * Disable IRQs
80 */
81static inline void arch_local_irq_disable(void)
82{
83 unsigned long temp;
84 asm volatile(
85 " mrs %0, cpsr @ arch_local_irq_disable\n"
86 " orr %0, %0, #128\n"
87 " msr cpsr_c, %0"
88 : "=r" (temp)
89 :
90 : "memory", "cc");
91}
92 #endif
与local_irq_disable()和local_irq_enable()不同,disable_irq()、enable_irq()针对的则是外部的中断控制器。在内核中,透过irq_chip结构体来描述中断控制器。该结构体内部封装了中断mask、unmask、ack等成员函数,其定义于include/linux/irq.h:
303struct irq_chip {
304 const char *name;
305 unsigned int (*irq_startup)(struct irq_data *data);
306 void (*irq_shutdown)(struct irq_data *data);
307 void (*irq_enable)(struct irq_data *data);
308 void (*irq_disable)(struct irq_data *data);
309
310 void (*irq_ack)(struct irq_data *data);
311 void (*irq_mask)(struct irq_data *data);
312 void (*irq_mask_ack)(struct irq_data *data);
313 void (*irq_unmask)(struct irq_data *data);
314 void (*irq_eoi)(struct irq_data *data);
315
316 int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
317 int (*irq_retrigger)(struct irq_data *data);
318 int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
319 int (*irq_set_wake)(struct irq_data *data, unsigned int on);
334};
各个芯片公司会将芯片内部的中断控制器实现为irq_chip驱动的形式。受限于中断控制器硬件的能力,这些成员函数并不一定需要全部实现,有时候只需要实现其中的部分函数即可。譬如drivers/pinctrl/pinctrl-sirf.c驱动中的
1438static struct irq_chip sirfsoc_irq_chip = {
1439 .name = "sirf-gpio-irq",
1440 .irq_ack = sirfsoc_gpio_irq_ack,
1441 .irq_mask = sirfsoc_gpio_irq_mask,
1442 .irq_unmask = sirfsoc_gpio_irq_unmask,
1443 .irq_set_type = sirfsoc_gpio_irq_type,
1444};
我们只实现了其中的ack、mask、unmask和set_type成员函数,ack函数用于清中断,mask、unmask用于中断屏蔽和取消中断屏蔽、set_type则用于配置中断的触发方式,如高电平、低电平、上升沿、下降沿等。至于enable_irq()的时候,虽然没有实现irq_enable成员函数,但是内核会间接调用到irq_unmask成员函数,这点从kernel/irq/chip.c可以看出:
192void irq_enable(struct irq_desc *desc)
193{
194 irq_state_clr_disabled(desc);
195 if (desc->irq_data.chip->irq_enable)
196 desc->irq_data.chip->irq_enable(&desc->irq_data);
197 else
198 desc->irq_data.chip->irq_unmask(&desc->irq_data);
199 irq_state_clr_masked(desc);
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->parent_irq,我们透过irq_set_chained_handler()设置了第1级中断发生的时候,会调用链式IRQ处理函数sirfsoc_gpio_handle_irq():
1689 bank->domain = irq_domain_add_legacy(np, SIRFSOC_GPIO_BANK_SIZE,
1690 SIRFSOC_GPIO_IRQ_START + i * SIRFSOC_GPIO_BANK_SIZE, 0,
1691 &sirfsoc_gpio_irq_simple_ops, bank);
1692
1693 if (!bank->domain) {
1694 pr_err("%s: Failed to create irqdomain\n", np->full_name);
1695 err = -ENOSYS;
1696 goto out;
1697 }
1698
1699 irq_set_chained_handler(bank->parent_irq, sirfsoc_gpio_handle_irq);
1700 irq_set_handler_data(bank->parent_irq, bank);
而在sirfsoc_gpio_handle_irq()函数的入口出调用chained_irq_enter()暗示自身进入链式IRQ处理,在函数体内判决具体的GPIO中断,并透过generic_handle_irq()调用到最终的外设驱动中的中断服务程序,最后调用chained_irq_exit()暗示自身退出链式IRQ处理:
1446static void 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中,注册CSR SiRFprimaII内部中断控制器的代码仅为:
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("SIRFINTC", 1, irq_start, base, handle_level_irq);
33 ct = gc->chip_types;
34
35 ct->chip.irq_mask = irq_gc_mask_clr_bit;
36 ct->chip.irq_unmask = irq_gc_mask_set_bit;
37 ct->regs.mask = SIRFSOC_INT_RISC_MASK0;
38
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 = "arm,cortex-a9-gic";
38 #interrupt-cells = <3>;
39 interrupt-controller;
40 reg = <0x10481000 0x1000>, <0x10482000 0x2000>;
41 };
而在arch/arm/mach-exynos/mach-exynos5-dt.c中即含有:
95DT_MACHINE_START(EXYNOS5_DT, "SAMSUNG EXYNOS5 (Flattened Device Tree)")
96 /* Maintainer: Kukjin Kim <[email protected]> */
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