1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 背景

本文基于 Linux 4.14 内核源码进行分析。

3. 简介

Linux 内核输入子系统,负责对系统中的输入设备进行管理。
一方面,它向内核空间的输入设备驱动提供数据处理的公共代码逻辑,屏蔽输入设备硬件的实现细节;另一方面,它向用户空间提供输入设备数据访问接口,用户空间应用程序可通过内核提供的访问接口,获取输入设备数据。

4. 代码实现分析

4.1 输入子系统代码目录结构

/* 输入子系统公共代码 */
drivers/input/input.c: 输入设备、事件处理对象注册、注销,输入事件数据上报等输入子系统核心代码
drivers/input/input-mt.c: 【多点】输入设备、事件处理对象注册、注销,输入事件数据上报等输入子系统核心代码drivers/input/evdev.c: 输入事件数据处理通用 input_handler
drivers/input/joydev.c: joystick 类设备输入事件数据处理 input_handler
drivers/input/mousedev.c: mouse 类设备输入事件数据处理 input_handler
drivers/input 目录下的其它 .c,.h: 输入子系统其它核心代码/* 各类输入设备驱动目录 */
drivers/input/gameport: gameport 类输入设备驱动
drivers/input/joystick: joystick 类输入设备驱动
drivers/input/keyboard: keyboard 类输入设备驱动
drivers/input/mouse: mouse 类输入设备驱动
drivers/input/touchscreen: touchscreen 类输入设备驱动
...
drivers/input/misc: 其它杂项类输入设备驱动

4.2 输入子系统初始化

4.2.1 输入数据的处理对象注册

内核输入子系统提供接口 input_register_handler() 来注册输入数据处理对象 input_handler ,这些 input_handler 最终通过输入数据处理对象句柄 input_handle 间接地绑定到输入设备 input_dev

/* 所有输入类设备公共的数据处理接口对象注册 */
evdev_init()input_register_handler(&evdev_handler)/* 所有输入类设备公共的数据处理调试信息接口对象注册 */
evbug_init()input_register_handler(&evbug_handler)/* 输入类设备 LED 灯处理接口对象注册 */
input_leds_init()input_register_handler(&input_leds_handler)/* RF 类设备事件数据处理接口对象注册 */
rfkill_init()rfkill_handler_init()input_register_handler(&rfkill_handler)/* joystick 类设备事件数据处理接口对象注册 */
joydev_init()input_register_handler(&joydev_handler)/* keyboard 类设备事件数据处理接口对象注册 */
kbd_init()/* 初始化键盘状态、各 lock 键的状态数据 */for (i = 0; i < MAX_NR_CONSOLES; i++) {kbd_table[i].ledflagstate = kbd_defleds();kbd_table[i].default_ledflagstate = kbd_defleds();kbd_table[i].ledmode = LED_SHOW_FLAGS;kbd_table[i].lockstate = KBD_DEFLOCK;kbd_table[i].slockstate = 0;kbd_table[i].modeflags = KBD_DEFMODE;kbd_table[i].kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE;}/* 键盘 LED 灯控制初始化 */kbd_init_leds()input_register_handler(&kbd_handler)/* 更新键盘 LED 灯的 tasklet 初始化 */tasklet_enable(&keyboard_tasklet);tasklet_schedule(&keyboard_tasklet);

继续看输入设备数据处理对象 input_handler 的注册流程:

input_register_handler()INIT_LIST_HEAD(&handler->h_list);/* 添加到 输入事件处理对象 到全局列表 @input_handler_list */list_add_tail(&handler->node, &input_handler_list);/* 绑定 输入数据处理对象 到 输入设备的 【场景1】 */list_for_each_entry(dev, &input_dev_list, node)input_attach_handler(dev, handler) /* 细节参考后续分析 */

4.2.2 输入设备的创建和注册

4.2.2.1 输入设备的创建

内核输入子系统提供接口 input_allocate_device() 创建输入设备 input_dev ,具体流程如下:

