首页IT科技中断控制器(中断控制器)

中断控制器(中断控制器)

时间2025-08-03 16:51:09分类IT科技浏览7137
导读:在Linux内核中,各个设备驱动可以简单地调用request_irq()、enable_irq()、disable_irq()、 local_irq_disable()、local_irq_enable()等通用API来完成中断申请、使能、禁止等功能。...

在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版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
看新闻可以赚钱的app(看那些新闻可以赚钱的软件是什么软件下载-手机刷新闻还能赚钱?当心碰触到法律底线!) 如何通过计算密度避免堆砌嫌疑(优化SEO排名,提高网站质量)