I.MX6ULL ARM驱动开发---Linux中断之下半部

引言

  Linux 内核提供了多种下半部机制,接下来来学习一下这些下半部机制。

一、下半部机制

1、tasklet

  tasklet 是利用软中断来实现的另外一种下半部机制,在软中断和 tasklet 之间,建议大家使用 tasklet。Linux 内核使用 tasklet_struct 结构体来表示 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 的参数 */
};

(1)初始化tasklet

使用 tasklet_init 函数初始化 tasklet,taskled_init 函数原型如下:

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);

函数参数和返回值含义如下:

  t:要初始化的 tasklet

  func:tasklet 的处理函数。

  data:要传递给 func 函数的参数

  返回值:没有返回值。

  也可以使用宏 DECLARE_TASKLET 来一次性完成 tasklet 的 定义和初始化 ,DECLARE_TASKLET 定义在 include/linux/interrupt.h 文件中,定义如下:

DECLARE_TASKLET(name, func, data)

  其中 name 为要定义的 tasklet 名字,这个名字就是一个 tasklet_struct 类型的时候变量,func就是 tasklet 的处理函数,data 是传递给 func 函数的参数。

  在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行,tasklet_schedule 函数原型如下:

void tasklet_schedule(struct tasklet_struct *t)

函数参数和返回值含义如下:

  t:要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name。

  返回值:没有返回值。

(2)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、工作队列

  工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。Linux 内核使用 work_struct 结构体表示一个工作,内容如下(省略掉条件编译):

struct work_struct {
	atomic_long_t data; 
	struct list_head entry;
	work_func_t func; /* 工作队列处理函数 */
};

  这些工作组织成工作队列,工作队列使用 workqueue_struct 结构体表示,内容如下(省略掉条件编译):

struct workqueue_struct {
	struct list_head pwqs; 
	struct list_head list; 
	struct mutex mutex; 
	int work_color;
	int flush_color; 
	atomic_t nr_pwqs_to_flush;
	struct wq_flusher *first_flusher;
	struct list_head flusher_queue; 
	struct list_head flusher_overflow;
	struct list_head maydays; 
	struct worker *rescuer; 
	int nr_drainers; 
	int saved_max_active;
	struct workqueue_attrs *unbound_attrs;
	struct pool_workqueue *dfl_pwq; 
	char name[WQ_NAME_LEN];
	struct rcu_head rcu;
	unsigned int flags ____cacheline_aligned;
	struct pool_workqueue __percpu *cpu_pwqs;
	struct pool_workqueue __rcu *numa_pwq_tbl[];
};

  Linux 内核使用工作者线程(worker thread)来处理工作队列中的各个工作,Linux 内核使用worker 结构体表示工作者线程,worker 结构体内容如下:

struct worker {
	union {
		struct list_head entry; 
		struct hlist_node hentry;
	};
	struct work_struct *current_work; 
	work_func_t current_func; 
	struct pool_workqueue *current_pwq;
	bool desc_valid;
	struct list_head scheduled; 
	struct task_struct *task; 
	struct worker_pool *pool; 
	struct list_head node; 
	unsigned long last_active; 
	unsigned int flags; 
	int id; 
	char desc[WORKER_DESC_LEN];
	struct workqueue_struct *rescue_wq;
};

  从示例代码可以看出,每个 worker 都有一个工作队列,工作者线程处理自己工作队列中的所有工作。在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作队列和工作者线程我们基本不用去管。简单创建工作很简单,直接定义一个 work_struct 结构体变量即可,然后使用 INIT_WORK 宏来初始化工作,INIT_WORK 宏定义如下:

#define INIT_WORK(_work, _func)

  _work 表示要初始化的工作,_func 是工作对应的处理函数。

  也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化,宏定义如下:

#define DECLARE_WORK(n, f)

  n 表示定义的工作(work_struct),f 表示工作对应的处理函数。

  和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为 schedule_work,函数原型如下所示:

bool schedule_work(struct work_struct *work)

