linux input输入子系统应用分析

输入设备(如按键、键盘、触摸屏、鼠标等)是典型的字符设备,其一般的工作机理是底层在按键、触摸等动作发送时产生一个中断(或驱动通过timer定时查询),然后CPU通过SPI、I2 C或外部存储器总线读取键值、坐标等数据,放入1个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值、坐标等数据。

显然,在这些工作中,只是中断、读值是设备相关的,而输入事件的缓冲区管理以及字符设备驱动的file_operations接口则对输入设备是通用的。基于此,内核设计了输入子系统,由核心层处理公共的工作。硬件驱动层处理中断、读值相关(中断中上报读值)。

input子系统分层设计,共三层:硬件驱动层、子系统核心层、事件处理层。

驱动层由驱动程序员完成,主要处理中断(上报事件-读值)。

子系统核心层是链接其他两个层之间的纽带和桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。

事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。

设备驱动并不创建文件节点,它只负责将采集到数据通过input.c中的函数input_event() 向上一层汇报而各个事件驱动则分别将他们各自感兴趣的事件信息提取出来,通过文件节点,提供给用户。在这个过程中,input 子系统的核心负责这两层的交互工作,并管理和维护着记录了他们各自信息的链表。

驱动程序中上报的事件,input子系统已规定好,在linux/input.h中。支持的事件类型(Event types)有EV_CNT个(EV_SYN同步事件,EV_KEY按键事件,EV_REL相对坐标事件,EV_ABS绝对坐标事件,EV_MSC零散事件,EV_SW开关事件,EV_LED LED事件,EV_SND,EV_REP重复事件,EV_FF,EV_PWR,EV_FF_STATUS,EV_MAX,EV_CNE)(后几项用于服务事件类型,不代表实际事件),在代码中用“type”表示。每种事件类型都有多种属性(或选项,或编码),代码中用“code”表示,每种属性所对应的值就是要上报的数据(上面所说的“读值”)。“事件-属性-属性值”(type-code-value)唯一表征一个上报事件,“type-code”输入子系统有完整的规定,代码中通过“位掩码”表示支持该事件,该属性。举个简单例子,触摸设备支持绝对坐标事件EV_ABS,EV_ABS有ABS_CNT个code,其中ABS_X代表X坐标,ABS_Y代表Y坐标。
input_dev结构体自带事件相关数组,如下:
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
所有输入事件,内核都用统一的数据结构来描述,这个数据结构是input_event。
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
----------------------------------------------------------------------------------------------------
设备支持的事件在驱动模块加载函数中设置,一般设置方法为:
button_dev->evbit[0] = BIT_MASK(EV_KEY); //直接位赋值(兼容性差)
static inline void __set_bit(int nr, volatile unsigned long *addr); // 函数设置,非原子
static inline void set_bit(int nr, volatile unsigned long *addr); //函数设置,原子,asm/bitops/atomic.h
中断上报事件用input_event(),此外还有3个变体函数分别用于报告EV_KEY、EV_REL、EV_ABS事件。
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value);
input_sync()用于事件同步,告知事件接收者驱动已发出一个完整的报告。
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
在宋宝华的《linux设备驱动开发详解》中,介绍了通过input子系统增加输入设备的驱动工作。
>>在模块加载函数中告知input子系统设备可以报告的事情。(set_bit())
>>在模块加载函数中注册输入设备。(int input_register_device(struct input_dev *dev);)
>>在键被按下/抬起、触摸屏被触摸/抬起/移动、鼠标被移动/单击/抬起时通过input_report_xxx()报告发生的事情及对应的键值/坐标等状态。
>>在模块卸载函数中注销输入设备。(void input_unregister_device(struct input_dev *dev);)
---------------------------------------------------------------------------------------------------
事件处理层与用户程序和输入子系统核心打交道,是两层的桥梁。一般内核有好几个事件处理器,像evdev,mousedev,joydev。evdev事件处理器可以处理所有的事情。
evdev用户端结构:

1.    struct evdev_client {
2.        struct input_event buffer[EVDEV_BUFFER_SIZE];
3.            //这个是一个input_event数据结构的数组,input_event代表一个事件,基本成员:类型(type),编码(code),值(value)
4.        int head;              //针对buffer数组的索引
5.        int tail;              //针对buffer数组的索引,当head与tail相等的时候,说明没有事件
6.        spinlock_t buffer_lock; /* protects access to buffer, head and tail */
7.        struct fasync_struct *fasync;  //异步通知函数
8.        struct evdev *evdev;           //evdev设备
9.        struct list_head node;         // evdev_client 链表项
10.    }; 

