引言
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的亮灭。