input_allocate_device()static atomic_t input_no = ATOMIC_INIT(-1);struct input_dev *dev;dev = kzalloc(sizeof(*dev), GFP_KERNEL);dev->dev.type = &input_dev_type;dev->dev.class = &input_class;...init_timer(&dev->timer);INIT_LIST_HEAD(&dev->h_list);INIT_LIST_HEAD(&dev->node);dev_set_name(&dev->dev, "input%lu",(unsigned long)atomic_inc_return(&input_no));...return dev;

4.2.2.2 输入设备的配置

驱动在调用 input_allocate_device() 创建输入设备 input_dev 后,通常对设备支持的特性进行配置,以键盘类设备举例,代码片段如下:

input_dev->name = "ttp229-keypad";
input_dev->dev.parent = &ttp229->pdev->dev;/* 配置设备支持的按键 */
for(i = 0; i < ARRAY_SIZE(key_hash_tb); i++)input_set_capability(input_dev, EV_KEY, key_hash_tb[i].code);
__set_bit(EV_REP, input_dev->evbit); /* 启用设备按键自动 repeat 支持 */ttp229->input_dev = input_dev;

完整的代码驱动可参考博文 linux input: TTP229触摸键盘驱动 获取。

4.2.2.3 输入设备的注册

创建、配置输入设备 input_dev 后,接下来是将输入设备注册到系统。内核输入子系统提供接口 input_register_device() 注册输入设备,具体流程如下:

input_register_device().../* Every input device generates EV_SYN/SYN_REPORT events. */__set_bit(EV_SYN, dev->evbit);.../* 预估输入数据帧的大小 */packet_size = input_estimate_events_per_packet(dev);if (dev->hint_events_per_packet < packet_size)dev->hint_events_per_packet = packet_size;/* 为数据帧预分配空间。设备事件产生时,将事件数据填入其中。 */dev->max_vals = dev->hint_events_per_packet + 2;dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);/* 按键自动 repeat 的默认延时和周期设置 */if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD])input_enable_softrepeat(dev, 250, 33);error = device_add(&dev->dev); /* 添加设备对象到设备驱动模型 *//* * 在内核日志中打印输入设备对象,在设备驱动对象架构中的完整路径信息。* 如:input: r_gpio_keys as /devices/platform/r_gpio_keys/input/input0 */path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);pr_info("%s as %s\n",dev->name ? dev->name : "Unspecified device",path ? path : "N/A");kfree(path);/* 添加到输入设备对象 input_dev 的全局列表 @input_dev_list */list_add_tail(&dev->node, &input_dev_list); /* 绑定 输入数据处理对象 到 输入设备的 【场景2】 */list_for_each_entry(handler, &input_handler_list, node)input_attach_handler(dev, handler); /* 细节参考后续分析 */

前面没有分析输入设备 input_devinput_handler 的绑定流程,在这里分析一下:

input_attach_handler(dev, handler)/* 看输入设备 @dev 和 输入数据处理对象 @handler 是否匹配? */id = input_match_device(handler, dev);if (!id) /* 彼此不匹配 */return -ENODEV;/** 绑定输入设备对象 @dev 和 输入事件处理对象 @handler: * evdev_connect()* evbug_connect()* input_leds_connect()* kbd_connect()* joydev_connect()* kgdboc_reset_connect()* mousedev_connect()* sysrq_connect()* ...* 这里只看 evdev_connect() 的实现细节,其它类设备的代码,感兴趣的读者可自行阅读。*/handler->connect(handler, dev, id) = evdev_connect()struct evdev *evdev;.../* 输入事件字符设备 /dev/input/eventX 次设备号分配 */minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);/* 创输入事件处理对象 */evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);...init_waitqueue_head(&evdev->wait); /* 输入事件字符设备的进程等待队列初始化 */evdev->exist = true;dev_no = minor;dev_set_name(&evdev->dev, "event%d", dev_no);evdev->handle.dev = input_get_device(dev);evdev->handle.name = dev_name(&evdev->dev);evdev->handle.handler = handler;evdev->handle.private = evdev;evdev->dev.devt = MKDEV(INPUT_MAJOR, minor); /* 设置事件字符设备的主次设备号 */evdev->dev.class = &input_class;.../* 通过 input_handle 将 input_handler 绑定到 input_dev */input_register_handle(&evdev->handle)struct input_handler *handler = handle->handler;struct input_dev *dev = handle->dev;.../* 将 input_handler 通过 input_handle 绑定到 input_dev */ if (handler->filter)list_add_rcu(&handle->d_node, &dev->h_list);elselist_add_tail_rcu(&handle->d_node, &dev->h_list);/* 将 input_handle 关联到 input_handler */ list_add_tail_rcu(&handle->h_node, &handler->h_list);/* 启动 input_handler */if (handler->start)handler->start(handle); /* 如 kbd_start(), rfkill_start(), ... *//* 事件字符设备的初始化和注册 */cdev_init(&evdev->cdev, &evdev_fops);cdev_device_add(&evdev->cdev, &evdev->dev);