这个结构在进程打开event0设备的时候调用evdev的open方法,在open中创建这个结构,并初始化。在关闭设备文件的时候释放这个结构。在read时复制到用户空间。
用户空间的应用程序如何访问设备呢,设备文件是?

1.    struct evdev {
2.        int exist;
3.        int open;           //打开标志
4.        int minor;          //次设备号
5.        struct input_handle handle;  //关联的input_handle
6.        wait_queue_head_t wait;      //等待队列,当进程读取设备,而没有事件产生的时候,进程就会睡在其上面
7.        struct evdev_client *grab;   //强制绑定的evdev_client结构,这个结构后面再分析
8.        struct list_head client_list;  //evdev_client 链表,这说明一个evdev设备可以处理多个evdev_client,可以有多个进程访问evdev设备
9.        spinlock_t client_lock; /* protects client_list */
10.        struct mutex mutex;
11.        struct device dev;       //device结构,说明这是一个设备结构
12.    };  

evdev结构体在配对成功的时候生成,由handler->connect生成,对应设备文件为/dev/input/event(n),如触摸屏驱动的event0,这个设备是用户空间要访问的设备,可以理解它是一个虚拟设备,因为没有对应的硬件,但是通过handle->dev 就可以找到input_dev结构,而它对应着触摸屏,设备文件为/dev/input/input0。这个设备结构生成之后保存在 evdev_table中,索引值是minor。设备文件通过设备号关联。
在evdev_connect()中,有设备文件初始化过程:

dev_set_name(&evdev->dev, "event%d", minor);

evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);

也可通过/proc/bus/input/devices查看设备文件,PC虚拟机下测试如下:
cat /proc/bus/input/devices
I: Bus=0019 Vendor=0000 Product=0001 Version=0000
N: Name="Power Button"
P: Phys=LNXPWRBN/button/input0
S: Sysfs=/devices/LNXSYSTM:00/LNXPWRBN:00/input/input0
U: Uniq=
H: Handlers=kbd event0
B: PROP=0
B: EV=3
B: KEY=100000 0 0 0

I: Bus=0011 Vendor=0002 Product=0005 Version=0000
N: Name="ImPS/2 Generic Wheel Mouse"
P: Phys=isa0060/serio1/input0
S: Sysfs=/devices/platform/i8042/serio1/input/input3
U: Uniq=
H: Handlers=mouse1 event3
B: PROP=0
B: EV=7
B: KEY=70000 0 0 0 0 0 0 0 0
B: REL=103
通过比较VID、PID来找到对应的usb mouse设备,然后找到对应的mouse1、event3
其实也可以不写应用程序,直接通过cat /dev/input/mouse1 | hexdump来获取鼠标数据。
一个网上示例如下:

1.    /*
2.    20150101
3.    just a simple input test code
4.    lei_wang
5.    */
6.
7.    #include <stdio.h>
8.    #include <stdlib.h>
9.    #include <unistd.h>
10.    #include <fcntl.h>
11.    #include <string.h>
12.    #include <linux/input.h>
13.
14.    int main()
15.    {
16.        int fd;
17.        int version;
18.        int ret;
19.        struct input_event ev;
20.
21.        fd = open("/dev/input/event1", O_RDONLY);
22.        if (fd < 0) {
23.            printf("open file failed\n");
24.            exit(1);
25.        }
26.
27.        ioctl(fd, EVIOCGVERSION, &version);
28.        printf("evdev driver version is 0x%x: %d.%d.%d\n",
29.                        version, version>>16, (version>>8) & 0xff, version & 0xff);
30.
31.        while (1) {
32.            ret = read(fd, &ev, sizeof(struct input_event));
33.            if (ret < 0) {
34.                printf("read event error!\n");
35.                exit(1);
36.            }
37.
38.            if (ev.type == EV_KEY)
39.                printf("type %d,code %d, value %d\n", ev.type, ev.code, ev.value);
40.        }
41.
42.        return 0;
43.    }  

参考:

  1. http://www.linuxidc.com/Linux/2011-09/43187.htm   input子系统分析
  2. http://blog.csdn.net/zhangxizhicn/article/details/6642062  linux input子系统分析
  3. http://blog.csdn.net/21cnbao/article/details/5615493  linux设备驱动的分层设计思想
  4. linux设备驱动详解    宋宝华
  5. http://blog.csdn.net/luckywang1103/article/details/42324229   input子系统—架构、驱动、应用程序
  6. http://www.cnblogs.com/leaven/archive/2011/02/12/1952793.html   linux input子系统io控制字段
时间: 2024-12-23 20:27:44

linux input输入子系统应用分析的相关文章

