I.MX6U嵌入式Linux驱动开发(11)Linux中断实验

1、Linux内核中断处理简介

1.1、Linux中断

(1)先知道你要使用的中断对应的中断号。
(2)再申请request_irq,此函数会激活中断。
(3)如果不用中断了,那就释放掉,使用free_irq。
(4)中断处理函数irqreturn_t (*irq_handler_t) (int, void *)。
(5)使能和禁止中断。

local_irq_save(flags);//用于禁止中断,并且将中断状态保存在flags中。
local_irq_restore(flags);//用于恢复中断,将中断到flags状态。

1.2、上半部与下半部

**上半部:**上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
**下半部:**如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。

主要目的就是实现中断处理函数的快进快出,那些对时间敏感、执行速度快的操作可以放到中断处理函数中,也就是上半部。剩下的所有工作都可以放到下半部去执行,比如在上半部将数据拷贝到内存中,关于数据的具体处理就可以放到下半部去执行。

参考点:
①、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
②、如果要处理的任务对时间敏感,可以放到上半部。
③、如果要处理的任务与硬件有关,可以放到上半部
④、除了上述三点以外的其他任务,优先考虑放到下半部。

上半部处理很简单,直接编写中断处理函数就行了。Linux 内核提供了多种下半部机制。

软中断
static struct softirq_action softirq_vec[NR_SOFTIRQS] 10个
要使用软中断,要先注册,使用函数open_softir。注册以后使用raise_softirq触发。
软中断我们不要去用!!

tasklet
也需要用到上半部,只是上半部的中断处理函数重点是调用tasklet_schedule。
①、定义一个tasklet函数。
②、初始化、重点是设置对应的处理函数

2、如何在设备树中添加中断节点信息

2.1、设备树中断信息节点

Linux 内核通过读取设备树中的中断属性信息来配置中断。
设备树绑定信息参考文档Documentation/devicetree/bindings/arm/gic.txt。
打开 imx6ull.dtsi 文件,其中的 intc 节点就是I.MX6ULL 的中断控制器节点。

1 intc: interrupt-controller@00a01000 {
2 	compatible = "arm,cortex-a7-gic";
3 	#interrupt-cells = <3>;
4 	interrupt-controller;
5 	reg =	<0x00a01000 0x1000>,
6 			<0x00a02000 0x100>;
7 };

对于 gpio 来说,gpio 节点也可以作为中断控制器,比如 imx6ull.dtsi 文件中的 gpio5 节点内容如下所示:

1 gpio5: gpio@020ac000 {
2 		compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
3 		reg = <0x020ac000 0x4000>;
4 		interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
5 				 	 <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
6 		gpio-controller;
7 		#gpio-cells = <2>;
8 		interrupt-controller;
9 		#interrupt-cells = <2>;
10 };

打开 imx6ull-alientek-emmc.dts 文件,找到如下所示内容:

//fxls8471 设备节点
1 fxls8471@1e {
2 		compatible = "fsl,fxls8471";
3 		reg = <0x1e>;
4 		position = <0>;
5		interrupt-parent = <&gpio5>;
6 		interrupts = <0 8>;
7 };

fxls8471 是 NXP 官方的 6ULL 开发板上的一个磁力计芯片,fxls8471 有一个中断引脚链接到了 I.MX6ULL 的 SNVS_TAMPER0 因脚上,这个引脚可以复用为 GPIO5_IO00。
第 5 行,interrupt-parent 属性设置中断控制器,这里使用 gpio5 作为中断控制器。
第 6 行,interrupts 设置中断信息,0 表示 GPIO5_IO00,8 表示低电平触发。

我们在使用时,只是添加第5、6行的内容。

3、编写驱动

3.1、修改设备树

将按键作为一个中断输入。在设备树中添加按键节点。打开imx6ull-alientek-emmc.dts,找到key这么一个节点。这个节点只是添加了key作为一个普通IO,现在要把中断相关的节点添加进去。

interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;

设置 interrupt-parent 属性值为“gpio1”,因为 KEY0 所使用的 GPIO 为GPIO1_IO18,也就是设置 KEY0 的 GPIO 中断控制器为 gpio1。

