【转载】input子系统
转载连接:https://blog.csdn.net/qq_41076734/article/details/116754820
1. Linux中input子系统介绍
input驱动程序是Linux输入设备的驱动程序,分成游戏杆(joystick)、鼠标(mouse和mice)、键盘(keyboard)、事件设备(event)。其中事件设备驱动程序是目前通用的驱动程序,可支持键盘、鼠标、触摸屏等多种输入设备。
事件设备驱动程序(event)是通用的,可以包括所有类型的输入设备,也是目前的主流。
Linux input子系统将一个输入设备的输入过程分成了设备驱动(input device driver)和事件驱动(input event driver)两个层。前者负责从硬件设备采集数据,后者负责与用户程序对接,将采集的数据分发给不同的用户接口。通过这样的设计,将千差万别的输入设备统一到了为数不多的几种驱动接口上。同一种事件驱动可以用来处理多个同类设备,同一个设备也可以和多种事件按驱动相衔接。事件驱动和设备驱动则由输入核心层进行连接、匹配。
上:输入事件驱动层 (打包数据,面向应用)
中:输入核心层 (向下提供注册接口,向上给具体的hander发送数据)
下:输入设备驱动层 (底层驱动,面向硬件)
2. input输入子系统工作过程
以一次鼠标按下事件为例来说明:
设备驱动层:鼠标左键按下,触发中断(中断是早就注册好的),在中断服务函数中读取硬件寄存器来判断按下的是哪个按键和状态--->
input core层:中断服务函数中得到的按键信息会上报给input core层,input core层处理好了之后就会上报给input event层--->
input event层:input event层将收到的输入事件封装成一个input_event结构体放入一个缓冲区中--->
应用层:应用层read就会将缓冲区的数据读取出去
3. input子系统中的四个对象
input_device:代表着具体的输入设备,它直接从硬件中读取数据,并以事件的形式转发
handle:用于将input_device和handler连接起来,对应于某一个具体的设备文件
client:对应于用户程序对文件的访问接口,每open一次事件驱动,就创建一个client
4. input子系统的核心层维护着两条重要的链表
static LIST_HEAD(input_dev_list); //记录所有的输入设备
static LIST_HEAD(input_handler_list); //记录所有的事件驱动
每当一个新的设备或者一个新的事件驱动被系统加载(调用input_register_device()或input_register_driver()),都会扫描整个链表,并调用函数input_match_device尝试配对工作,input_handler->id_table记录了需要匹配的特征。
5. input子系统的代码框架
5.1 核心层
input子系统本身的注册如下:
input_init(void) class_register(&input_class); /* sysfs的class下出现input文件夹 */ input_proc_init(); /* 初始化input的proc文件系统 */ register_chrdev(INPUT_MAJOR, "input", &input_fops); /* 注册字符设备 */
5.2 设备驱动层
input的dev注册过程如下:
input = input_allocate_device(); /* 申请一个设备空间 */ ...... /* 初始化input里面的数据 */ input_register_device(input); /* 注册该设备 */ ...... /* 各种填充input里面的变量 */ device_add(&dev->dev); /* 经过此步骤后sysfs的class里面的input下面会多出来一个inputx,里面展示的name等就是前面填充和初始化的 */ list_add_tail(&dev->node, &input_dev_list); /* 增加该dev到核心层维护的dev链表中 */ list_for_each_entry(handler, &input_handler_list, node) input_attach_handler(dev, handler); /* 尝试匹配handler */ input_match_device(handler, dev) /* 匹配过程(算法) */ handler->connect(handler, dev, id); /* 匹配上了则连接两者到handle,并创建设备(假设匹配到了evdev) */ ....... /* 分配次设备号,申请一个struct evdev,并初始化里面的链表,等待队列等等 */ input_register_handle(&evdev->handle); /* 绑定dev到handle,handler在初始化这里以及绑定了 */ evdev_install_chrdev(evdev); /* 把该evdev添加到evdev维护的数组中 */ device_add(&evdev->dev); /* 在sysfs的 class里的input文件夹下创建eventx,为应用层提供接口 */
5.3 事件驱动层
input的handler注册过程如下:
input_register_handler(&evdev_handler); input_table[handler->minor >> 5] = handler; /* 注册该handler类型 */ list_add_tail(&handler->node, &input_handler_list); /* 把该handler加入到核心层维护的handler链表 */ list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler); /* 尝试匹配dev */ input_match_device(handler, dev) /* 匹配过程(算法) */ handler->connect(handler, dev, id); /* 匹配上了则连接两者到handle */ ....... /* 分配次设备号,申请一个struct evdev,并初始化里面的链表,等待队列等等 */ input_register_handle(&evdev->handle); /* 绑定dev到handle,handler在初始化这里以及绑定了 */ evdev_install_chrdev(evdev); /* 把该evdev添加到evdev维护的数组中 */ device_add(&evdev->dev); /* 在sysfs的 class里的input文件夹下创建eventx,为应用层提供接口 */
5.4 事件如何从设备驱动层传递到应用层(以按键事件为例)
input_report_key(button_dev, KEY_LEFT, !gpio_get_value(S5PV210_GPH0(2))); input_event(dev, EV_KEY, code, !!value); input_handle_event(dev, type, code, value); ..... /* 各种分析按键类型等 */ input_pass_event(dev, type, code, value); /* 通过dev->hlist找到handle */ handle->handler->event(handle, type, code, value); /* 进而通过handle找到handler以及里面的event函数 */ ....... /* event里面打包数据成input_event格式 */ evdev_pass_event(client, &event); /* 把数据放到环形数组,并通知上层 */ client->buffer[client->head++] = *event; /* 数据放到缓冲区 */ kill_fasync(&client->fasync, SIGIO, POLL_IN) /* 异步通知应用层 */ wake_up_interruptible(&evdev->wait); /* 唤醒等待队列 */
5.5 应用层如何读取一个事件
evdev_read ...... /* 错误分析 */ retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist); /* 睡眠,自己会进入等待队列,等待被唤醒 */ /* 执行到这里说明发生了事件,是被上面的等待队列唤醒的 */ evdev_fetch_next_event(client, &event)); *event = client->buffer[client->tail++]; /* 将buffer中数据取走*/ input_event_to_user(buffer + retval, &event); copy_to_user(buffer, event, sizeof(struct input_event)); /* 将数据拷贝到用户空间 */
5.6 应用层如何打开一个设备
evdev_open evdev = evdev_table[i]; /* 通过次设备号得到该event,event是在connect中放的 */ ...... /* 申请一个client并初始化,即使是同一个event,打开多次也要申请多个event */ evdev_attach_client(evdev, client); /* 把该client加入到该evdev的打开链表中 */ evdev_open_device(evdev); ...... /* 判断是不是已经打开了,已经打开的就单纯的把evdev里的open打开次数+1,如果打开次数是0,测需要条用核心层的open */ input_open_device(&evdev->handle); ...... /* 核心层则通过handlr里面的open计数来打开次数 ,同时该设备也有自己的user打开次数统计*/ dev->open(dev); /* 核心层检查发现确实是第一次打开,并且设备驱动层有定义open函数,则继续调用设备驱动层的open函数 */ nonseekable_open(inode, file); /* 打开完后,把该打开模式设置为不能使用seek读取(输入只能一包一包读,不能跳着读) */
7. 个人总结(现有水平有限,可能总结得不对):
7.1 浅谈绑定机制
总线设备(platform device或者i2c device等等),它们都调用了driver_register和device_add两个设备模型接口,这两个接口在执行设备的probe函数之前,会将driver结构体挂接到对应的device结构体上。
input子系统(非总线设备)和总线设备不太一样,注册设备驱动时并没有使用driver_register接口,它自己实现了一套绑定机制,dev和handler匹配以后,将两者都添加到另一个数据结构handle中(称之为绑定),上报事件的过程中先找到handle,然后在handle中找到相应的dev和handler。
7.2 驱动中等待队列的惯用手法
应用层读操作的时候很可能会遇到要读的内容还没准备好,所有这个时候读进程需要休眠,使用等待队列入队;
驱动层上报事件完毕之后(缓冲区中有了内容),唤醒等待队列(应用层的读进程被唤醒)。
8.实例代码
触摸屏按键产生中断代码实例 #include <linux/init.h> #include <linux/module.h> #include <linux/input.h> #include <linux/interrupt.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/gpio_keys.h> #include <linux/platform_device.h> #include <linux/fs.h> #include <linux/irq.h> #include <linux/sched.h> #include <linux/pm.h> #include <linux/slab.h> #include <linux/sysctl.h> #include <linux/proc_fs.h> #include <linux/delay.h> #include <linux/workqueue.h> #include <linux/gpio.h> #include <linux/of_platform.h> #include <linux/of_gpio.h> #include <linux/spinlock.h> #include <asm/io.h> #include <asm/gpio.h> #define IMX_GPIO_NR(bank, nr) (((bank) -1) * 32 + (nr)) #define GPIO1_31 IMX_GPIO_NR(1,31) static struct input_dev *inputdev; struct work_struct my_wq; void my_wq_func(struct work_struct *work); struct timer_list my_timer; int irq; /*top half*/ static irqreturn_t input_gpio_irq_handler(int irq, void *dev_id) { printk(KERN_ALERT "-----------input_gpio_irq_handler------\n" ); schedule_work(&my_wq); return IRQ_HANDLED; } /*bottom half*/ static void my_do_work(struct work_struct *work) { gpio_direction_input(GPIO1_31); //mod_timer(&my_timer, jiffies + HZ/99); my_timer.expires = jiffies + HZ/100; add_timer(&my_timer); } static void my_timer_function(unsigned long arg) { if(!gpio_get_value(GPIO1_31)) { input_event(inputdev, EV_KEY, KEY_POWER, 0); input_sync(inputdev); printk(KERN_ALERT "A\n"); } else{ input_event(inputdev, EV_KEY, KEY_POWER, 1); input_sync(inputdev); printk(KERN_ALERT "B\n"); }
//my_timer.expires = jiffies + HZ/100; //add_timer(&my_timer); } static int __init gpio_input_init(void) { printk(KERN_ALERT "-------hsae_gpio_init--------\n"); int ret;
/*分配输入设备*/ inputdev = input_allocate_device(); if (inputdev == NULL) { printk(KERN_ALERT "input_allocate_device error!\n"); return -ENOMEM; } /*添加/sys/class/event/device信息*/ inputdev->name = "qiwangwang"; inputdev->phys = "gpio-keys/input0"; inputdev->id.bustype = BUS_HOST; inputdev->id.vendor = 0x0001; inputdev->id.product = 0x0001; inputdev->id.version = 0x0100; /*初始化支持的事件类型*/ __set_bit(EV_KEY, inputdev->evbit); /*设置支持的事件码*/ __set_bit(KEY_POWER,inputdev->keybit); /*注册输入设备*/ ret = input_register_device(inputdev); if (ret != 0) { printk(KERN_ALERT "input_register_device error!\n"); goto err_0; } /*获取中断号*/ irq = gpio_to_irq(GPIO1_31); /*注册中断*/ ret = request_irq(irq, input_gpio_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "hsae_gpio_irq", NULL) if(ret < 0) { printk(KERN_ALERT "----request_irq failed----\n"); goto err_1; } /*初始化工作队列*/ INIT_WORK(&my_wq, my_do_work); /*初始化定时器,用于按键消抖*/ init_timer(&my_timer); my_timer.function = my_timer_function; //add_timer(&my_timer); return 0; err_1: input_unregister_device(inputdev); err_0: input_free_device(inputdev); return ret; } static void __exit gpio_input_exit(void) { free_irq(irq, NULL); del_timer(&my_timer); input_unregister_device(inputdev); input_free_device(inputdev); } module_init(gpio_input_init); module_exit(gpio_input_exit); MODULE_LICENSE("GPL");
//应用层实例
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <linux/input.h> #include <string.h> #define DEVICE_KEY "/dev/input/event0" int main(void) { int fd = -1, ret = -1; struct input_event ev; // 第1步:打开设备文件 fd = open(DEVICE_KEY, O_RDONLY); if (fd < 0) { perror("open"); return -1; } while (1) { // 第2步:读取一个event事件包 memset(&ev, 0, sizeof(struct input_event)); ret = read(fd, &ev, sizeof(struct input_event)); if (ret != sizeof(struct input_event)) { perror("read"); close(fd); return -1; } // 第3步:解析event包,才知道发生了什么样的输入事件 printf("-------------------------\n"); printf("type: %hd\n", ev.type); printf("code: %hd\n", ev.code); printf("value: %d\n", ev.value); printf("\n"); } // 第4步:关闭设备 close(fd); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2016-07-11 [转载]git tag — 标签相关操作