用一张图来总结一下 input_dev, input_handle, input_handlerev_dev, evdev_client 之间的关系,如下:

4.3 输入事件的上报

内核输入子系统提供通用接口 input_event() 上报输入事件数据。为方便各类型的输入设备驱动,对 input_event() 的进行封装,又提供了下列接口上报输入事件数据:

input_report_key()
input_report_rel()
input_report_abs()
...
input_sync()
input_mt_sync()

我们还是以键盘类设备为例,看一下输入事件上报的流程:

ttp229_key_report()if (ttp229->state == new_state) /* long tap not support now!!! */return;input_report_key(ttp229->input_dev, ttp229_key_hash(new_state == 0xFFFF ? ttp229->state : new_state), new_state == 0xFFFF ? 0 : 1)input_event(dev, EV_KEY, code, !!value)input_handle_event(dev, type, code, value)struct input_value *v;v = &dev->vals[dev->num_vals++];v->type = type;v->code = code;v->value = value;...input_pass_values(dev, dev->vals, dev->num_vals)/* 将事件数据传递给挂接在输入设备 input_dev 上 input_handler 处理 */list_for_each_entry_rcu(handle, &dev->h_list, d_node)if (handle->open) {count = input_to_handler(handle, vals, count)/* * 将通用事件数据传递给具体的 input_handler 处理。* evdev_event(), kbd_event(), joydev_event(),...*/handler->event(handle, v->type, v->code, v->value)evdev_event()struct input_value vals[] = { { type, code, value } };evdev_events(handle, vals, 1)/* 传递设备事件数据到 open() 打开的客户端 (evdev_client) */list_for_each_entry_rcu(client, &evdev->client_list, node)evdev_pass_values(client, vals, count, ev_time)for (v = vals; v != vals + count; v++) {                                                                                              event.type = v->type;event.code = v->code;event.value = v->value;/* 将事件数据传递给用户侧 */__pass_event(client, &event)client->buffer[client->head++] = *event;client->head &= client->bufsize - 1;                                                                                                  ...}}/* 上报按键后,紧随着发送一个 EV_SYN 事件,唤醒因等待事件而陷入睡眠的进程 */                        input_sync(ttp229->input_dev)input_event(dev, EV_SYN, SYN_REPORT, 0)...evdev_pass_values(client, vals, count, ev_time)for (v = vals; v != vals + count; v++) {if (v->type == EV_SYN && v->code == SYN_REPORT) {...wakeup = true;}event.type = v->type;event.code = v->code;event.value = v->value;__pass_event(client, &event);}if (wakeup)wake_up_interruptible(&evdev->wait); /* 唤醒因等待事件而陷入睡眠的进程 */ttp229->state = new_state;

我们来简单总结一下输入设备事件数据上报的流程:
首先是驱动间接或直接通过 input_event() 将事件数据传递给输入子系统,然后输入输入子系统将数据传递给具体类型的事件处理接口,如 evdev_event(), kbd_event(), joydev_event() 等;然后这些接口将通用事件数据格式转换为具体类型的事件数据格式,然后放入用户空间事件数据查询客户端 evdev_client 的数据缓冲,供用户空间应用程序读取。