设置 interrupts 属性,也就是设置中断源,第一个cells的18表示GPIO1组的18号IO。IRQ_TYPE_EDGE_BOTH 定义在文件 include/linux/irq.h 中。

make dtbs
sudo cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb /home/yang/linux/tftpboot/ -f

启动开发板:

/ #
/ # cd proc/device.tree/
/sys/firmware/devicetree/base # ls
/sys/firmware/devicetree/base # cd key/

3.2、编写驱动(使用上半部)

创建工程:

3.2.1、编写驱动框架

搭建一个字符设备驱动框架。

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>

#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IMX6UIRQ_CNT		1		/* 设备号个数 */
#define IMX6UIRQ_NAME	"imx6uirq"	/* 名字 */


/* key设备结构体 */
struct imx6uirq_dev{
	dev_t devid;			/* 设备号 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */	
	struct device_node	*nd; /* 设备节点 */
};

struct imx6uirq_dev imx6uirq;	/* irq设备 */

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &imx6uirq; /* 设置私有数据 */
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t imx6uirq_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int ret = 0;
	
	return ret;
}

static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int imx6uirq_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
	.owner   =  THIS_MODULE,
	.open    =  imx6uirq_open,
	.write   =  imx6uirq_write,
	.read    =  imx6uirq_read,
	.release = 	imx6uirq_release,
};

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init imx6uirq_init(void)
{
	int ret = 0;

	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	imx6uirq.major = 0;
	if (imx6uirq.major) {		/*  定义了设备号 */
		imx6uirq.devid = MKDEV(imx6uirq.major, 0);
		ret = register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
	} else {						/* 没有定义设备号 */
		ret = alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);	/* 申请设备号 */
		imx6uirq.major = MAJOR(imx6uirq.devid);	/* 获取分配号的主设备号 */
		imx6uirq.minor = MINOR(imx6uirq.devid);	/* 获取分配号的次设备号 */
	}
	if(ret < 0) {
		goto fail_devid;
	}
	printk("imx6uirq major=%d,minor=%d\r\n",imx6uirq.major, imx6uirq.minor);	
	
	/* 2、初始化cdev */
	imx6uirq.cdev.owner = THIS_MODULE;
	cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
	
	/* 3、添加一个cdev */
	ret = cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
	if(ret)
		goto fail_cdevadd;

	/* 4、创建类 */
	imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
	if (IS_ERR(imx6uirq.class)) {
		ret = PTR_ERR(imx6uirq.class);
		goto fail_class;
	}

	/* 5、创建设备 */
	imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
	if (IS_ERR(imx6uirq.device)) {
		ret = PTR_ERR(imx6uirq.device);
		goto fail_device;
	}
	
	return 0;

fail_device:
	class_destory(imx6uirq.class);
fail_class:
	cdev_del(&imx6uirq.cdev);
fail_cdevadd:
	unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
fail_devid:
	return ret;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit imx6uirq_exit(void)
{
	/* 注销字符设备驱动 */
	cdev_del(&imx6uirq.cdev);/*  删除cdev */
	unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT); /* 注销设备号 */

	device_destroy(imx6uirq.class, imx6uirq.devid);
	class_destroy(imx6uirq.class);
}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yang");

3.2.2、编写中断驱动

编写一个按键初始化函数。在这个函数中
(1)按键初始化:
获取节点;
获取GPOIO对应的编号;新建一个新的结构体来描述按键的一些信息,这个按键信息就不能放在imx6uirq设备结构体中了。

#define KEY_NUM  1
/* key结构体 */
struct irq_keydesc{
	int gpio;				//io编号
	int irqnum;				//中断号
	unsihned char value;	//键值
	char name[10];			//名字
							//中断处理函数
}; 

在imx6uirq_dev设备结构体中添加按键信息

struct imx6uirq_dev{
	...
	struct irq_keydesc irqkey[KEY_NUM];
};

(2)按键中断初始化:

static int keyio_init(void)
{
	int ret = 0;
	int i = 0;
	/* 按键初始化 */
	imx6uirq.nd = of_find_node_by_path("/key");
	if(imx6uirq.nd == NULL){
		ret = -EINVAL;
		goto fail_nd;
	/* 按键中断初始化 */
	
	return 0;

fail_nd:
	return ret;
}

接下来获取按键编号;如果有很多IO,就循环获取:
初始化IO,也是循环,使用gpio_request;

/* 1、按键初始化 */
...
for(i=0; i<KEY_NUM; i++){
	imx6irq.irqkey[i].gpio = of_get_named_gpio(imx6uirq.nd, "key.gpios", i);
}
/* 2、按键中断初始化 */

在按键初始化函数中,可以传递参数struct imx6uirq_dev *dev,则函数修改为如下:

static int keyio_init(struct imx6uirq_dev *dev)
{
	int ret = 0;
	int i = 0;
	/* 按键初始化 */
	dev->nd = of_find_node_by_path("/key");
	if(dev->nd == NULL){
		ret = -EINVAL;
		goto fail_nd;
	}
		
	for(i=0; i<KEY_NUM; i++){
		dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key.gpios", i);
	}
		
	for(i=0; i<KEY_NUM; i++){
		memset(dev->irqkey[i].name, 0, sizeof(imx6uirq.irqkey[i].name));
		sprintf(dev->irqkey[i].name, "KEY%d", i);
		gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
		gpio_direction_input(dev->irqkey[i].gpio);
	}

	/* 2、按键中断初始化 */
	
	return 0;

fail_nd:
	return ret;
}

编译一下:可以在入口函数中,使用按键初始化函数,添加头文件

#include <linux/string.h>

加载到开发板上:

make
sudo cp imx6uirq.ko /home/yang/linux/nfs/rootfs/lib/modules/4.1.15/ -f

接下来,获取各个gpio对应的中断号

for(i=0; i<KEY_NUM; i++){
	memset(dev->irqkey[i].name, 0, sizeof(imx6uirq.irqkey[i].name));
	sprintf(dev->irqkey[i].name, "KEY%d", i);
	gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
	gpio_direction_input(dev->irqkey[i].gpio);
	
	dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio);//获取中断号
	
#if 0	
	dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd, i);//通用的获取中断号
#endif

}

	/* 2、按键中断初始化 */
	for(i = 0; i<KEY_NUM; i++){
		ret = request_irq(dev->irqkey[i].irqnum, );//参数:中断号,中断处理函数

参考Linux内核中使用中断处理函数的用法:

/* 按键中断处理函数 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
	return IRQ_HANDLED;
}

那么:先将中断处理函数添加到结构体中,然后再做个初始化处理。

/* key结构体 */
irqreturn_t (*handler) (int, void*) /* 中断处理函数 */

还需要定义按键值

#define KEY0VALUE   0x01
#define INVAKEY		0xFF
dev->irqkey[0].handler = key0_handler;
dev->irqkey[0].value = KEY0VALUE;
/* 2、按键中断初始化 */
for(i = 0; i<KEY_NUM; i++){
	ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, dev->irqkey[i].name, &imx6uirq);//参数:中断号,中断处理函数,触发方式,中断名字,传递个中断函数的参数
	if(ret){
		printk("irq %d request failed!\r\n", dev->irqkey[i].irqnum);
		goto fail_irq;
	}
}
return 0;

fail_irq://到此时,request需要释放
	for(i=0;i<KEY_NUM;i++){
		gpio_free(dev->irqkey[i].gpio, );
	}
fail_nd:
	return ret;	

在驱动入口函数中,初始化IO

/* 初始化IO */
ret = keyio_init(&imx6uirq);
if(ret<0){
	goto fail_keyinit;
}
return 0;
fail_keyinit:

在驱动出口函数释放IO,要先释放中断

int i = 0;
/* 1、释放中断 */
for(i=0; i<KEY_NUM; i++){
	free_irq(imx6uirq.irqkey[i].irqnum, &imx6uirq);
}
/* 2、释放IO */
for(i=0; i<KEY_NUM; i++){
	gpio_free(imx6uirq.irqkey[i].gpio);
}

编译一下:
添加头文件:

#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/ide.h>//会引用interrupt.h

在按键中断函数中做处理:

/* 按键中断处理函数 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
	int value = 0;
	struct imx6uirq_dev *dev = dev_id;
	value = gpio_get_value(dev->irqkey[0].gpio);
	if(value == 0){			/* 按下 */
		printk("KEY0 Push!\r\n");
	}else if(value == 1){	/* 释放 */
		printk("KEY0 Release!\r\n");
	}
	
	return IRQ_HANDLED;
}

编译并拷贝到开发板:

make
sudo cp imx6uirq.ko /home/yang/linux/nfs/rootfs/lib/modules/4.1.15/ -f
/ # cd lib/modules/4.1.15/
/lib/modules/4.1.15/ # ls
/lib/modules/4.1.15/ # depmod
/lib/modules/4.1.15/ # modprobe imx6uirq.ko
/lib/modules/4.1.15/ # ls /proc/interrupts
/lib/modules/4.1.15/ # cat /proc/interrupts
KEY0 Push!//按下按键
KEY0 Release!//松开按键

3.2.3、按键消抖

通过定时器来消抖。
在imx6uirq_dev设备结构体中添加定时器相关的东西。

struct imx6uirq_dev{
	...
	struct timer_list timer;
};

定时器需要初始化的,紧接着在按键初始化完了之后进行定时器初始化。刚开始的定时器的周期值不能设定,我们按下按键后才能让定时器工作,后面通过mod_timer来启动定时器。

还需要添加定时器的处理函数

/* 定时器的处理函数 */
static void timer_func(unsigned long arg){
	
}
/* 2、按键中断初始化 */
/* 3、定时器初始化 */
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_func;

在退出驱动的时候,需要删除定时器。

/* 1、释放中断 */
/* 2、释放IO */
/* 3、删除定时器 */
del_timer(&imx6uirq.timer);
del_timer_sync(&imx6uirq.timer);

按键按下去,进入按键中断处理函数,开启定时器,做一个简单的延时,比如10ms,10ms以后,定时器函数处理会执行,如果定时器函数执行的话,代表消抖已经过去啦,所以key0_handler()中,只需要触发定时器10ms即可。

static irqreturn_t key0_handler(int irq, void *dev_id)
{
	int value = 0;
	struct imx6uirq_dev *dev = dev_id;
	dev->timer.data = (volatile long)dev_id;
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));/* 10ms定时 */
	return IRQ_HANDLED;
}

10ms定时结束以后,定时器处理函数timer_func就会执行。进入这个函数之后,我们再去读取按键值,

/* 定时器的处理函数 */
static void timer_func(unsigned long arg){
	int value = 0struct imx6uirq_dev *dev = (struct imx6uirq_dev*)arg;
	value = gpio_get_value(dev->irqkey[0].gpio);
	if(value == 0){			/* 按下 */
		printk("KEY0 Push!\r\n");
	}else if(value == 1){	/* 释放 */
		printk("KEY0 Release!\r\n");
	}
}

编译,验证,发现有一点点问题,修改10ms–>20ms,
在定时器处理函数中添加一句打印消息:

printk("timer_func\r\n");
value......

编译验证,没有问题。

3.2.4、完善驱动

接下来要做:向应用层上报按键值。
在imx6uirq_dev设备结构体中添加2个变量。
第一个:按键值用atomic_t原子变量,
第二个:需要一个变量来标记按键值有没有被按下。

atomic_t keyvalue;
atomic_t releaekey;

将这两个原子变量在驱动入口函数中初始化,

/* 初始化IO */
/* 初始化原子变量  无效按键*/
atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.relasekey, 0);

要在定时器函数处理里面读取按键值value,按下以后要进行处理,每个按键都i有对应的一个值,将按键的值赋给keyvalue。释放的话,给按键值最高位置1。或上一个0x80。

/* 定时器的处理函数 */
static void timer_func(unsigned long arg)
{
	...
	if(value == 0){			/* 按下 */
		atomic_set(&dev->keyvalue, dev->irqkey[0].value);
	}else if(value == 1){	/* 释放 */
		atomic_set(&dev->keyvalue, 0x80 | (dev->irqkey[0].value));
		atomic_set(&dev->rleasekey, 1);/* 完整的一个按键过程 */
	}
}

