内核中断
每个中断都有一个中断号,通过中断号即可区分不同的中断
申请中断,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:中断处理函数,当中断发生以后就会执行此中断处理函数。
name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
flags:参数如下
参数 | 含义 |
---|---|
IRQF_SHARED | 多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话,request_irq 函数的 dev 参数就是唯一区分他们的标志。 |
IRQF_ONESHOT | 单次中断,中断执行一次就结束。 |
IRQF_TRIGGER_NONE | 无触发。 |
IRQF_TRIGGER_RISING | 上升沿触发。 |
IRQF_TRIGGER_FALLING | 下降沿触发。 |
IRQF_TRIGGER_HIGH | 高电平触发。 |
IRQF_TRIGGER_LOW | 低电平触发。 |
释放中断
通过 free_irq 函数释放掉相应的中断
void free_irq(unsigned int irq, void *dev)
irq:要释放的中断。
dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉
中断处理函数
irqreturn_t (*irq_handler_t) (int, void *)
第一个参数是要中断处理函数要相应的中断号。第二个参数是一个指向 void 的指针
返回值为 irqreturn_t 类型:
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
中断使能与静止
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq);//要等到当前正在执行的中断处理函数执行完才返回
void disable_irq_nosync(unsigned int irq)// 函数调用以后立即返回,不会等待当前中断处理程序执行完毕
关闭和使能整个处理器的中断
local_irq_enable()
local_irq_disable()
推荐使用
local_irq_save(flags)
local_irq_restore(flags)
local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags 中。
local_irq_restore 用于恢复中断,将中断到 flags 状态
中断上半部与下半部:
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可
以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部
去执行,这样中断处理函数就会快进快出
在使用request_irq 申请中断的时候注册的中断服务函数属于中断处理的上半部;
1 软中断
Linux 内核提供了“bottom half”机制来实现下半部,简称“BH”。后面引入了软中断和 tasklet 来替代“BH”机制,从 2.5 版本的 Linux内核开始 BH 已经被抛弃了。
2 tasklet
struct tasklet_struct
{
struct tasklet_struct *next; /* 下一个 tasklet */
unsigned long state; /* tasklet 状态 */
atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
void (*func)(unsigned long); /* tasklet 执行的函数 */
unsigned long data; /* 函数 func 的参数 */
};
func 函数就是 tasklet 要执行的处理函数
初始化函数
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),
unsigned long data);
t:要初始化的 tasklet
func:tasklet 的处理函数。
data:要传递给 func 函数的参数
/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
/* tasklet 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度 tasklet */
tasklet_schedule(&testtasklet);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 tasklet *
tasklet_init(&testtasklet, testtasklet_func, data);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
工作队列
工作队列将要推后的工作交给一个内核线程去执行
工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet
/* 定义工作(work) */
struct work_struct testwork;
void testwork_func_t(struct work_struct *work);
{
/* work 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度 work */
schedule_work(&testwork);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 work */
INIT_WORK(&testwork, testwork_func_t);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
设备树中断信息
Linux 内核通过读取设备树中 的 中 断 属 性 信 息 来 配 置 中 断
1)I.MX6ULL 的中断控制器节点
intc: interrupt-controller@00a01000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>;
};
interrupt-cells 描述了 interrupts 属性的 cells 大小,也就是一条信息有几个 cells
这里一共有 3 个 cells
第一个 cells:中断类型, 0 表示 SPI (shared processor interrupts)中断, 1 表示 PPI(per processor interrupts) 中断。
第二个 cells:中断号,对于 SPI 中断来说中断号的范围为 0~987,对于 PPI 中断来说中断号的范围为 0~15。
第三个 cells:标志, bit[3:0]表示中断触发类型。
为 1 的时候表示上升沿触发,
为 2 的时候表示下降沿触发,
为 4 的时候表示高电平触发,
为 8 的时候表示低电平触发。
bit[15:8]为 PPI 中断的 CPU 掩码。
interrupt-controller 节点为空,表示当前节点是中断控制器
2)gpio 节点也可以作为中断控制器
imx6ull.dtsi 文件中的 gpio5节点信息
gpio5: gpio@020ac000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x020ac000 0x4000>;
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>;
};
interrupts 描述中断源信息,对于 gpio5 来说一共有两条信息,中断类型都是 SPI,触发电平都是 IRQ_TYPE_LEVEL_HIGH。不同之处在于中断源,一个是 74,一个是 75。参考手册 Chapter 3 Interrupts and DMA Events章节中
可以看出, GPIO5 一共用了 2 个中断号,一个是 74,一个是 75。其中 74 对应 GPIO5_IO00~GPIO5_IO15 这低 16 个 IO, 75 对应 GPIO5_IO16~GPIOI5_IO31 这高 16 位 IO
interrupt-controller 表明了 gpio5 节点也是个中断控制器,用于控制 gpio5 所有 IO的中断。
#interrupt-cells 为 2。表示子interrupts 有两个参数
3)在imx6ull-alientek-emmc.dts 文件 找到磁力计芯片
fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>;
interrupts = <0 8>;
}
interrupt-parent 属性设置中断控制器,这里使用 gpio5 作为中断控制器。
interrupts 设置中断信息,第一个参数表示使用GPIO5的低0号中断,这里的0号中断指的是0号引脚,即 GPIO5_IO00, 第二个参数表示触发方式:8 表示低电平触发。这里有两个参数在gpio5节点 #interrupt-cells 设置的2一致。
4)中断设备树总结
①、 #interrupt-cells,指定中断源的信息 cells 个数。
②、 interrupt-controller,表示当前节点为中断控制器。
③、 interrupts,指定中断号,触发方式等。
④、 interrupt-parent,指定父中断,也就是中断控制器
中断号获取API
1)从interupts 属性中提取到对应的设备号
unsigned int irq_of_parse_and_map(struct device_node *dev,int index
dev: 设备节点。
index:索引号, interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。
返回值:中断号。
2)使用 gpio_to_irq 函数来获取 gpio 对应的中断号,函数原型如下
int gpio_to_irq(unsigned int gpio)