且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-08-26 09:52:02

宋宝华 Barry Song <21cnbao@gmail.com>

新浪微博: @宋宝华Barry

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以前的体系结构,则是透过MRSMSR指令来读取和设置ARMCPSR寄存器。由此可见,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结构体来描述中断控制器。该结构体内部封装了中断maskunmaskack等成员函数,其定义于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};

我们只实现了其中的ackmaskunmaskset_type成员函数,ack函数用于清中断,maskunmask用于中断屏蔽和取消中断屏蔽、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控制器外围的4GPIO,每组GPIO上又有32个中断(许多芯片的GPIO控制器也同时是一个中断控制器),其关系如下图:

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

那么,一般来讲,在实际操作中,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号中断即可。这个关系图看起来如下:

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

还是以drivers/pinctrl/pinctrl-sirf.cirq_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()填入MACHINEhandle_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



 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/1127017,如需转载请自行联系原作者