歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

Linux芯片級移植與底層驅動(基於3.7.4內核) 中斷控制器

3.中斷控制器驅動

在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

Copyright © Linux教程網 All Rights Reserved