一、linux 中断API
1.1. 申请中断号
request_irq(unsigned int irq, /*中断号*/
irq_handler_t handler, /*中断处理函数*/
unsigned long flags, /*中断标志*/
const char *name, /*中断名称*/
void *dev) /*一般为设备结构体,dev会传递给中断处理函数*/
中断标志定义:
/include/linux/interrupt.h
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010
1.2. 注销中断号
void free_irq(unsigned int, void *);
1.3.中断处理函数
中断申请需要提供中断处理函数
类型定义:
// /include/linux/irqreturn.h
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
#define IRQ_RETVAL(x) ((x) ? IRQ_HANDLED : IRQ_NONE)
// /include/linux/interrupt.h
typedef irqreturn_t (*irq_handler_t)(int, void *);
由上可见,中断函数返回枚举类型,中断函数常用如下返回格式:
return IRQ_RETVAL(IRQ_HANDLED)
1.4.中断使能与禁止
// /include/linux/interrupt.h
void enable_irq(unsigned int irq); /*使能中断(irq : 中断号)*/
void disable_irq(unsigned int irq); /*确保中断完成后,再禁止中断*/
void disable_irq_nosync(unsigned int irq); /*等待中断函数结束后才执行禁止中断*/
// /include/linux/irqflags.h
// 全局中断,使能或禁止整个中断系统:
local_irq_enable();
local_irq_disable();
// 保存/恢复中断状态,防止因其他中断启动导致启动
local_irq_save(flags);
local_irq_restore(flags);
二、上半部下半部
中断函数一般快进快出,提高CPU调度效率。但是实际情况中,不可避免存在中断处理函数耗时。出于上述考虑,将中断函数分为上下两部分。上半部分处理过程快,耗时短,通过调度触发下半部分,处理耗时程序。
2.1.上半部下半部代码执行参考
- 中断处理内容不允许打断,放上半部。
- 中断处理任务对时间敏感,放上半部。
- 中断处理与硬件有关,放上半部。
- 其余,可优先放下半部。
2.2.下半部机制
2.2.1 软中断
上半部通过触发软中断服务函数实现下半部处理。
// /include/linux/interrupt.h
struct softirq_action
{
void (*action)(struct softirq_action *);
};
enum
{
HI_SOFTIRQ=0, /* 高优先级软中断 */
TIMER_SOFTIRQ, /* 定时器软中断 */
NET_TX_SOFTIRQ, /* 网络数据发送软中断 */
NET_RX_SOFTIRQ, /* 网络数据接收软中断 */
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ, /* tasklet 软中断 */
SCHED_SOFTIRQ, /* 调度软中断 */
HRTIMER_SOFTIRQ, /* 高精度定时器软中断 */
RCU_SOFTIRQ, /* RCU 软中断 */
NR_SOFTIRQS
};
// /kernel/softirq.c
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp; /*十个软中断*/
softirq_afuction中action变量为中断服务函数,softirq_vec为全局数组,每个cpu都有自己的触发控制机制,只执行自己的软中断。
那么如何使用软中断?
- 注册处理函数
// /include/linux/interrupt.h
void open_softirq(int nr, void (*action)(struct softirq_action *));
// nr: 需要开启的软中断,即上述枚举
// action: 中断函数
- 触发处理函数
void raise_softirq(unsigned int nr);
2.2.2 tasklet
tasklet 是实现下半部的另一种机制,对比软中断,建议使用tasklet。
定义
// /include/linux/interrupt.h
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long); /*处理函数*/
unsigned long data; /*func 参数*/
};
如何使用tasklet?
- 初始化
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long),
unsigned long data);
- 上半部调度执行
void tasklet_schedule(struct tasklet_struct *t);
tasklet代码流程
/* 定义 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);
......
}
2.2.3 工作队列
定义
// /include/linux/workquene.h
struct work_struct {
atomic_long_t data; /*处理函数参数*/
struct list_head entry;
work_func_t func; /*处理函数*/
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
如何使用工作队列?
- 初始化
#define INIT_WORK(_work, _func)
- 上半部调度执行
bool schedule_work(struct work_struct *work)
代码流程
/* 定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */
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);
......
三、设备树中的中断
3.1 配置设备树
linux 是通过设备树获取中断信息的。
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>;
};
interrupt-controller:当前节点为中断控制器。
interrupt-cells:表示控制器下中断设备cell大小。对设备而言,表示使用interrupts属性描述设备信息。
gpio5中断器intertrupts:
- 第一个cell:中断类型
- 第二个cell:中断号
- 第三个cell:中断类型标志
fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>;
interrupts = <0 8>;
};
interrupt-parent:指定父中断。
interrupt::中断方式,中断号等。
3.2 获取设备树中断信息
一般在驱动初始化函数内,通过设备树获取中断号,进而向linux内核申请中断。
从设备节点中获取中断号
/include/linux/of_irq.h
unsigned int irq_of_parse_and_map(struct device_node *node, int index);
从gpio获取中断号
/include/linux/gpio.h
int gpio_to_irq(unsigned gpio);
四、demo参考
驱动
https://github.com/Jiongyu/linux_driver_example/blob/master/13_irq/imx6uirq.c
测试
https://github.com/Jiongyu/linux_driver_example/blob/master/13_irq/imx6uirqApp.c
五、总结
- 如何向Linux内核申请中断?
- 中断的上半部、下半部。
- 设备树中中断如何配置?
- 如何在驱动初始化函数中,通过设备树获取中断号等中断信息?