linux驱动学习笔记—实现中断下半部以及驱动编写规范(七)【转】

  • 2019 年 10 月 10 日
  • 笔记

转自:https://blog.csdn.net/weixin_42471952/article/details/81609141

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/weixin_42471952/article/details/81609141 中断下半部: tasklet : struct tasklet_struct { struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); //下半部要执行的代码 unsigned long data; // 传递给func的参数 }; 1, 初始化tasklet tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data) 2, 在中断上半部,将tasklet加入到内核线程 tasklet_schedule(struct tasklet_struct * t) 3, 模块卸载的时候,需要从内核线程中移除tasklet tasklet_kill(struct tasklet_struct * t) —————————————————————— typedef void (*work_func_t)(struct work_struct *work); struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func;//下半部要执行的代码 };

1, 初始化work INIT_WORK(struct work_struct * work, work_func_t func); work_func_t func为结构体struct work_struct中的函数指针; 2, 在中断上半部,将work加入到内核线程 schedule_work(struct work_struct * work); 3, 模块卸载的时候,需要从内核线程中移除work cancel_work_sync(struct work_struct * work)

工作队列(work queue)是另外一种将工作推后执行的形式,它和前面讨论的tasklet有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。

那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。

引用: https://blog.csdn.net/av_geek/article/details/41278801

一个为 struct tasklet_struct *next任务链表,一个为struct list_head entry;内核链表

驱动编写规范 设计一个对象描述所有的全局变量

//设计一个对象类型–描述所有的全局的变量 struct s5pv210_key{ __u8 have_data; //用于描述是否有数据 int major ; //记录主设备号 struct class *cls; //用于创建 类 struct device *dev; //用来创建设备文件 wait_queue_head_t wq_head; //用于实现阻塞的等待队列头 struct key_event event; //用于存放数据的包 struct work_struct work;//用于实现中断下半部 }; 声明一个对象

//声明一个对象 struct s5pv210_key *key_dev; 初始化时统一申请空间(这也是好处之一)

// 0-为全局设备/数据对象分配空间 //参数2–标志–分配内存方式, GFP_KERNEL(如果当前内存不够的时候会等待) key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL); 错误判断

if(key_dev == NULL) { printk(KERN_ERR"kmalloc errorn"); return -ENOMEM; } 指针错误判断

