内核中断
Linux 中断处理程序
Linux中断处理机制要求缩短中断处理时间,因此要求中断处理函数尽快短小(快进快出),但实际上中断响应处理需要进行大量的处理计算。Linux中断处理函数分为上半部和下半部。上半部即响应的中断处理函数,进行简单的响应,而下部分通过tasklet、 工作队列、 软中断和线程化irq实现高耗时操作。
Linux 上半部
申请中断
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev);
int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id);//不需要显示释放
irq 具体中断号,通过解析设备树获得
handler 中断处理函数, irqreturn_t (*irq_handler_t) (int, void *)
flags 中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志
name 中断名称
dev 中断处理函数的入参
释放中断
void free_irq(unsigned int irq,void *dev_id);
使能与失能中断
void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);
disable_irq() 待中断处理函数处理完以后返回,即同步
disable_irq_nosync() 不等待中断处理函数返回,即异步
Linux下半部
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 的参数 */
};
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);
使用时定义tasklet 结构体和处理函数,利用tasklet_init进行绑定,在上半部的中断处理函数最后调用void tasklet_schedule(struct tasklet_struct *t)
来实现。
使用DECLARE_TASKLET(name, func, data)
宏可以定义名为name的tasklet来一步实现定义和注册。
工作队列
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func; /* 工作队列处理函数 */
};
#define INIT_WORK(_work, _func)
_work 表示要初始化的工作, _func 是工作对应的处理函数。
#define DECLARE_WORK(nme, func)
也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化。
在上半部的中断处理函数内使用 bool schedule_work(struct work_struct *work)
来调度工作队列。
软中断
在Linux内核中, 用struct softirq_action
结构体表征一个软中断, 这个结构体包含软中断处理函数指针和传递给该函数的参数。 使用void open_softirq(int nr, void (*action)(struct softirq_action *))
函数可以注册软中断对应的处理函数, 而void raise_softirq(unsigned int nr)
函数可以触发一个软中断。一般来说, 驱动的编写者不会也不宜直接使用softirq。
threaded_irq
在内核中, 除了可以通过request_irq()
、 devm_request_irq()
申请中断以外, 还可以通过
request_threaded_irq()
和devm_request_threaded_irq()
申请。
int request_threaded_irq(unsigned int irq, irq_handler_t handler,irq_handler_t thread_fn,unsigned long flags, const char *name, void *dev);
int devm_request_threaded_irq(struct device *dev, unsigned int irq,irq_handler_t handler, irq_handler_t thread_fn,unsigned long irqflags, const char *devname,void *dev_id);
参数handler对应的函数执行于中断上下文, thread_fn参数对应的函数则执行于内核线程。 如果handler结束的时候, 返回值是IRQ_WAKE_THREAD, 内核会调度对应线程执行thread_fn对应的函数。
设备树
intc: interrupt-controller@00a01000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>;
7 };
以IMX6ULL的设备树为例,其驱动是"arm,cortex-a7-gic"
。
#interrupt-cells
描述了 interrupts 属性的 cells 大小,也就是一条信息有几个 cells。每个 cells 都是 32 位整形值,对于 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 掩码。
interrupt-controller
节点为空,表示当前节点是中断控制器。
arm,cortex-a7-gic 驱动文档路径
Linux-4.9.88\Documentation\devicetree\bindings\interrupt-controller\arm,gic.txt
以下是文档内的部分内容,可以查看该中断控制器驱动的设备树描述
Main node required properties:
- compatible : should be one of:
"arm,arm1176jzf-devchip-gic"
"arm,arm11mp-gic"
"arm,cortex-a15-gic"
"arm,cortex-a7-gic"
"arm,cortex-a9-gic"
"arm,eb11mp-gic"
"arm,gic-400"
"arm,pl390"
"arm,tc11mp-gic"
"brcm,brahma-b15-gic"
"nvidia,tegra210-agic"
"qcom,msm-8660-qgic"
"qcom,msm-qgic2"
- interrupt-controller : Identifies the node as an interrupt controller
- #interrupt-cells : Specifies the number of cells needed to encode an
interrupt source. The type shall be a <u32> and the value shall be 3.
The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
interrupts.
The 2nd cell contains the interrupt number for the interrupt type.
SPI interrupts are in the range [0-987]. PPI interrupts are in the
range [0-15].
The 3rd cell is the flags, encoded as follows:
bits[3:0] trigger type and level flags.
1 = low-to-high edge triggered
2 = high-to-low edge triggered (invalid for SPIs)
4 = active high level-sensitive
8 = active low level-sensitive (invalid for SPIs).
bits[15:8] PPI interrupt cpu mask. Each bit corresponds to each of
the 8 possible cpus attached to the GIC. A bit set to '1' indicated
the interrupt is wired to that CPU. Only valid for PPI interrupts.
Also note that the configurability of PPI interrupts is IMPLEMENTATION
DEFINED and as such not guaranteed to be present (most SoC available
in 2014 seem to ignore the setting of this flag and use the hardware
default value).
可以使用unsigned int irq_of_parse_and_map(struct device_node *dev,int index)
从设备树中解析出中断号,index 通常为0。