函数参数和返回值含义如下:

  work:要调度的工作。

  返回值:0 成功,其他值 失败。

(2)工作队列参考使用示例

/* 定义工作(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);
	......
}

二、实验程序编写

  编写代码实现key按钮按下松开,切换led的亮灭。

1、驱动程序编写(tasklet)

#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/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IMX6UIRQ_CNT		1			/* 设备号个数 	*/
#define IMX6UIRQ_NAME		"tasklet"	/* 名字 		*/
#define KEY0VALUE			0X01		/* KEY0按键值 	*/
#define INVAKEY				0XFF		/* 无效的按键值 */
#define KEY_NUM				1			/* 按键数量 	*/

/* 中断IO描述结构体 */
struct irq_keydesc {
	int gpio;								/* gpio */
	int irqnum;								/* 中断号     */
	unsigned char value;					/* 按键对应的键值 */
	char name[10];							/* 名字 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
};

/* tasklet设备结构体 */
struct tasklet_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */
	atomic_t keyvalue;		/* 有效的按键键值 */
	atomic_t releasekey;	/* 标记是否完成一次完成的按键,包括按下和释放 */
	struct timer_list timer;/* 定义一个定时器*/
	struct irq_keydesc irqkeydesc[KEY_NUM];	/* 按键描述数组 */
	unsigned char curkeynum;				/* 当前的按键号 */
	struct tasklet_struct key_tasklet;
	int led_gpio;
	atomic_t led_sta;
};

struct tasklet_dev tasklet;	/* irq设备 */


void testtasklet_func(unsigned long data) {

	int ret=0,sta=0;
	
	struct tasklet_dev *dev = (struct tasklet_dev *)data;

	sta=atomic_read(&dev->led_sta);
	
	if(atomic_read(&dev->releasekey))
	{
		if(sta==0)
		{
			ret = gpio_direction_output(dev->led_gpio, 0);
			atomic_set(&dev->led_sta,1);
		}
		else
		{
			ret = gpio_direction_output(dev->led_gpio, 1);
			atomic_set(&dev->led_sta,0);		
		}
		if(ret < 0) {
			printk("can't set gpio!\r\n");
		}
	}
}
/* @description		: 中断服务函数,开启定时器,延时10ms,
 *				  	  定时器用于按键消抖。
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
	struct tasklet_dev *dev = (struct tasklet_dev *)dev_id;

	dev->curkeynum = 0;
	dev->timer.data = (volatile long)dev_id;
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));	/* 10ms定时 */
	return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description	: 定时器服务函数,用于按键消抖,定时器到了以后
 *				  再次读取按键值,如果按键还是处于按下状态就表示按键有效。
 * @param - arg	: 设备结构变量
 * @return 		: 无
 */
void timer_function(unsigned long arg)
{
	unsigned char value;
	unsigned char num;
	struct irq_keydesc *keydesc;
	struct tasklet_dev *dev = (struct tasklet_dev *)arg;

	num = dev->curkeynum;
	keydesc = &dev->irqkeydesc[num];

	value = gpio_get_value(keydesc->gpio); 	/* 读取IO值 */
	if(value == 0){ 						/* 按下按键 */
		atomic_set(&dev->keyvalue, keydesc->value);
	}
	else{ 									/* 按键松开 */
		atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
		atomic_set(&dev->releasekey, 1);	/* 标记松开按键,即完成一次完整的按键过程 */			
	}
	tasklet_schedule(&dev->key_tasklet);
}

/*
 * @description	: 按键IO初始化
 * @param 		: 无
 * @return 		: 无
 */