4.4 输入事件的读取

输入子系统通过字符设备接口,让用户空间访问输入设备的事件数据。看一下用来读取按键数据的用户空间代码:

#define KEYBOARD_EVENT_DEVICE "/dev/input/event2" // 随意写的设备名,要根据具体情况设定int fd;
struct input_event event;fd = open(KEYBOARD_EVENT_DEVICE, O_RDONLY);
read(fd, &event, sizeof(event));
if (event.type == EV_KEY) { // 按键事件// 按键码switch (event.code) {case KEY_1: ... break;...}// 按键状态:0 松开,1 按下,2 按住if (event.value == 0) {// 按键松开处理} else if (event.value == 1) {// 按键按下处理} else if (event.value == 2) {// 按键按住处理}
}close(fd);

上述是用户空间读取按键事件数据的逻辑,内核空间的处理流程如下:

sys_open()...evdev_open()unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);struct evdev_client *client;client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);client->bufsize = bufsize;client->evdev = evdev;evdev_attach_client(evdev, client);list_add_tail_rcu(&client->node, &evdev->client_list);evdev_open_device(evdev)if (!evdev->exist)retval = -ENODEV;else if (!evdev->open++) {...}...file->private_data = client;sys_read()...evdev_read()size_t read = 0;...for (;;) {...while (read + input_event_size() <= count &&evdev_fetch_next_event(client, &event)) {/* 将读取的事件数据,拷贝到用户空间 */if (input_event_to_user(buffer + read, &event))return -EFAULT;read += input_event_size();}if (read) /* 读取到需要的数据,返回用户空间 */break;/* 当前没有数据,同时以阻塞模式读取,进程将进入睡眠状态 */if (!(file->f_flags & O_NONBLOCK)) {error = wait_event_interruptible(evdev->wait,client->packet_head != client->tail ||!evdev->exist || client->revoked);if (error)return error;}}/* 返回读取的数据量 */return read;

4.5 输入系统调试信息