还需要写一个read函数,因为应用程序需要读,在read函数中,先把私有数据提取出来。先定义2个变量,一个用来保存按键值,另一个保存有没有被释放。出现有效按键就要上报,如果是0x80,则说明释放掉了,释放掉以后把最高位置1,释放掉,然后把keyvalue拷贝给内核。判断release后需要清0。

static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int ret = 0;
	unsigned char keyvalue;
	unsigned char releasekey;
	struct imx6uirq_dev *dev = filp->private_data;
	
	keyvalue = atomic_read(&dev->keyvalue);
	releasekey = atomic_read(&dev->releasekey);
	
	if(releasekey) { /* 有效按键 */
		if(keyvalue & 0x80){
			keyvalue &= -0x80;
			ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
		} else {
			goto data_error;
		}
		atomic_set(&dev->releasekey, 0);//按下标志清零
	}
	return ret;
data_error:
	return -EINVAL;
}

3.3、编写应用APP

将上个实验的timerAPP拷贝过来。

cd /linux/IMX6ULL/Linux_Driver/13_timer/
ls
cp timerAPP.c ../14_imx6uirq/
ls
mv timerAPP.c imx6uirqAPP.c

在imx6uirqAPP.c中:

/* 循环读取 */
while(1){
	ret = read(fd, &data, sizeof(data));
	if(ret < 0){
	
	}else{
		if(data)
			printf("key value = %#x\r\n", data);
	}
}

编译

arm-linux-gnueabihf-gcc imx6uirqAPP.c -o imx6uirqAPP
sudo cp imx6uirq.ko imx6uirqAPP /home/yang/linux/nfs/tootfs/lib/modules/4.1.15/ -f

加载驱动执行APP

lsmod
rmmod imx6uirq
modprobe imx6uirq.ko
./imx6uirqAPP /dev/imx6uirq

一直打印0x01,说明驱动有问题,应该按下一次,打印一次,在驱动里面,只处理了releasekey有效的按键,因此需要添加无效的时候。应该goto error。

if(releasekey){ /* 有效按键 */
	......
} else {
	goto data_error;
}

验证:按下释放才会传递按键值0x01。

3.2、修改驱动(使用下半部)

复制一份驱动文件,直接在上面修改,将复制的文件重新命名为:tasklet.c,将Makefile文件改为tasklet.o

3.2.1、tasklet

在key设备结构体里面添加(一个按键一个中断):

struct tasklet_struct tasklet;

在init初始化函数中添加tasklet_init();

/* 2、按键中断初始化*/
for(i=0 ....)
{
	ret=...
	if(ret...)
	...
	tasklet_init(&dev->tasklet, );//第二个参数就是tasklet所对应的处理函数
}

编写tasklet所对应的处理函数,位置放到按键中断处理函数下面:

/* tasklet */
static void key_tasklet(unsigned long data)
{
	
}

也需要将其初始化,

//dev->irqkey[0].tasklet = key_tasklet;

/* 2、按键中断初始化*/
for(i=0 ....)
{
	ret=...
	if(ret...)
	...
	tasklet_init(&dev->irqkey[i].tasklet, key_tasklet, (unsigned long)dev);//第二个参数就是tasklet所对应的处理函数.不管是哪几个io,使用的都是一个key_tasklet,
}

在按键中断处理函数中只需要调度一下tasklet_schedule();

/* 按键中断处理函数 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
	//int value = 0;
	struct imx6uirq_dev *dev = dev_id;
#if 0
	dev->timer.data = (volatile long)dev_id;
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));/* 10ms定时 */
#endif
	tasklet_schedule(&dev->irqkey[0].tasklet);//写死了是0
	return IRQ_HANDLED;
}

调度完了之后,key_tasklet()里面执行:

/* tasklet */
static void key_tasklet(unsigned long data)
{
	struct imx6uirq_dev *dev = (struct imx6uirq_dev)data;
	printk("key_task!\r\n");
	dev->timer.data = data;
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));/* 20ms定时 */
}

3.2.2、工作队列

cp tasklet.c work.c
与tasklet用法类似,也可以参考Linux源码里面的用法进行使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值