static int keyio_init(void)
{
	unsigned char i = 0;
	int ret = 0;
	
	tasklet.nd = of_find_node_by_path("/key");
	if (tasklet.nd== NULL){
		printk("key node not find!\r\n");
		return -EINVAL;
	} 

	/* 提取GPIO */
	for (i = 0; i < KEY_NUM; i++) {
		tasklet.irqkeydesc[i].gpio = of_get_named_gpio(tasklet.nd ,"key-gpio", i);
		if (tasklet.irqkeydesc[i].gpio < 0) {
			printk("can't get key%d\r\n", i);
		}
	}
	
	/* 初始化key所使用的IO,并且设置成中断模式 */
	for (i = 0; i < KEY_NUM; i++) {
		memset(tasklet.irqkeydesc[i].name, 0, sizeof(tasklet.irqkeydesc[i].name));	/* 缓冲区清零 */
		sprintf(tasklet.irqkeydesc[i].name, "KEY%d", i);		/* 组合名字 */
		gpio_request(tasklet.irqkeydesc[i].gpio, tasklet.irqkeydesc[i].name);
		gpio_direction_input(tasklet.irqkeydesc[i].gpio);	
		tasklet.irqkeydesc[i].irqnum = irq_of_parse_and_map(tasklet.nd, i);
#if 0
		tasklet.irqkeydesc[i].irqnum = gpio_to_irq(tasklet.irqkeydesc[i].gpio);
#endif
		printk("key%d:gpio=%d, irqnum=%d\r\n",i, tasklet.irqkeydesc[i].gpio, 
                                         tasklet.irqkeydesc[i].irqnum);
	}
	/* 申请中断 */
	tasklet.irqkeydesc[0].handler = key0_handler;
	tasklet.irqkeydesc[0].value = KEY0VALUE;
	
	for (i = 0; i < KEY_NUM; i++) {
		ret = request_irq(tasklet.irqkeydesc[i].irqnum, tasklet.irqkeydesc[i].handler, 
		                 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, tasklet.irqkeydesc[i].name, &tasklet);
		if(ret < 0){
			printk("irq %d request failed!\r\n", tasklet.irqkeydesc[i].irqnum);
			return -EFAULT;
		}
	}

	/* 创建定时器 */
	init_timer(&tasklet.timer);
	tasklet.timer.function = timer_function;
	return 0;
}

static int led_init(void)
{
	int ret = 0;

	tasklet.nd = of_find_node_by_path("/gpioled");
	if (tasklet.nd== NULL) {
		return -EINVAL;
	}

	tasklet.led_gpio = of_get_named_gpio(tasklet.nd ,"led-gpio", 0);
	if (tasklet.led_gpio < 0) {
		printk("can't get led\r\n");
		return -EINVAL;
	}
	
	/* 初始化led所使用的IO */
	gpio_request(tasklet.led_gpio, "led");		/* 请求IO 	*/
	ret = gpio_direction_output(tasklet.led_gpio, 1);
	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}
	atomic_set(&tasklet.led_sta, 0);
	return 0;
}

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

 /*
  * @description     : 从设备读取数据 
  * @param - filp    : 要打开的设备文件(文件描述符)
  * @param - buf     : 返回给用户空间的数据缓冲区
  * @param - cnt     : 要读取的数据长度
  * @param - offt    : 相对于文件首地址的偏移
  * @return          : 读取的字节数,如果为负值,表示读取失败
  */
static ssize_t tasklet_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int ret = 0;
	unsigned char keyvalue = 0;
	unsigned char releasekey = 0;
	struct tasklet_dev *dev = (struct tasklet_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);/* 按下标志清零 */
	} else {
		goto data_error;
	}
	return 0;
	
data_error:
	return -EINVAL;
}