/proc/bus/input/devices # 导出系统中的输入设备 input_dev
/proc/bus/input/handlers # 导出系统中的输入事件处理对象 input_handler/sys/class/input/* # 导出系统中输入设备的 device 信息# 其它
/sys/bus/serio/*
/sys/bus/gameport/*
...

5. 典型的输入设备驱动框架

// 驱动入口
int xxx_probe(...)
{struct input_dev *input_dev;// 1. 创建输入设备input_dev = input_allocate_device();// 2. 配置输入设备特性input_set_capability(input_dev, EV_KEY, KEY_1);...__set_bit(EV_REP, input_dev->evbit);// 3. 注册输入设备input_register_device(input_dev);// 4. 配置输入事件采集接口// . timer 轮询采集// . work 轮询采集// . 中断采集...
}// 驱动输入事件上报接口
void xxx_report(...)
{...input_report_key(input_dev, KEY_xxx, value);input_sync(input_dev);...
}

6. 输入子系统小结

7. 后记

限于篇幅,本文的内容远不不足以覆盖 Linux内核输入子系统 的方方面面,如果能够起到一个导读作用,本篇的目的已经达到。

8. 参考资料

内核文档:Documentation\input\*

Linux输入子系统简析相关推荐

  1. Linux内核引导简析

    bootsect.S.setup.S.head.S分析 收藏 2010-01-14 13:36:34 bootsect.S,系统引导程序,一般不超过512字节. 在PC系统结构中,线性地址0xA000 ...

  2. 4. Linux - 输入子系统框架详解

    输入子系统概述 Linux内核为了能够处理各种不同类型的输入设备,比如 触摸屏 ,鼠标 , 键盘 , 操纵杆 ,设计并实现了为驱动层程序的实现提供统一接口函数:为上层应用提供试图统一的抽象层 , 即是 ...

  3. 7.Linux 输入子系统分析

    为什么要引入输入子系统? 在前面我们写了一些简单的字符设备的驱动程序,我们是怎么样打开一个设备并操作的呢? 一般都是在执行应用程序时,open一个特定的设备文件,如:/dev/buttons 1 .. ...

  4. [arm 驱动]Linux输入子系统分析

    首先说明一下,本文是基于Linux-2.6.38版本内核来分析Linux输入子系统架构和原理的.这阵子本来没有打算花时间来分析Linux input system的,然而当在研究S3C6410触摸屏驱 ...

  5. Linux 输入子系统原理理解(原创)

    linux    输入子系统原理理解(原创) 以前学了单独的按键设备驱动以及鼠标驱动,实际上,在linux中实现这些设备驱动,有一种更为推荐的方法,就是input输入子系统.平常我们的按键,触摸屏,鼠 ...

  6. Linux输入子系统框架

    输入子系统 自己写的驱动程序,自己可以调用,我们自己写驱动的流程一般是,建立fops结构,使用register_chrdev在初始化函数中进行注册,在应用中使用open函数打开该设备.这种驱动不标准只 ...

  7. linux abs函数怎么用,Linux输入子系统详解

    原标题:Linux输入子系统详解 纯手工打造每一篇开源资讯与技术干货,数十万程序员和Linuxer已经关注. 导读 linux输入子系统由驱动层.输入子系统核心.事件处理层三部分组成.一个输入事件,如 ...

  8. linux驱动通过地址配置按键,linux输入子系统之多个按键

    文章目录 linux输入子系统:驱动多个按键 驱动多个按键 一个按键有多个与其相关的元素: 中断号码 按键状态 按键的值 input_dev的详细描述如下图 在设备树文件中增加以下信息: 按键定义配置 ...

  9. linux输入子系统

    输入子系统由驱动层.输入子系统核心.事件处理层三部分组成.一个输入事件,如鼠标移动.键盘按下等通过Driver->Inputcore->Event handler->userspac ...

最新文章

  1. oracle initialization or shutdown in progress解决方法
  2. sQLserver T-SQL 事务的用法
  3. (jQuery,SVG)使用jQuery和svg仿QQ地图测距功能(抛砖引玉)
  4. SAP Spartacus url里默认electronics-spa的由来
  5. 为什么8位数据范围是-128到127,而不是-127到128?
  6. NET流行高性能JSON框架-Json.NET
  7. as3调用java_关于openamf我用as3链接java程序,并调用相关的方法,但是能够连上,却不能够调用是怎么回事...
  8. [转载] Java8 Stream流遍历 如何使用索引
  9. 京瓷1020手动双面打印提示_自动双面打印时纸张是如何被翻转的?
  10. (4)华为ensp--RIP基本配置
  11. 阿里云负载均衡SLB简介
  12. 词消歧算法:使用WordNet和Lesk算法进行英文消歧义
  13. java 实心圆,如何用css3实现实心圆
  14. android代码 灰色,一行代码实现界面全灰(android,web,flutter)
  15. 一文搞懂候选码、主码、全码、外码、主属性、主键、主关键字、非主属性清晰总结
  16. Codeforces-686C Robbers' watch
  17. HTTP协议:三.HTTP 报文信息
  18. python玫瑰花代码
  19. python中treenode_python ElementTree 基本读操作示例
  20. 精选6个超实用的Word技巧,每一个大有用处!

热门文章

  1. final 和 abstract
  2. 【面试02】面试网络工程师,会经历什么?
  3. ctl文件去空格_CTL文件用法
  4. ajax的post跟get区别,Ajax中post与get的区别
  5. 赛尔号服务器维护时间4月27,赛尔号4月27日更新内容 赛尔号4月27日攻略大全
  6. c语言语法基础之——概念理解
  7. 嵌入式工程师是怎样炼成的(五)---- 踏实工作
  8. linux火车订票系统设计文档,linux下局域网火车时刻表广播系统设计与实现
  9. 排列组合 “n个球放入m个盒子m“问题 总结
  10. 川贝母修饰卵清蛋白(Fritillaria thunbergii-OVA/Ovalbumin )