linux 内核中断理解

内核中断

每个中断都有一个中断号,通过中断号即可区分不同的中断

申请中断,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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为了维护世界和平_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值