linux内核中断(一)

一、中断简介

中断是指 CPU 在执行程序的过程中,出现了某些突发事件急待处理,CPU 必须暂停当前程序的执行, 转去处理突发事件,处理完毕后又返回原程序被中断的位置继续执行。由于中断的存在极大的提高了 CPU 的运行效率,但是设备的中断会打断内核进程中的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽量短小精悍。

二、中断上下文

为保证系统实时性,中断服务程序必须足够简短,但实际应用中某些时候发生中断时必须处理大量的 事物,这时候如果都在中断服务程序中完成,则会严重降低中断的实时性,基于这个原因,linux 系统提出了一个概念:把中断服务程序分为两部分:中断上下文,也叫做顶半部-底半部 。

  • 中断上文(顶半部):完成尽可能少的比较急的功能,它往往只是简单的读取寄存器的中断状态,并清除中断标志后就进行“中断标记”(也就是把底半部处理程序挂到设备的底半部执行队列中)的工作。 顶半部的特点就是响应速度快。
  • 中断下文(底半部):处理中断的剩余大部分任务,可以被新的中断打断。

三、linux开发使用中断的总体思路

linux 中断有专门的中断子系统,其实现原理很复杂,但是驱动开发者不需要知道其实现的具体细节, 只需要知道怎么在设备树中指定中断,如何应用该子系统提供的 API 函数来编写中断相关驱动代码即可其他的事情,比如设备树中的中断控制器,这些都是由原厂的BSP工程师帮我们写好了,我们不需要来修改他。

四、设备树中配置中断

如果一个设备需要用到中断功能,开发人员就需要在设备树中配置好中断属性信息,因为设备树是用来描述硬件信息的,然后Linux内核通过设备树配置的中断属性来配置中断功能。

参考文档:

内核Documentation\devicetree\bindings\interrupt-controller\interrupts.txt

1、各级中断中断控制器

在硬件上,“中断控制器”只有GIC这一个,但是我们在软件上也可以把上图中的“GPIO”称为“中断控制器”。

GPIO1连接到GIC,GPIO2连接到GIC,所以GPIO1的父亲是GIC,GPIO2的父亲是GIC。

假设GPIO1有32个中断源,但是它把其中的16个汇聚起来向GIC发出一个中断,把另外16个汇聚起来向GIC发出另一个中断。这就意味着GPIO1会用到GIC的两个中断,会涉及GIC里的2个hwirq。

这些层级关系、中断号(hwirq),都会在设备树中有所体现。

2、中断控制器节点的语法

在设备树中,中断控制器节点中必须有的两个属性:

interrupt-controller,表明它是“中断控制器”。

#interrupt-cells,表明引用这个中断控制器的话需要多少个cell。

#interrupt-cells的,别的节点要引用这个中断控制器时,需要用几个cells来描述

3、示例


比如,在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 = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(1) | IRQ_TYPE_LEVEL_HIGH)>;
	#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 的含义如下:

  1. cells:中断类型,0 表示 SPI 中断,1 表示 PPI 中断。
  2. cells:中断号,对于 SPI 中断来说中断号的范围为 0~987,对于 PPI 中断来说中断号的范围为 0~15。
  3. 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 = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
				 <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
	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 66 IRQ_TYPE_LEVEL_HIGH>
  • 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

五、驱动代码中使用中断

1、获取中断号

当在设备数中使用了interrupt-parent和interrupts 属性来描述中断后,可以通过irq_of_parse_and_map来获取中断号。gpio中断也可以不使用这两个属性,直接通过gpio号来获取中断号。

2、request_irq 函数申请中断

request_irq 函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用

request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
irq :要申请中断的中断号。  
handler :中断处理函数,当中断发生以后就会执行此中断处理函数。
flags :中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志
name :中断名字,设置以后可以在 /proc/interrupts 文件中看到对应的中断名字。
dev :如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将 dev 设置为 设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
返回值 0 中断申请成功,其他负值 中断申请失败,如果返回 -EBUSY 的话表示中断已经被申请了。

3、编写中断处理函数

使用 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;
一般中断服务函数返回值使用如下形式: return IRQ_RETVAL(IRQ_HANDLED)

4、释放中断资源

中断使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会 删除中断处理函数并且禁止中断。free_irq 函数原型如下所示:

void free_irq(unsigned int irq, void *dev)
irq :要释放的中断。
dev :如果中断设置为共享 (IRQF_SHARED) 的话,此参数用来区分具体的中断。共享中断
只有在释放最后中断处理函数的时候才会被禁止掉。

5、中断使能和禁止函数

常用的中断使用和禁止函数如下所示:

void enable_irq(unsigned int irq);
void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
  • disable_irq 函数要 等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。
  • disable_irq_nosync 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。

6、其他

local_irq_disable(); //屏蔽中断 
local_irq_enable(); //打开中断 
local_irq_save(flags); //禁止中断并保存当前 CPU 中断信息 
local_irq_restore(flags); //打开中断并回复之前保存的 CPU 中断信息

六、综合示例:

1、设备树

 2、驱动代码

3、调试手段

查看中断是否申请注册成功:cat /proc/interrupts命令

其中: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

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值