Linux芯片级移植与底层驱动(基于3.7.4内核) --中断控制器

来源:互联网 发布:怎么看待网络流行语 编辑:程序博客网 时间:2024/05/16 18:37

 中断控制器驱动

在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_saven"

 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_saven"

 55                "       orr     %1, %0, #128n"

 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_enablen"

 71                "       bic     %0, %0, #128n"

 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_disablen"

 86                "       orr     %0, %0, #128n"

 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 irqdomainn", 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 <kgene.kim@samsung.com> */

 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

0 0