中断是指 CPU 在执行程序的过程中,出现了某些突发事件急待处理,CPU 必须暂停当前程序的执行, 转去处理突发事件,处理完毕后又返回原程序被中断的位置继续执行。由于中断的存在极大的提高了 CPU 的运行效率,但是设备的中断会打断内核进程中的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽量短小精悍。
为保证系统实时性,中断服务程序必须足够简短,但实际应用中某些时候发生中断时必须处理大量的 事物,这时候如果都在中断服务程序中完成,则会严重降低中断的实时性,基于这个原因,linux 系统提出了一个概念:把中断服务程序分为两部分:中断上下文,也叫做顶半部-底半部 。
linux 中断有专门的中断子系统,其实现原理很复杂,但是驱动开发者不需要知道其实现的具体细节, 只需要知道怎么在设备树中指定中断,如何应用该子系统提供的 API 函数来编写中断相关驱动代码即可。其他的事情,比如设备树中的中断控制器,这些都是由原厂的BSP工程师帮我们写好了,我们不需要来修改他。
如果一个设备需要用到中断功能,开发人员就需要在设备树中配置好中断属性信息,因为设备树是用来描述硬件信息的,然后Linux内核通过设备树配置的中断属性来配置中断功能。
参考文档:
内核Documentation\devicetree\bindings\interrupt-controller\interrupts.txt
在硬件上,“中断控制器”只有GIC这一个,但是我们在软件上也可以把上图中的“GPIO”称为“中断控制器”。
GPIO1连接到GIC,GPIO2连接到GIC,所以GPIO1的父亲是GIC,GPIO2的父亲是GIC。
假设GPIO1有32个中断源,但是它把其中的16个汇聚起来向GIC发出一个中断,把另外16个汇聚起来向GIC发出另一个中断。这就意味着GPIO1会用到GIC的两个中断,会涉及GIC里的2个hwirq。
这些层级关系、中断号(hwirq),都会在设备树中有所体现。
在设备树中,中断控制器节点中必须有的两个属性:
interrupt-controller,表明它是“中断控制器”。
#interrupt-cells,表明引用这个中断控制器的话需要多少个cell。
#interrupt-cells的,别的节点要引用这个中断控制器时,需要用几个cells来描述
比如,在imx6u11.dtsi文件,其中的inc节点就是imx6ul1的中断控制器节点,如下图所示:
imx6ul.dtsi - arch/arm/boot/dts/imx6ul.dtsi - Linux source code (v5.16.16) - Bootlin
intc: interrupt-controller@a01000 {
compatible = "arm,gic-400", "arm,cortex-a7-gic";
interrupts = ;
#interrupt-cells = <3>;
interrupt-controller;
interrupt-parent = <&intc>;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x2000>,
<0x00a04000 0x2000>,
<0x00a06000 0x2000>;
};
compatible 属性值为“arm,cortex-a7-gic”在 Linux 内核源码中搜索“arm,cortex-a7- gic”即可找到 GIC 中断控制器驱动文件。
interrupt-controller 节点为空,表示当前节点是中断控制器。
#interrupt-cells 和#address-cells、#size-cells 一样。指它的子节点是用多少个cells来描述一个中断。的对于 ARM 处理的 GIC 来说,一共有 3 个 cells,这三个 cells 的含义如下:
- cells:中断类型,0 表示 SPI 中断,1 表示 PPI 中断。
- cells:中断号,对于 SPI 中断来说中断号的范围为 0~987,对于 PPI 中断来说中断号的范围为 0~15。
- cells:标志,bit[3:0]表示中断触发类型,为 1 的时候表示上升沿触发,为 2 的时候表示下降沿触发,为 4 的时候表示高电平触发,为 8 的时候表示低电平触发。bit[15:8]为 PPI 中断的 CPU 掩码。
比如,GPI0的节点也可以作为中断控制器,在imx6ul.dtsi文件中GPlO1的节点内容如下图所示:
gpio1: gpio@209c000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = ,
;
clocks = <&clks IMX6UL_CLK_GPIO1>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-ranges = <&iomuxc 0 23 10>, <&iomuxc 10 17 6>,
<&iomuxc 16 33 16>;
};
interrupts中就是用三个cells来描述中断的:
interrupts =
- GIC_SPI:代表共享中断(GIC_PPI代表私有中断)
- 66:代表中断号,一组gpio共享一个中断号
- IRQ_TYPE_LEVEL_HIGH:中断类型
打开可以打开《IMX6ULLRM.pdf》的“Chapter 3 Interrupts and DMA Events”章节,找到表 3-1 ,GPIO1 一共用了 2 个中断号,一个是 66 ,一个是 67 。66 对 应 GPIO1_IO00~GPIO1_IO15 这低 16 个 IO , 67 对应 GPIO1_IO16~GPIOI1_IO31 这高 16 位 IO 。interrupt- controller 表明了 gpio1 节点也是个中断控制器,用于控制 gpio1 所有 IO 的中断。
上述工作都是由原厂的BSP工程师来帮我们写好的,并不需要我们来写。我们需要关注的点是怎么在设备树里面描述一个外设的中断节点,我们来看一个例子:
在这个例子中,我们先使用pinctrl和gpio子系统把这个引脚设置为了gpio功能,因为我们在使用中断的时候需要把引脚设置成输入。然后使用interrupt-parent和interrupts 属性来描述中断。
我们的引脚使用的是gpiol里面的io18,所以我们使用的是gpio1这个中断控制器
interrupts属性设置的是中断源,为什么里面是俩个cells呢,因为我们在gpio1这个中断控制器里面#interrupt-cells的值为2
当在设备数中使用了interrupt-parent和interrupts 属性来描述中断后,可以通过irq_of_parse_and_map来获取中断号。gpio中断也可以不使用这两个属性,直接通过gpio号来获取中断号。
request_irq 函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用。
request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
使用 request_irq 函数申请中断的时候需要设置中断处理函数,中断处理函数格式如下所示:
irqreturn_t (*irq_handler_t) (int, void *)
第一个参数是要中断处理函数要相应的中断号。
第二个参数是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev 也可以指向设备数据结构。
中断处理函数的返回值为 irqreturn_t 类型,定义如下所示:
enum irqreturn {
IRQ_NONE = (0 << 0), //表示不是本驱动的中断不处理
IRQ_HANDLED = (1 << 0), //表示正常处理,通常是这个
IRQ_WAKE_THREAD = (1 << 1), //表示在中断下文中处理
}typedef enum irqreturn irqreturn_t;
中断使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会 删除中断处理函数并且禁止中断。free_irq 函数原型如下所示:
void free_irq(unsigned int irq, void *dev)
常用的中断使用和禁止函数如下所示:
void enable_irq(unsigned int irq);
void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
local_irq_disable(); //屏蔽中断
local_irq_enable(); //打开中断
local_irq_save(flags); //禁止中断并保存当前 CPU 中断信息
local_irq_restore(flags); //打开中断并回复之前保存的 CPU 中断信息
其中:48就是中断号
拿到中断号之后,可以看这个中断触发了多少次:
ref:
【原创】Linux中断子系统(三)-softirq和tasklet - LoyenWang - 博客园
韦东山:在Linux设备树(DTS)中指定中断_在代码中获得中断(附.视频) - 知乎
【北京迅为】嵌入式学习之Linux驱动篇_哔哩哔哩_bilibili
https://elixir.bootlin.com/linux/v5.16.16/source/arch/arm/boot/dts/imx6ul.dtsi