linux内核中断

一、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.上半部下半部代码执行参考

  1. 中断处理内容不允许打断,放上半部。
  2. 中断处理任务对时间敏感,放上半部。
  3. 中断处理与硬件有关,放上半部。
  4. 其余,可优先放下半部。

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

五、总结

  1. 如何向Linux内核申请中断?
  2. 中断的上半部、下半部。
  3. 设备树中中断如何配置?
  4. 如何在驱动初始化函数中,通过设备树获取中断号等中断信息?
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值