Linux驱动-输入子系统框架
2017-09-07 20:42
447 查看
部分内容参考Linux学习之路,表示感谢.
② 在注册的fops中,仅仅只有1个open函数,下面我们来看这个open函数input_open_file
以 Evdev.c 为例来更好的进行说明
那么谁来唤醒休眠呢?
回看input.c/input_event函数
刚好与我们的例子对应:evdev_handler->evdev_event >>
设置事件的类型:
驱动函数:buttons_drv.c
① 通过控制台查看
这里有个问题就是必须按下回车才会出现回显,因为我们默认的输出设备为串口,现在将他改为tty1,使用
② 通过
那么我们这里:
刚好一一对应测试成功。
输入子系统一般将该类驱动划分为3部分,事件处理层为纯软件的东西,设备层涉及底层硬件,它们通过核心层建立联系,对外提供open write等接口。
一、核心层 input.c向外界提供接口
① 在 input_init 中注册了字符设备驱动err = register_chrdev(INPUT_MAJOR, "input", &input_fops); static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, };
② 在注册的fops中,仅仅只有1个open函数,下面我们来看这个open函数input_open_file
static int input_open_file(struct inode *inode, struct file *file) { // 定义一个input_handler指针,根据次设备号,从 input_table 数组中取出对应的 handler struct input_handler *handler = input_table[iminor(inode) >> 5]; const struct file_operations *old_fops, *new_fops = NULL; // 将 handler 的fops赋值给file->f_op,并用调用新的open函数 old_fops = file->f_op; file->f_op = fops_get(handler->fops); err = file->f_op->open(inode, file);
那么,必定有个地方创建了handler并对它进行一定的设置,并提供fops函数,将它放入input_table。
就这样,Input.c 实现了一个通用对外接口。二、事件处理层,注册input_handler
① 放入链表、数组(input_register_handler)
input.c/input_register_handler 函数中 创建了handler并对它进行一定的设置,提供fops函数,将它放入input_tableint input_register_handler(struct input_handler *handler) { // 将 handler 放入 input_table input_table[handler->minor >> 5] = handler; // 将 handler 放入 input_handler_list 链表 list_add_tail(&handler->node, &input_handler_list); // 取出 input_dev_list 链表中的每一个 dev 与 该 handler 进行 比对 list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler); }
以 Evdev.c 为例来更好的进行说明
static struct input_handler evdev_handler = { .event = evdev_event, .connect = evdev_connect, .disconnect = evdev_disconnect, .fops = &evdev_fops, .minor = EVDEV_MINOR_BASE, .name = "evdev", .id_table= evdev_ids, }; static int __init evdev_init(void) { return input_register_handler(&evdev_handler); }
我们与核心层提供的接口对应一下,假设APP需要读按键:
app: read > ... > file->f_op->read == handler->fops->read == evdev_handler->evdev_fops->evdev_read
/* 读函数中 如果没有事件上报休眠,等待上报事件 唤醒休眠,将事件传送到用户空间 */ static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { //如果无数据可读,且为非阻塞方式 立刻返回 if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK)) return -EAGAIN; //否则,进入休眠 retval = wait_event_interruptible(evdev->wait, client->head != client->tail || !evdev->exist); //将内核空间数据拷贝到用户空间,略 return retval; }
② 匹配 (input_attach_handler)
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) { // 看 dev.id 是否存在于 handler->id_table 中 id = input_match_device(handler->id_table, dev); if (!id) return -ENODEV; // 在的话,调用 handler->connect error = handler->connect(handler, dev, id); }
③ 建立连接
我们以 Evdev.c 为例,看一下connect函数static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) { // 不要关心 evdev ,只看 evdev->handle 即可,这里构建了一个 handle ,注意不是handler // handle 就是个 中间件,可以理解成胶带,它把 hander 与 dev 连在一起 evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); // 第一次建立联系,在 handle 中记录 dev 与 handle 的信息,这样通过handle就可以找到dev与handler // 即是 实现 handle -> dev handle -> hander 的联系 evdev->handle.dev = dev; evdev->handle.handler = handler; // 申请设备号,创建设备节点 devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor), dev_set_name(&evdev->dev, "event%d", minor); // 在input 类下面创建设备,文件夹的名字是 evdev->name ->inputn ,设备名是 dev->cdev.dev.name -> eventn cdev = class_device_create(&input_class, &dev->cdev, devt, dev->cdev.dev, evdev->name); // 注册 handle error = input_register_handle(&evdev->handle); }
④ 注册handle,第二次建立联系
int input_register_handle(struct input_handle *handle) { struct input_handler *handler = handle->handler; // 将handle 记录在 dev->h_list 中 list_add_tail(&handle->d_node, &handle->dev->h_list); // 将handle 记录在 handler->h_list 中 list_add_tail(&handle->h_node, &handler->h_list); // 至此,dev 与 hander 也可以找到handle了,dev <-> handle <-> handler 之间畅通无阻 }
小结:
事件处理层,构建 handler , 通过 input_register_handler 进行注册,注册时
1、将 handler 放入 input_handler_list 链表
2、将 handler 放入 input_table
3、取出 input_dev_list链表中的每一个dev 调用 input_attach_handler 进行id匹配
4、如果匹配成功,则调用 handler->connect 第一次建立连接
5、创建 handle 来进行第二次建立连接,在 handle 中记录 dev 与 handler 的信息,这样通过handle就可以找到dev与handler
6、在dev hander 中记录 handle的信息,实现 dev <-> handle <-> handler
三、设备层,注册input_dev
int input_register_device(struct input_dev *dev) { // 将 dev 放入 input_dev_list list_add_tail(&dev->node, &input_dev_list); // 匹配 handler ,参考 ①、② list_for_each_entry(handler, &input_handler_list, node) input_attach_handler(dev, handler); }
建立设备层与设备处理层联系作用
以上面Evdev.c读按键为例:假设无按键按下进如休眠模式wait_event_interruptible(evdev->wait, client->head != client->tail || !evdev->exist);
那么谁来唤醒休眠呢?
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { wake_up_interruptible(&evdev->wait); }
这样我们似乎明白了,在设备层,我们写驱动的时候,比如按键按了一下,我们要上报event 到Handler层进行处理,然后提交给用户程序。
例如:Gpio_keys.c 中断处理函数中static irqreturn_t gpio_keys_isr(int irq, void *dev_id) { // 上报事件 input_event(input, type, button->code, !!state); input_sync(input); return IRQ_HANDLED; }
回看input.c/input_event函数
input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) struct input_handle *handle; list_for_each_entry(handle, &dev->h_list, d_node) if (handle->open) handle->handler->event(handle, type, code, value);
最终调用 handler->event(handle, type, code, value);
刚好与我们的例子对应:evdev_handler->evdev_event >> wake_up_interruptible(&evdev->wait);
四、写一个基于Input子系统的设备驱动
事件处理层不用我们管了,- -是暂时能力有限管不了。写写设备层的程序就好了。
软件设计流程:
/* 1. 分配一个Input_dev结构体 */
/* 2. 设置 支持哪一类事件,该类事件里的那些事件*/
/* 3.注册 */
/* 4.硬件相关操作 */
流程图:
设置事件的类型:
/*事件类型:*/ struct input_dev { void *private; //输入设备私有指针,一般指向用于描述设备驱动层的设备结构 const char *name; //提供给用户的输入设备的名称 const char *phys; //提供给编程者的设备节点的名称 const char *uniq; //指定唯一的ID号,就像MAC地址一样 struct input_id id; //输入设备标识ID,用于和事件处理层进行匹配 unsigned long evbit[NBITS(EV_MAX)]; //位图,记录设备支持的事件类型 /* * #define EV_SYN 0x00 //同步事件 * #define EV_KEY 0x01 //按键事件 * #define EV_REL 0x02 //相对坐标 * #define EV_ABS 0x03 //绝对坐标 * #define EV_MSC 0x04 //其它 * #define EV_SW 0x05 //开关事件 * #define EV_LED 0x11 //LED事件 * #define EV_SND 0x12 * #define EV_REP 0x14<span style="white-space:pre"> </span>//重复上报 * #define EV_FF 0x15 * #define EV_PWR 0x16 * #define EV_FF_STATUS 0x17 * #define EV_MAX 0x1f */ unsigned long keybit[NBITS(KEY_MAX)]; //位图,记录设备支持的按键类型 unsigned long relbit[NBITS(REL_MAX)]; //位图,记录设备支持的相对坐标 unsigned long absbit[NBITS(ABS_MAX)]; //位图,记录设备支持的绝对坐标 unsigned long mscbit[NBITS(MSC_MAX)]; //位图,记录设备支持的其他功能 unsigned long ledbit[NBITS(LED_MAX)]; //位图,记录设备支持的指示灯 unsigned long sndbit[NBITS(SND_MAX)]; //位图,记录设备支持的声音或警报 unsigned long ffbit[NBITS(FF_MAX)]; //位图,记录设备支持的作用力功能 unsigned long swbit[NBITS(SW_MAX)]; //位图,记录设备支持的开关功能 unsigned int keycodemax; //设备支持的最大按键值个数 unsigned int keycodesize; //每个按键的字节大小 void *keycode; //指向按键池,即指向按键值数组首地址 int (*setkeycode)(struct input_dev *dev, int scancode, int keycode); //修改按键值 int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode); //获取按键值 struct ff_device *ff; unsigned int repeat_key; //支持重复按键 struct timer_list timer; //设置当有连击时的延时定时器 int state; int sync; //同步事件完成标识,为1说明事件同步完成 int abs[ABS_MAX + 1]; //记录坐标的值 int rep[REP_MAX + 1]; //记录重复按键的参数值 unsigned long key[NBITS(KEY_MAX)]; //位图,按键的状态 unsigned long led[NBITS(LED_MAX)]; //位图,led的状态 unsigned long snd[NBITS(SND_MAX)]; //位图,声音的状态 unsigned long sw[NBITS(SW_MAX)]; //位图,开关的状态 int absmax[ABS_MAX + 1]; //位图,记录坐标的最大值 int absmin[ABS_MAX + 1]; //位图,记录坐标的最小值 int absfuzz[ABS_MAX + 1]; //位图,记录坐标的分辨率 int absflat[ABS_MAX + 1]; //位图,记录坐标的基准值 int (*open)(struct input_dev *dev); //输入设备打开函数 void (*close)(struct input_dev *dev); //输入设备关闭函数 int (*flush)(struct input_dev *dev, struct file *file); //输入设备断开后刷新函数 int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); //事件处理 struct input_handle *grab; struct mutex mutex; //用于open、close函数的连续访问互斥 unsigned int users; struct class_device cdev; //输入设备的类信息 union { //设备结构体 struct device *parent; } dev; struct list_head h_list; //handle链表 struct list_head node; //input_dev链表 };
驱动函数:buttons_drv.c
#include <linux/module.h> #include <linux/version.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/sched.h> #include <linux/pm.h> #include <linux/sysctl.h> #include <linux/proc_fs.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/input.h> #include <linux/irq.h> #include <asm/gpio.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> struct pin_desc { int irq; char *name; unsigned int pin; unsigned int key_val; }; struct pin_desc pins_desc[3] = { {IRQ_EINT0, "s2",S3C2410_GPF0,KEY_L}, {IRQ_EINT2, "s3",S3C2410_GPF2,KEY_S}, {IRQ_EINT11,"s4",S3C2410_GPG3,KEY_ENTER}, }; static struct input_dev *buttons_dev; static struct pin_desc *irq_pd; static struct timer_list buttons_timer; static irqreturn_t buttons_irq(int irq, void *dev_id) { /* 设置定时器,发生中断后10ms后再去读电平值 */ irq_pd = (struct pin_desc *)dev_id; mod_timer(&buttons_timer,jiffies+HZ/100); return IRQ_RETVAL(IRQ_HANDLED); } static void buttons_timer_function(unsigned long data) { struct pin_desc *pindesc = irq_pd; unsigned int pinval; if(!pindesc) return; pinval = s3c2410_gpio_getpin(pindesc->pin); if(pinval) { /* 松开 : 最后一个参数: 0-松开,1-按下 */ input_event(buttons_dev,EV_KEY,pindesc->key_val,0); } else { /* 按下 : 最后一个参数: 0-松开,1-按下 */ input_event(buttons_dev,EV_KEY,pindesc->key_val,1); } } static int buttons_init(void) { int i; /* 1. 分配一个input_dev结构体 */ buttons_dev = input_allocate_device(); /* 2. 设置 */ /* 2.1 能产生哪类事件 */ set_bit(EV_KEY,buttons_dev->evbit); set_bit(EV_REP, buttons_dev->evbit);//用于重复事件,按下按键不松开可一直输入 /* 2.2 能产生这类事件里的哪些操作:L,S,ENTER */ set_bit(KEY_L,buttons_dev->keybit); set_bit(KEY_S,buttons_dev->keybit); set_bit(KEY_ENTER,buttons_dev->keybit); /* 3. 注册 */ input_register_device(buttons_dev); /* 4. 硬件相关的操作 */ init_timer(&buttons_timer); buttons_timer.function = buttons_timer_function; add_timer(&buttons_timer); for(i = 0 ;i < 3;i++) { request_irq(pins_desc[i].irq, buttons_irq,IRQT_BOTHEDGE,pins_desc[i].name,&pins_desc[i]); } return 0; } static void buttons_exit(void) { int i; for(i = 0;i < 3;i++) { free_irq(pins_desc[i].irq,&pins_desc[i]); } del_timer(&buttons_timer); input_unregister_device(buttons_dev); input_free_device(buttons_dev); } module_init(buttons_init); module_exit(buttons_exit); MODULE_LICENSE("GPL");
测试方法:
① 通过控制台查看cat /dev/tty1
这里有个问题就是必须按下回车才会出现回显,因为我们默认的输出设备为串口,现在将他改为tty1,使用
exec 0</dev/tty1
② 通过hexdump /dev/event1
查看用户得到的数据和驱动中的数据比对
硬件有数据产生时,调用input_event 上报时间(上报事件核心) --》handle->handler->event(handle, type, code, value);//从输入设备的h_list里面找出handle,从handle得到handler,调用它的event函数 --》evdev_event//记录按键值-->发信号--》唤醒程序,因为之前read时没数据会休眠;
那么我们这里:
open(/dev/event1)–》
read > ... >–》
evdev_handler->evdev_fops->evdev_read–》
evdev_event_to_user–》读到
input_event -->
struct input_event { struct timeval time; 时间 __u16 type; 类别(按键类、相对位移、绝对位移) __u16 code;那个位置位置 __s32 value; };//这个结构体可以支持所有的输入事件。
刚好一一对应测试成功。
相关文章推荐
- Linux驱动之输入子系统框架
- Linux输入子系统框架分析及输入设备驱动编程
- 从一个简单的sensor驱动看linux输入子系统框架。
- linux输入子系统(5) - 学习框架
- linux驱动由浅入深系列:输入子系统之三(应用层模拟input_event)
- linux驱动—input输入子系统—The simplest example(一个最简单的实例)分析(1)
- Linux驱动子系统之输入子系统
- android底层驱动学习之linux输入子系统的理解
- Linux驱动子系统之输入子系统
- linux输入子系统--按键驱动
- Linux视频驱动 学习总结之第13课(输入子系统)
- [转]linux 输入子系统驱动实例分析--gpio keys
- linux input输入子系统分析《三》:S3C2440的触摸屏驱动实例
- linux驱动模型<输入子系统>
- Linux的input输入子系统:设备驱动之按键驱动
- Linux输入子系统框架
- linux驱动子系统之输入子系统(5)
- Linux输入子系统框架-JZ2440
- linux驱动子系统之输入子系统(5)
- LINUX设备驱动之输入子系统(一)