linux input输入子系统分析《四》:input子系统整体流程全面分析

1      input输入子系统整体流程 本节分析input子系统在内核中的实现,包括输入子系统(Input Core),事件处理层(Event Handler)和设备驱动层.由于上节代码讲解了设备驱动层的写法,因此在开头部分会从设备驱动层做为线索,分析输入子系统和事件处理层是如何配合的,最后从用户角度出发,从"/dev/input/*"接口如何使用输入子系统提供的服务. 既然需要详细分析,有一个这样的流程图能够帮助我们在被绕进代码的过程中,找到出口,你能够知道你现在位于代码框架的什

input输入子系统框架分析

input子系统的搭建要点: 核心层为事件驱动层和设备驱动层的注册提供API的实现.核心层为设备驱动层上报事件提供API的实现 .事件驱动层为应用层提供API的实现 . (1)核心层:提供事件驱动层和设备驱动层所需的函数接口(为input dev和input handler建立联) drivers/input/input.c: ##主要接口函数一览:## ①为事件驱动层提供的: 注册API: int input_register_handler(struct input_handler *han

Linux输入子系统框架分析(1)

在Linux下的输入设备键盘.触摸屏.鼠标等都可以用输入子系统来实现驱动.输入子系统分为三层,核心层和设备驱动层,事件层.核心层和事件层由Linux输入子系统本身实现,设备驱动层由我们实现.我们在设备驱动层将输入事件上报给核心层input.c,核心层找到匹配的事件层,将事件交给事件层处理,事件层处理完后传递到用户空间. 我们最终要搞清楚的是在用户空间调用open和read最终在内核中是怎样处理的,向内核上报的事件又是谁处理的,处理完后是怎样传递到用户空间的? 上面两个图是输入子系统的框架. 下面

input 输入子系统分析

//input 输入子系统分析//刘术河2016.08.22 linux-2.6.39-at91-2016.08.11-lsh\drivers\input\Input.c该文件下有input_register_device 和 input_register_handler 函数接口0.先分析input.c的核心层架构 input_init(void) class_register(&input_class); //注册一个类 register_chrdev(INPUT_MAJOR, "i

input_subsys 输入子系统框架分析

在linux内核中 已做好各类驱动的框架,驱动程序也属于内核的一部分,我们可以在原有的驱动上修改,来匹配我们自已的硬件,也可以自已编写符合内核驱动框架的驱动程序.出于学习的目的,便于更好的理解各类驱动的框架和编程思想,先分析内核自带的驱动框架和流程,再自已编写符合内核框架的驱动程序.下面开始,从输入子系统开始学习分析,后面一步一步涉及各类驱动. 一.输入子系统 从 drivers/input/input.c 这个文件开始分析,分析驱动程序的时候,先从其入口函数开始,因为每当加载一个驱动程序的时候

Linux/Android——输入子系统input_event传递

在前文Linux/Android--usb触摸屏驱动 - usbtouchscreen中记录了如何在kernel中添加input device 类型为touchscreen的驱动, 这在整个输入体系中是最下层的设备驱动部分,往上一层就是linux内核的管理驱动input系统,kernel中的源码位置:/kernel/drivers/input/input.c 撰写不易,转载需注明出处:http://blog.csdn.net/jscese/article/details/42099381 到目前

Linux/Android——输入子系统input_event传递 (二)【转】

本文转载自:http://blog.csdn.net/jscese/article/details/42099381 在前文Linux/Android——usb触摸屏驱动 - usbtouchscreen (一)中记录了如何在kernel中添加input device 类型为touchscreen的驱动, 这在整个输入体系中是最下层的设备驱动部分,往上一层就是linux内核的管理驱动input系统,kernel中的源码位置:/kernel/drivers/input/input.c 撰写不易,转

linux 的 输入子系统 与 平台设备系统个人理解

首先 ,   输入子系统跟平台设备之间没有必然联系 输入子系统,默认主设备号13,自己使用的时候要定义次设备号 ,输入子系统 ,完成一些复杂的输入功能  ,鼠标,键盘,等等输入.是一些输入设备的合集 平台设备驱动 ,是一种程序框架的,一种方式,将设备驱动的硬件配置,和软件处理分开来写. 具体知道套路就可以了,不用深究 原文地址:https://www.cnblogs.com/niuxiaojie521/p/11127257.html

输入子系统之按键驱动

上一篇博文<input输入子系统框架分析>,尝试使用这种驱动模型来看一个按键驱动程序. 下面的程序是根据韦东山老师写的代码进行修改的,我的开发板是tq2440. button.c文件: #include <linux/module.h> #include <linux/version.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/interrupt.h>