中断控制器(中断控制器)
在Linux内核中 ,各个设备驱动可以简单地调用request_irq() 、enable_irq() 、disable_irq()、
local_irq_disable() 、local_irq_enable()等通用API来完成中断申请 、使能 、禁止等功能 。local_irq_disable() 、local_irq_enable()的实现与具体中断控制器无关 ,对于ARM v6以上的体系结
构而言 ,是直接调用CPSID/CPSIE指令进行 ,而对于ARM v6以前的体系结构 ,则是通过MRS 、MSR指令
来读取和设置ARM的CPSR寄存器 。由此可见 ,local_irq_disable() 、local_irq_enable()针对的并不是
外部的中断控制器 ,而是直接让CPU本身不响应中断请求 。相关的实现位于arch/arm/include/asm/irqflags.h
中 1#if __LINUX_ARM_ARCH__ >= 6 2 3static inline unsigned long arch_local_irq_save(void) 4{ 5 unsigned long flags; 6 7 asm volatile( 8 " mrs %0, cpsr @ arch_local_irq_save\n" 9 " cpsid i" 10 : "=r" (flags) : : "memory", "cc"); 11 return flags; 12} 13 14static inline void arch_local_irq_enable(void) 15{ 16 asm volatile( 17 " cpsie i @ arch_local_irq_enable" 18 : 19 : 20 : "memory", "cc"); 21} 22 23static inline void arch_local_irq_disable(void) 24{ 25 asm volatile( 26 " cpsid i @ arch_local_irq_disable" 27 : 28 : 29 : "memory", "cc"); 30} 31#else 32 33/* 34 * Save the current interrupt enable state & disable IRQs 35 */ 36static inline unsigned long arch_local_irq_save(void) 37{ 38 unsigned long flags, temp; 39 40 asm volatile( 41 " mrs %0, cpsr @ arch_local_irq_save\n" 42 " orr %1, %0, #128\n" 43 " msr cpsr_c, %1" 44 : "=r" (flags), "=r" (temp) 45 : 46 : "memory", "cc"); 47 return flags; 48} 49 50/* 51 * Enable IRQs 52 */ 53static inline void arch_local_irq_enable(void) 54{ 55 unsigned long temp; 56 asm volatile( 57 " mrs %0, cpsr @ arch_local_irq_enable\n" 58 " bic %0, %0, #128\n" 59 " msr cpsr_c, %0" 60 : "=r" (temp) 61 : 62 : "memory", "cc"); 63} 64 65/* 66 * Disable IRQs 67 */ 68static inline void arch_local_irq_disable(void) 69{ 70 unsigned long temp; 71 asm volatile( 72 " mrs %0, cpsr @ arch_local_irq_disable\n" 73 " orr %0, %0, #128\n" 74 " msr cpsr_c, %0" 75 : "=r" (temp) 76 : 77 : "memory", "cc"); 78} 79 #endif与local_irq_disable()和local_irq_enable()不同 ,disable_irq() 、enable_irq()针对的则是中断
控制器 ,因此它们适用的对象是某个中断 。disable_irq()的字面意思是暂时屏蔽掉某中断(其实在内核
的实现层面上做了延后屏蔽),直到enable_irq()后再执行ISR 。实际上 ,屏蔽中断可以发生在外设 、中
断控制器 、CPU三个位置 ,如图1所示 。对于外设端,是从源头上就不产生中断信号给中断控制器 ,由
于它高度依赖于外设于本身 ,所以Linux不提供标准的API而是由外设的驱动直接读写自身的寄存器 。
在内核中,通过irq_chip结构体来描述中断控制器 。该结构体内部封装了中断mask、unmask 、ack等成
员函数 ,其定义于include/linux/irq.h中 ,如代码清单4所示 。 1struct irq_chip { 2 const char *name; 3 unsigned int (*irq_startup)(struct irq_data *data); 4 void (*irq_shutdown)(struct irq_data *data); 5 void (*irq_enable)(struct irq_data *data); 6 void (*irq_disable)(struct irq_data *data); 7 8 void (*irq_ack)(struct irq_data *data); 9 void (*irq_mask)(struct irq_data *data); 10 void (*irq_mask_ack)(struct irq_data *data); 11 void (*irq_unmask)(struct irq_data *data); 12 void (*irq_eoi)(struct irq_data *data); 13 14 int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force); 15 int (*irq_retrigger)(struct irq_data *data); 16 int (*irq_set_type)(struct irq_data *data, unsigned int flow_type); 17 int (*irq_set_wake)(struct irq_data *data, unsigned int on); 18};各个芯片公司会将芯片内部的中断控制器实现为irq_chip驱动的形式。受限于中断控制器硬件的能
力 ,这些成员函数并不一定需要全部实现 ,有时候只需要实现其中的部分函数即可 。譬如
drivers/pinctrl/sirf/pinctrl-sirf.c驱动中的下面代码部分: static struct irq_chip sirfsoc_irq_chip = { .name = "sirf-gpio-irq", .irq_ack = sirfsoc_gpio_irq_ack, .irq_mask = sirfsoc_gpio_irq_mask, .irq_unmask = sirfsoc_gpio_irq_unmask, .irq_set_type = sirfsoc_gpio_irq_type, };我们只实现了其中的ack 、mask、unmask和set_type成员函数 ,ack函数用于清中断 ,mask 、unmask用
于中断屏蔽和取消中断屏蔽 、set_type则用于配置中断的触发方式 ,如高电平、低电平 、上升沿 、下降沿
等 。至于到enable_irq()的时候 ,虽然没有实现irq_enable()成员函数 ,但是内核会间接调用
irq_unmask()成员函数,这点从kernel/irq/chip.c中可以看出: void irq_enable(struct irq_desc *desc) { irq_state_clr_disabled(desc); if (desc->irq_data.chip->irq_enable) desc->irq_data.chip->irq_enable(&desc->irq_data); else desc->irq_data.chip->irq_unmask(&desc->irq_data); irq_state_clr_masked(desc); }在芯片内部 ,中断控制器可能不止1个 ,多个中断控制器之间还很可能是级联的。举个例子,假设芯
片内部有一个中断控制器 ,支持32个中断源 ,其中有4个来源于GPIO控制器外围的4组GPIO,每组GPIO
上又有32个中断(许多芯片的GPIO控制器也同时是一个中断控制器) ,其关系如图4所示 。图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号中断即可 。这个关系图看起来如图5所示 。
图5 中断级联与映射要特别注意的是,上述图4和5中所涉及的中断号的数值 ,无论是base还是具体某个GPIO对应的
中断号是多少 ,都不一定是如图20.4和图20.5所描述的简单线性映射 。Linux使用IRQ Domain来描述一个
中断控制器所管理的中断源 。换句话说,每个中断控制器都有自己的Domain 。我们可以将IRQ Domain看
作是IRQ控制器的软件抽象 。在添加IRQ Domain的时候 ,内核中存在的映射方法有:
irq_domain_add_legacy() 、irq_domain_add_linear() 、irq_domain_add_tree()等 。irq_domain_add_legacy()实际上是一种过时的方法 ,它一般是由IRQ控制器驱动直接指定中断源硬
件意义上的偏移(一般称为hwirq)和Linux逻辑上的中断号的映射关系 。类似图20.5的指定映射可以被这
种方法弄出来。irq_domain_add_linear()则在中断源和irq_desc之间建立线性映射 ,内核针对这个IRQ
Domain维护了一个hwirq和Linux逻辑IRQ之间关系的一个表 ,这个时候我们其实也完全不关心逻辑中断号
了;irq_domain_add_tree()则更加灵活 ,逻辑中断号和hwirq之间的映射关系是用一棵radix树来描述的 ,
我们需要通过查找的方法来寻找hwirq和Linux逻辑IRQ之间的关系 ,一般适合某中断控制器支持非常多中
断源的情况 。实际上 ,在当前的内核中 ,中断号更多的是一个逻辑概念,具体数值是多少不是很关键 。人们更多的
是关心在设备树中设置正确的interrupt_parrent和相对该interrupt_parent的偏移。
以drivers/pinctrl/sirf/pinctrl-sirf.c的irq_chip部分为例 ,在sirfsoc_gpio_probe()函数中 ,每组GPIO的中
断都通过gpiochip_set_chained_irqchip()级联到上一级中断控制器的中断 。代码清单5 二级GPIO中断级联到一级中断控制器
1static int sirfsoc_gpio_probe(struct device_node *np) 2{ 3... 4for (i = 0; i < SIRFSOC_GPIO_NO_OF_BANKS; i++) { 5 bank = &sgpio->sgpio_bank[i]; 6 spin_lock_init(&bank->lock); 7 bank->parent_irq = platform_get_irq(pdev, i); 8 if (bank->parent_irq < 0) { 9 err = bank->parent_irq; 10 goto out_banks; 11 } 12 13 gpiochip_set_chained_irqchip(&sgpio->chip.gc, 14 &sirfsoc_irq_chip, 15 bank->parent_irq, 16 sirfsoc_gpio_handle_irq); 17} 18 19... 20}下面用一个实例来呈现这个过程,假设GPIO0_0~31对应上级中断号28 ,而外设A使用了GPIO0_5(即
第0组GPIO的第5个) ,并假定外设A的中断号为37,即32+5 ,中断服务程序为deva_isr() 。那么 ,当
GPIO0_5中断发生的时候 ,内核的调用顺序是:sirfsoc_gpio_handle_irq()->generic_handle_irq()-deva_isr()。如果硬件的中断系统有更深的层次 ,这种软件上的中断服务程序级联实际上可以有更深的
级别 。在上述实例中 ,GPIO0_0~31的interrupt_parrent实际是上级中断控制器 ,而外设A的interrupt_parrent就
是GPIO0 ,这些都会在设备树中进行呈现 。特别值得一提的是 ,目前多数主流ARM芯片内部的一级中断控制器都使用了ARM公司的GIC ,我们
几乎不需要实现任何代码,只需要在设备树中添加相关的节点 。如在arch/arm/boot/dts/exynos5250.dtsi中即含有:
gic:interrupt-controller@10481000 { compatible = "arm,cortex-a9-gic"; #interrupt-cells = <3>; interrupt-controller; reg = <0x10481000 0x1000>, <0x10482000 0x2000>; };打开drivers/irqchip/irq-gic.c ,发现GIC驱动的入口声明如下:
IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init); IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init); IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init); IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init); IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init); IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);这说明drivers/irqchip/irq-gic.c这个驱动可以兼容arm ,gic-400 、arm,cortex-a15-gic 、arm ,cortex-a7-gic
等 ,但是初始化函数都是统一的gic_of_init 。创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!