if(IS_ERR(key_dev->cls)) { ret = PTR_ERR(key_dev->cls); //自动根据指针转换一个对应的出错码 goto err_unregister; } 统一下函数的最后做处理

static int __init key_drv_init(void) { int ret; // 0-为全局设备/数据对象分配空间 //参数2–标志–分配内存方式, GFP_KERNEL(如果当前内存不够的时候会等待) key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL); if(key_dev == NULL) { printk(KERN_ERR"kmalloc errorn"); return -ENOMEM; } // 1, 申请主设备号–动态申请主设备号 key_dev->major = register_chrdev(0, "key_dev", &key_fops); if(key_dev->major < 0) { printk(KERN_ERR"register_chrdev errorn"); ret = key_dev->major; goto err_free; } // 2, 创建设备节点 key_dev->cls = class_create(THIS_MODULE, "key_cls"); if(IS_ERR(key_dev->cls)) { ret = PTR_ERR(key_dev->cls); //自动根据指针转换一个对应的出错码 goto err_unregister; } key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->major, 0), NULL, "key0"); if(IS_ERR(key_dev->dev)) { ret = PTR_ERR(key_dev->dev); goto err_destory_cls; } // 3, 硬件初始化– 映射地址或者中断申请 //参数1–中断号码 //获取中断号码的方法: 1,外部中断IRQ_EINT(x) // 2, 头文件 #include <mach/irqs.h> #include <plat/irqs.h> //参数2–中断的处理方法 //参数3–中断触发方式: 高电平,低电平,上升沿,下降沿,双边沿 /* #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 */ //参数4–中断的描述-自定义字符串 //参数5–传递个参数2的任意数据 //返回值0表示正确 int i; int irqno; int flags; char *name; for(i=0; i<ARRAY_SIZE(all_keys); i++) { name = all_keys[i].name; irqno = all_keys[i].irqno; flags = all_keys[i].flags; ret = request_irq(irqno, key_irq_handler, flags, name, &all_keys[i]); if(ret != 0) { printk("request_irq errorn"); goto err_destroy_dev; } } // 定义一个等待队列头,并且初始化 init_waitqueue_head(&key_dev->wq_head); //初始化work INIT_WORK(&key_dev->work, work_key_irq); return 0; err_destroy_dev: device_destroy(key_dev->cls, MKDEV(key_dev->major, 0)); err_destory_cls: class_destroy(key_dev->cls); err_unregister: unregister_chrdev(key_dev->major, "key_dev"); err_free: kfree(key_dev); return ret; } 完整驱动代码 #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/interrupt.h> #include <linux/gpio.h> #include <linux/input.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/poll.h> #include <linux/slab.h> #include <asm/uaccess.h> // 设计一个对象–描述按键产生的数据包 struct key_event{ int code; //什么按键: 左键, 回车键 int value; // 按键的状态: 按下/抬起 (1/0) }; //设计一个描述按键的对象: 名字, irqno, gpio, 按键值,触发方式 struct key_desc{ char *name; int irqno; int gpio; int code; int flags;// 触发方式 }; //设计一个对象类型–描述所有的全局的变量 struct s5pv210_key{ __u8 have_data; //用于描述是否有数据 int major ; //记录主设备号 struct class *cls; //用于创建 类 struct device *dev; //用来创建设备文件 wait_queue_head_t wq_head; //用于实现阻塞的等待队列头 struct key_event event; //用于存放数据的包 struct work_struct work;//用于实现中断下半部 }; //声明一个对象 struct s5pv210_key *key_dev; struct key_desc all_keys[] = { [0] = { .name = "key1_up_eint0", .irqno = IRQ_EINT(0), .gpio = S5PV210_GPH0(0), .code = KEY_UP, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [1] = { .name = "key2_down_eint1", .irqno = IRQ_EINT(1), .gpio = S5PV210_GPH0(1), .code = KEY_DOWN, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [2] = { .name = "key3_left_eint2", .irqno = IRQ_EINT(2), .gpio = S5PV210_GPH0(2), .code = KEY_LEFT, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, [3] = { .name = "key4_right_eint3", .irqno = IRQ_EINT(3), .gpio = S5PV210_GPH0(3), .code = KEY_LEFT, .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, }, }; int key_drv_open (struct inode *inode, struct file *filp) { printk("——–^_* %s——-n", __FUNCTION__); // 通过文件路径可以得到inode // 通过得到次设备号可以区分不同的设备 //int major = MAJOR(filp->f_path.dentry->d_inode->i_rdev); int major = imajor(filp->f_path.dentry->d_inode); int major2 = imajor(inode); int minor = iminor(filp->f_path.dentry->d_inode); int minor2 = iminor(inode); printk("major = %d, minor = %dn", major, minor); printk("major2 = %d, minor2 = %dn", major2, minor2); memset(&key_dev->event, 0, sizeof(struct key_event)); key_dev->have_data= 0; //为假–一开始都没有按键按下或者抬起 return 0; } ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos) { int ret; //区分当前是阻塞还是非阻塞 if((filp->f_flags & O_NONBLOCK) && !key_dev->have_data) { return -EAGAIN; } // 在资源不可达的时候,进行休眠 // 参数1—当前驱动中的等待队列头 // 参数2–休眠的条件: 假的话就休眠,真就不休眠 wait_event_interruptible(key_dev->wq_head, key_dev->have_data); ret = copy_to_user(buf, &key_dev->event, count); if(ret > 0) { printk("copy_to_user errorn"); return -EFAULT; } //清零,以备下次充值 memset(&key_dev->event, 0, sizeof(struct key_event)); key_dev->have_data = 0; //没有数据了,等待下一次数据 return count; } int key_drv_close(struct inode *inode, struct file *filp) { printk("——–^_* %s——-n", __FUNCTION__); return 0; } unsigned int key_drv_poll (struct file *filp, struct poll_table_struct *pts) { //返回一个整数: 没有数据的时候返回0, 有数据的时候,返回POLLIN unsigned int mask = 0; // 将当前的等待队列头,注册到vfs层, 注意: poll_wait该函数不会导致休眠 //参数1-文件对象–当前file //参数2–当前的等待队列头 //参数3–当前传递过来的第三个参数 poll_wait(filp, &key_dev->wq_head, pts); if(key_dev->have_data) mask |= POLLIN; return mask; } // 4, 实现fops const struct file_operations key_fops = { .owner = THIS_MODULE, .open = key_drv_open, .read = key_drv_read, .poll = key_drv_poll, .release =key_drv_close, }; void work_key_irq(struct work_struct *work) { printk("———–%s——-n", __FUNCTION__); key_dev->have_data = 1;//表示有数据了 wake_up_interruptible(&key_dev->wq_head); } //中断处理方法 //参数1–当前产生的中断的号码 //参数2–request_irq传递过来的参数 irqreturn_t key_irq_handler(int irqno, void *dev_id) { int *p = (int *)dev_id; //printk("———–%s——-0x%x—–n", __FUNCTION__, *p); //区分当前是哪个按键 struct key_desc *pdesc = (struct key_desc *)dev_id; //区分按下还是抬起 int value = gpio_get_value(pdesc->gpio); if(value) { //抬起 printk("<kernel>–%s : releasen", pdesc->name); //填充值 key_dev->event.code = pdesc->code; key_dev->event.value = 0; }else { //按下 printk("<kernel>–%s : pressedn", pdesc->name); key_dev->event.code = pdesc->code; key_dev->event.value = 1; } //在中断上半部,将work加入到内核线程 schedule_work(&key_dev->work); return IRQ_HANDLED; } static int __init key_drv_init(void) { int ret; // 0-为全局设备/数据对象分配空间 //参数2–标志–分配内存方式, GFP_KERNEL(如果当前内存不够的时候会等待) key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL); if(key_dev == NULL) { printk(KERN_ERR"kmalloc errorn"); return -ENOMEM; } // 1, 申请主设备号–动态申请主设备号 key_dev->major = register_chrdev(0, "key_dev", &key_fops); if(key_dev->major < 0) { printk(KERN_ERR"register_chrdev errorn"); ret = key_dev->major; goto err_free; } // 2, 创建设备节点 key_dev->cls = class_create(THIS_MODULE, "key_cls"); if(IS_ERR(key_dev->cls)) { ret = PTR_ERR(key_dev->cls); //自动根据指针转换一个对应的出错码 goto err_unregister; } key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->major, 0), NULL, "key0"); if(IS_ERR(key_dev->dev)) { ret = PTR_ERR(key_dev->dev); goto err_destory_cls; } // 3, 硬件初始化– 映射地址或者中断申请 //参数1–中断号码 //获取中断号码的方法: 1,外部中断IRQ_EINT(x) // 2, 头文件 #include <mach/irqs.h> #include <plat/irqs.h> //参数2–中断的处理方法 //参数3–中断触发方式: 高电平,低电平,上升沿,下降沿,双边沿 /* #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 */ //参数4–中断的描述-自定义字符串 //参数5–传递个参数2的任意数据 //返回值0表示正确 int i; int irqno; int flags; char *name; for(i=0; i<ARRAY_SIZE(all_keys); i++) { name = all_keys[i].name; irqno = all_keys[i].irqno; flags = all_keys[i].flags; ret = request_irq(irqno, key_irq_handler, flags, name, &all_keys[i]); if(ret != 0) { printk("request_irq errorn"); goto err_destroy_dev; } } // 定义一个等待队列头,并且初始化 init_waitqueue_head(&key_dev->wq_head); //初始化work INIT_WORK(&key_dev->work, work_key_irq); return 0; err_destroy_dev: device_destroy(key_dev->cls, MKDEV(key_dev->major, 0)); err_destory_cls: class_destroy(key_dev->cls); err_unregister: unregister_chrdev(key_dev->major, "key_dev"); err_free: kfree(key_dev); return ret; } static void __exit key_drv_exit(void) { //移除work cancel_work_sync(&key_dev->work); // 释放中断 //参数1–中断号码 //参数5–和request_irq第5个参数保持一致 int i; int irqno; for(i=0; i<ARRAY_SIZE(all_keys); i++) { irqno = all_keys[i].irqno; free_irq(irqno, &all_keys[i]); } device_destroy(key_dev->cls, MKDEV(key_dev->major, 0)); class_destroy(key_dev->cls); unregister_chrdev(key_dev->major, "key_dev"); kfree(key_dev); } module_init(key_drv_init); module_exit(key_drv_exit); MODULE_LICENSE("GPL"); 应用层app代码 #include <stdio.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <poll.h> #include <linux/input.h> // 设计一个对象–描述按键产生的数据包 struct key_event{ int code; //什么按键: 左键, 回车键 int value; // 按键的状态: 按下/抬起 (1/0) }; int main(int argc, char *argv[]) { int on; int ret; char kbd_buf[128]; struct key_event data; //直接将驱动模块当做文件来操作 int fd = open("/dev/key0", O_RDWR); if(fd < 0) { perror("open"); exit(1); } struct pollfd pfd[2]; pfd[0].fd = fd; //自己写的按键设备 pfd[0].events = POLLIN; //监控读 (POLLIN|POLLOUT|POLLERR) pfd[1].fd = 0; //监控标准输入 pfd[1].events = POLLIN; while(1) { //参数1–你需要监控的文件描述符的集合 //参数2–监控的文件的个数 //参数3–监控的时间–毫秒为单位,如果是负数,永久监控 ret = poll(pfd, 2, -1); if(ret < 0) { perror("poll"); exit(1); } if(ret > 0) { //判断是哪个有数据 if(pfd[1].revents & POLLIN) { //表示键盘是输入 ret = read(0, kbd_buf, 128); //fgets(kbd_buf, 128, stdin); kbd_buf[ret] = ''; printf("kbd_buf = %sn", kbd_buf); } if(pfd[0].revents & POLLIN) { //获取数据–不会阻塞 ret = read(pfd[0].fd, &data, sizeof(struct key_event)); //解析包 switch(data.code) { case KEY_UP: if(data.value) { printf("<app>—KEY_UP pressedn"); }else { printf("<app>—KEY_UP releasen"); } break; case KEY_DOWN: if(data.value) { printf("<app>—KEY_DOWN pressedn"); }else { printf("<app>—KEY_DOWN releasen"); } break; case KEY_LEFT: if(data.value) { printf("<app>—KEY_LEFT pressedn"); }else { printf("<app>—KEY_LEFT releasen"); } break; case KEY_RIGHT: if(data.value) { printf("<app>—KEY_RIGHT pressedn"); }else { printf("<app>—KEY_RIGHT releasen"); } break; } } } } close(fd); }

———————————————— 版权声明:本文为CSDN博主“Moonright”的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_42471952/article/details/81609141