在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