/* 设备操作函数 */
static struct file_operations tasklet_fops = {
	.owner = THIS_MODULE,
	.open = tasklet_open,
	.read = tasklet_read,
};

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init tasklet_dev_init(void)
{
	/* 1、构建设备号 */
	if (tasklet.major) {
		tasklet.devid = MKDEV(tasklet.major, 0);
		register_chrdev_region(tasklet.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
	} else {
		alloc_chrdev_region(&tasklet.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
		tasklet.major = MAJOR(tasklet.devid);
		tasklet.minor = MINOR(tasklet.devid);
	}

	/* 2、注册字符设备 */
	cdev_init(&tasklet.cdev, &tasklet_fops);
	cdev_add(&tasklet.cdev, tasklet.devid, IMX6UIRQ_CNT);

	/* 3、创建类 */
	tasklet.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
	if (IS_ERR(tasklet.class)) {
		return PTR_ERR(tasklet.class);
	}

	/* 4、创建设备 */
	tasklet.device = device_create(tasklet.class, NULL, tasklet.devid, NULL, IMX6UIRQ_NAME);
	if (IS_ERR(tasklet.device)) {
		return PTR_ERR(tasklet.device);
	}
	
	/* 5、初始化按键 */
	atomic_set(&tasklet.keyvalue, INVAKEY);
	atomic_set(&tasklet.releasekey, 0);
	
	tasklet_init(&tasklet.key_tasklet,testtasklet_func,(unsigned long)&tasklet);
	
	keyio_init();
	led_init();
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit tasklet_dev_exit(void)
{
	unsigned int i = 0;
	/* 删除定时器 */
	del_timer_sync(&tasklet.timer);	/* 删除定时器 */
		
	/* 释放中断 */
	for (i = 0; i < KEY_NUM; i++) {
		free_irq(tasklet.irqkeydesc[i].irqnum, &tasklet);
		gpio_free(tasklet.irqkeydesc[i].gpio);
	}
	cdev_del(&tasklet.cdev);
	unregister_chrdev_region(tasklet.devid, IMX6UIRQ_CNT);
	device_destroy(tasklet.class, tasklet.devid);
	class_destroy(tasklet.class);
}

module_init(tasklet_dev_init);
module_exit(tasklet_dev_exit);
MODULE_LICENSE("GPL");

2、驱动程序编写(工作队列)

#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/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IMX6UIRQ_CNT		1			/* 设备号个数 	*/
#define IMX6UIRQ_NAME		"worker"	/* 名字 		*/
#define KEY0VALUE			0X01		/* KEY0按键值 	*/
#define INVAKEY				0XFF		/* 无效的按键值 */
#define KEY_NUM				1			/* 按键数量 	*/

/* 中断IO描述结构体 */
struct irq_keydesc {
	int gpio;								/* gpio */
	int irqnum;								/* 中断号     */
	unsigned char value;					/* 按键对应的键值 */
	char name[10];							/* 名字 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
};
struct my_work_struct {
	struct work_struct worker;
	struct worker_dev* dev;
};
/* worker设备结构体 */
struct worker_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */
	atomic_t keyvalue;		/* 有效的按键键值 */
	atomic_t releasekey;	/* 标记是否完成一次完成的按键,包括按下和释放 */
	struct timer_list timer;/* 定义一个定时器*/
	struct irq_keydesc irqkeydesc[KEY_NUM];	/* 按键描述数组 */
	unsigned char curkeynum;				/* 当前的按键号 */
	struct my_work_struct key_worker;
	int led_gpio;
	atomic_t led_sta;
};

struct worker_dev worker;	/* irq设备 */


void testwork_func_t(struct work_struct *work) {
	
	int ret=0,sta=0;
	
	struct my_work_struct* worker=(struct my_work_struct*)work;
	
	struct worker_dev *dev =(struct worker_dev *)worker->dev;
	
	sta=atomic_read(&dev->led_sta);
	
	if(atomic_read(&dev->releasekey))
	{
		if(sta==0)
		{
			ret = gpio_direction_output(dev->led_gpio, 0);
			atomic_set(&dev->led_sta,1);
		}
		else
		{
			ret = gpio_direction_output(dev->led_gpio, 1);
			atomic_set(&dev->led_sta,0);		
		}
		if(ret < 0) {
			printk("can't set gpio!\r\n");
		}
	}
}
/* @description		: 中断服务函数,开启定时器,延时10ms,
 *				  	  定时器用于按键消抖。
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
	struct worker_dev *dev = (struct worker_dev *)dev_id;

	dev->curkeynum = 0;
	dev->timer.data = (volatile long)dev_id;
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));	/* 10ms定时 */
	return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description	: 定时器服务函数,用于按键消抖,定时器到了以后
 *				  再次读取按键值,如果按键还是处于按下状态就表示按键有效。
 * @param - arg	: 设备结构变量
 * @return 		: 无
 */
void timer_function(unsigned long arg)
{
	unsigned char value;
	unsigned char num;
	struct irq_keydesc *keydesc;
	struct worker_dev *dev = (struct worker_dev *)arg;

	num = dev->curkeynum;
	keydesc = &dev->irqkeydesc[num];

	value = gpio_get_value(keydesc->gpio); 	/* 读取IO值 */
	if(value == 0){ 						/* 按下按键 */
		atomic_set(&dev->keyvalue, keydesc->value);
	}
	else{ 									/* 按键松开 */
		atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
		atomic_set(&dev->releasekey, 1);	/* 标记松开按键,即完成一次完整的按键过程 */			
	}
	schedule_work(&dev->key_worker.worker);
}

/*
 * @description	: 按键IO初始化
 * @param 		: 无
 * @return 		: 无
 */
static int keyio_init(void)
{
	unsigned char i = 0;
	int ret = 0;
	
	worker.nd = of_find_node_by_path("/key");
	if (worker.nd== NULL){
		printk("key node not find!\r\n");
		return -EINVAL;
	} 

	/* 提取GPIO */
	for (i = 0; i < KEY_NUM; i++) {
		worker.irqkeydesc[i].gpio = of_get_named_gpio(worker.nd ,"key-gpio", i);
		if (worker.irqkeydesc[i].gpio < 0) {
			printk("can't get key%d\r\n", i);
		}
	}
	
	/* 初始化key所使用的IO,并且设置成中断模式 */
	for (i = 0; i < KEY_NUM; i++) {
		memset(worker.irqkeydesc[i].name, 0, sizeof(worker.irqkeydesc[i].name));	/* 缓冲区清零 */
		sprintf(worker.irqkeydesc[i].name, "KEY%d", i);		/* 组合名字 */
		gpio_request(worker.irqkeydesc[i].gpio, worker.irqkeydesc[i].name);
		gpio_direction_input(worker.irqkeydesc[i].gpio);	
		worker.irqkeydesc[i].irqnum = irq_of_parse_and_map(worker.nd, i);
#if 0
		worker.irqkeydesc[i].irqnum = gpio_to_irq(worker.irqkeydesc[i].gpio);
#endif
		printk("key%d:gpio=%d, irqnum=%d\r\n",i, worker.irqkeydesc[i].gpio, 
                                         worker.irqkeydesc[i].irqnum);
	}
	/* 申请中断 */
	worker.irqkeydesc[0].handler = key0_handler;
	worker.irqkeydesc[0].value = KEY0VALUE;
	
	for (i = 0; i < KEY_NUM; i++) {
		ret = request_irq(worker.irqkeydesc[i].irqnum, worker.irqkeydesc[i].handler, 
		                 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, worker.irqkeydesc[i].name, &worker);
		if(ret < 0){
			printk("irq %d request failed!\r\n", worker.irqkeydesc[i].irqnum);
			return -EFAULT;
		}
	}

	/* 创建定时器 */
	init_timer(&worker.timer);
	worker.timer.function = timer_function;
	return 0;
}

static int led_init(void)
{
	int ret = 0;

	worker.nd = of_find_node_by_path("/gpioled");
	if (worker.nd== NULL) {
		return -EINVAL;
	}

	worker.led_gpio = of_get_named_gpio(worker.nd ,"led-gpio", 0);
	if (worker.led_gpio < 0) {
		printk("can't get led\r\n");
		return -EINVAL;
	}
	
	/* 初始化led所使用的IO */
	gpio_request(worker.led_gpio, "led");		/* 请求IO 	*/
	ret = gpio_direction_output(worker.led_gpio, 1);
	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}
	atomic_set(&worker.led_sta, 0);
	return 0;
}

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

 /*
  * @description     : 从设备读取数据 
  * @param - filp    : 要打开的设备文件(文件描述符)
  * @param - buf     : 返回给用户空间的数据缓冲区
  * @param - cnt     : 要读取的数据长度
  * @param - offt    : 相对于文件首地址的偏移
  * @return          : 读取的字节数,如果为负值,表示读取失败
  */
static ssize_t worker_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int ret = 0;
	unsigned char keyvalue = 0;
	unsigned char releasekey = 0;
	struct worker_dev *dev = (struct worker_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);/* 按下标志清零 */
	} else {
		goto data_error;
	}
	return 0;
	
data_error:
	return -EINVAL;
}

