一. IMX6ULL裸机中断
- 配置好中断相关外设及初始化GIC中断控制器。
- 在IRQ异常服务函数中,使用GIC控制器、cp15协处理器,获取当前发生的中断相关信息。
- 跳转到中断处理函数中,执行对应的中断服务函数。
- 返回至IRQ异常服务函数,退出至程序被打断处继续执行。
二. Linux中断——Linux内核提供了完善的中断框架
- 配置好设备节点的中断信息,Linux内核会根据这些信息初始化相关中断
- 初始化相关设备,以及加载设备、创建设备节点文件
- 从设备树中获取中断号,初始化上半部中断服务函数,快进快出
- 初始化下半部tasklet处理,用于处理耗时的中断处理
三. 中断处理函数
-
中断号:Linux下使用一个int类型表示中断号
-
request_irq:申请中断函数,可能会引起休眠,申请中断后会自动使能中断
/* 返回值为0表示申请成功,为负值表示申请失败,-EBUSY表示中断已被申请 */ 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文件定义,如
标志 | 描述 |
---|---|
IRQF_SHARED | 多个设备共享一个中断线,共享的所有中断都必须指定此标志。 如果使用共享中断的话,request_irq 函数的dev参数就是唯一 区分他们的标志。如GPIO通常是多组IO共用一个中断号 |
IRQF_ONESHOT | 单次中断,中断执行一次就结束 |
IRQF_TRIGGER_NONE | 无触发 |
IRQF_TRIGGER_RISING | 上升沿触发 |
- 参数name,表示中断名字,申请成功可在/proc/interrupts文件看到相应名字。
- 参数dev,用来区分不同中断,一般设置为设备结构体,该参数会传递给中断服务函数irq_handler_t的第二个参数。
-
free_irq:释放中断函数,若中断标志不是共享,则会删除中断服务函数,并且禁止中断
/* irq为要释放的中断号 dev:若为共享中断,该参数用于区分具体的中断 */ void free_irq(unsigned int irq, void *dev);
-
中断处理函数
/* int为中断号, void*参数与request_irq的dev参数相同,用于区分共享中断的不同中断函数 */ irqreturn_t (*irq_handler_t) (int, void *);
返回类型为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); /* IRQ_RETVAL定义如下 */ #define IRQ_RETVAL(x) ( (x)!=0)
-
中断使能与禁止函数
void enable_irq(unsigned int irq); //使能中断 /* 该禁止中断函数需要等到,当前正在执行的中断处理函数执行完才返回 */ void disable_irq(unsigned int irq); /* 该禁止中断函数调用后立即返回,不会等待中断处理函数执行完 */ void disable_irq_nosync(unsigned int irq);
/* 禁止整个系统的中断,并且保存中断状态至flags */ void local_irq_save(unsigned long flags); /* 恢复整个系统的中断,并恢复中断状态 */ void local_irq_restore(unsigned long flags);
四. 中断处理的上半部和下半部
-
为了实现中断处理函数的快进快出,Linux内核提出了将中断分为上半部和下半部的概念
- 上半部即为中断处理函数,要求快进快出
- 下半部为比较耗时的处理过程,用于上半部结束后再去处理
-
Linux内核提供的下半部机制
- 2.1. 软中断
- 2.2. tasklet(在软中断之上实现),Linux内核中的tasklet_struct结构体
struct tasklet_struct{
struct tasklet_struct *next; //下一个tasklet
unsigned long state; //tasklet状态
atomic_t count; //计数器,记录对tasklet的引用数
void (*func)(unsigned long); //tasklet执行函数
unsigned long data; //tasklet执行函数的传入参数
};
2.2.1. tasklet初始化函数
/* 参数t为要初始化的tasklet,func为tasklet执行函数,data为func传入参数 */
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);
/* 在include/linux/interrupt.h文件中,可用宏函数完成定义和初始化tasklet
name:要定义tasklet的名字;func:tasklet的执行函数;
data:执行函数func的传入参数 */
DECLARE_TASKLET(name, func, data);
2.2.2. tasklet调度函数,在上半部中断处理函数中,执行调度函数;tasklet就会在合适的时间去运行。
void tasklet_schedule(struct tasklet_struct *t); //t为要调度的tasklet
- 2.3. 工作队列:工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行;因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。
五. 设备树中断信息节点
-
在设备树中,要配置好中断属性信息;Linux内核通过读取设备树中的中断属性信息来初始化相关中断。
-
中断控制器GIC,绑定文档Documentation/devicetree/bindings/arm/gic.txt;在 .dtsi的根节点下intc节点,即为IMX6ULL的中断控制器节点。
intc: interrupt-controller@00a01000 { compatible = "arm,cortex-a7-gic"; /*arm驱动厂商,cortex-a7-gic驱动*/ #interrupt-cells = <3>; /*表示interrupt属性的cells大小,此处为3 1.中断类型,0为SPI(共享外设中断) or 1为PPI(私有外设中断); 2.中断号,SPI为0~987,PPI为0~15; 3.标志:bit[3:0]表示中断触发类型,bit[15:8]为PPI的CPU掩码 */ interrupt-controller; /*表示此节点为中断控制器*/ reg = <0x00a01000 0x1000>, <0x00a02000 0x100>; };
-
GPIO中断控制器举例
- 与中断有关的设备树属性信息
1. interrupt-cells,指定中断源的信息 cells 个数;
2. interrupt-controller,表示当前节点为中断控制器;
3. interrupt-parent,指定父中断,也就是中断控制器;
4. interrupts,表示中断信息,如中断号,触发方式等;
- GPIO5控制器设备节点
gpio5: gpio@020ac000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x020ac000 0x4000>;
/* 中断源信息,中断类型是SPI,中断号是74/75,触发电平是高电平 */
interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller; /* 表示此节点为中断控制器 */
#interrupt-cells = <2>; /* 将interrupt属性的cells大小,改为2 */
};
- fxls8471设备节点,开启中断,使用gpio5中断控制器
fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>; /*该属性为父中断,表明使用的中断控制器为gpio5 */
interrupts = <0 8>; /*该属性表示中断信息,0表示IO0,8表示低电平触发 */
/* 触发方式:1上升沿,2下降沿,4高电平,8低电平 */
/* 定义在include/linux/irq.h文件中 */
};
- 获取中断号
- 从设备节点的interupts属性获取;
/* dev表示设备节点;index表示索引值;返回GPIO中断号 */
unsigned int irq_of_parse_and_map(struct device_node*dev, int index);
- 从GPIO编号中,获取GPIO对应的中断号;
int gpio_to_irq(unsigned int gpio); //传入GPIO编号,传出对应GPIO中断号
以上是我在学习过程中的总结,不当之处请在评论区指出。