/* 设备操作函数 */
static struct file_operations worker_fops = {
	.owner = THIS_MODULE,
	.open = worker_open,
	.read = worker_read,
};

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init worker_init(void)
{
	/* 1、构建设备号 */
	if (worker.major) {
		worker.devid = MKDEV(worker.major, 0);
		register_chrdev_region(worker.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
	} else {
		alloc_chrdev_region(&worker.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
		worker.major = MAJOR(worker.devid);
		worker.minor = MINOR(worker.devid);
	}

	/* 2、注册字符设备 */
	cdev_init(&worker.cdev, &worker_fops);
	cdev_add(&worker.cdev, worker.devid, IMX6UIRQ_CNT);

	/* 3、创建类 */
	worker.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
	if (IS_ERR(worker.class)) {
		return PTR_ERR(worker.class);
	}

	/* 4、创建设备 */
	worker.device = device_create(worker.class, NULL, worker.devid, NULL, IMX6UIRQ_NAME);
	if (IS_ERR(worker.device)) {
		return PTR_ERR(worker.device);
	}
	
	/* 5、初始化按键 */
	atomic_set(&worker.keyvalue, INVAKEY);
	atomic_set(&worker.releasekey, 0);
	
	INIT_WORK((struct work_struct*)&worker.key_worker,testwork_func_t);
	worker.key_worker.dev=&worker;
	
	keyio_init();
	led_init();
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit worker_exit(void)
{
	unsigned int i = 0;
	/* 删除定时器 */
	del_timer_sync(&worker.timer);	/* 删除定时器 */
		
	/* 释放中断 */
	for (i = 0; i < KEY_NUM; i++) {
		free_irq(worker.irqkeydesc[i].irqnum, &worker);
		gpio_free(worker.irqkeydesc[i].gpio);
	}
	cdev_del(&worker.cdev);
	unregister_chrdev_region(worker.devid, IMX6UIRQ_CNT);
	device_destroy(worker.class, worker.devid);
	class_destroy(worker.class);
}

module_init(worker_init);
module_exit(worker_exit);
MODULE_LICENSE("GPL");

3、测试APP编写

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd;
	int ret = 0;
	char *filename;
	unsigned char data;
	
	if (argc != 2) {
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];
	fd = open(filename, O_RDWR);
	if (fd < 0) {
		printf("Can't open file %s\r\n", filename);
		return -1;
	}

	while (1) {
		ret = read(fd, &data, sizeof(data));
		if (ret < 0) {  /* 数据读取错误或者无效 */
			
		} else {		/* 数据读取正确 */
			if (data)	/* 读取到数据 */
				printf("key value = %#X\r\n", data);
		}
	}
	close(fd);
	return ret;
}

4、Makefile

KERNELDIR := /home/sh/Desktop/MUL/zimage
CURRENT_PATH := $(shell pwd)

#tasklet
obj-m := tasklet.o

#工作队列
#obj-m := worker.o

ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-

build: kernel_modules

kernel_modules:
	$(MAKE) -j32 -C $(KERNELDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -j32 -C $(KERNELDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(CURRENT_PATH) clean

三、运行测试

使用如下命令来测试:

./taskletApp /dev/tasklet

按下开发板上的 KEY0 键,终端就会输出按键值,并切换LED的亮灭。

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一盆电子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值