您的位置:首页 > 运维架构 > Linux

Linux--内核Uevent事件机制 与 Input子系统

2016-04-21 19:16 639 查看
目录(?)[-]

一Uevent机制

Uevent在kernel中的位置
Uevent的内部逻辑解析

二Input子系统

从应用层的角度出发看input子系统
输入设备上报事件的处理过程
通过设备节点读取输入事件
通过设备节点写入输入事件
总结

一、Uevent机制

1.前提摘要

(1)Sysfs文件系统

内核设备模型主要的模块和用户之间能看到的相关部分就是sysfs文件系统了。内核在启动的时候会注册sysfs文件系统,并且在启动系统的初期。通过mount命令挂载sysfs文件系统到/sys挂载点。

Mount -t sysfs sysfs /sys

那么sysfs文件系统的作用是什么呢。概括的说有三点:

1)、建立系统中总线、驱动、设备三者之间的桥梁

2)、像用户空间展示内核中各种设备的拓扑图

3)、提供给用户空间对设备获取信息和操作的接口,部分取代ioctl功能。

(2)Kobject:Sysfs文件系统中最基本的结构就是kobject,kobject可以代表一个设备,一条总线等。在sys目录下直观的以一个目录表示出来。

(3)Uevent机制
上面的分析其实只是对linux设备模型做了一些基础性的了解。也就是一个穿针引线的作用,如果要细致了解,需要仔细阅读代码。有了上面对于sysfs的基础。接下来我们来比较详细的了解一下uevent机制。
什么是uevent机制。这个得从热插拔设备开始说起。最简单的一个例子就是U盘了。当我们在计算机上插上一个U盘的时候,系统的USB hub会检测到U盘设备接入,并且完成设备枚举过程(从设备上读出相应的设备信息),并在内核中创建相应的设备结构体。但是,usb设备千奇百态,内核不可能预先将所有usb设备驱动都增加到内存中来。也就是当插入U盘设备的时候,内核中不一定存在对应这个设备的usb驱动。这个时候USB驱动也许以模块的形式保存在硬盘上。载入驱动必然只能从用户态来进行,那这时候应该怎么办呢?
看到这里的时候,有人一定会想,人工敲入命令载入驱动,呵呵。这必然是一种方法,但是是一种很古老的方法。Linux对类似的情况设计了一种uevent的机制。当有新的设备加入的时候,将设备的信息发送消息到用户态。而用户态有一个udev的进程监听这个信息。当收到信息后做一定的解析,根据解析到的结果和用户程序的配置做一些处理,也包括加载驱动程序。

2.具体介绍(http://www.wowotech.net/linux_kenrel/uevent.html

Uevent是Kobject的一部分,用于在Kobject状态发生改变时,例如增加、移除等,通知用户空间程序。

用户空间程序收到这样的事件后,会做相应的处理。

该机制通常是用来支持热拔插设备的,例如U盘插入后,USB相关的驱动软件会动态创建用于表示该U盘的device结构(相应的也包括其中的kobject),并告知用户空间程序,为该U盘动态的创建/dev/目录下的设备节点,

更进一步,可以通知其它的应用程序,将该U盘设备mount到系统中,从而动态的支持该设备。

Uevent在kernel中的位置



由此可知,Uevent的机制是比较简单的,设备模型中任何设备有事件需要上报时,会触发Uevent提供的接口。Uevent模块准备好上报事件的格式后。

可以通过两个途径把事件上报到用户空间:一种是通过kmod模块,直接调用用户空间的可执行文件;

另一种是通过netlink通信机制,将事件从内核空间传递给用户空间。

注1:有关kmod和netlink,会在其它文章中描述,因此本文就不再详细说明了。

Uevent的内部逻辑解析

Uevent的代码比较简单,主要涉及kobject.h和kobject_uevent.c两个文件,如下:

include/linux/kobject.h
lib/kobject_uevent.c

前面有提到过,在利用Kmod向用户空间上报event事件时,会直接执行用户空间的可执行文件。而在Linux系统,可执行文件的执行,依赖于环境变量,因此kobj_uevent_env用于组织此次事件上报时的环境变量。

说明:怎么指定处理uevent的用户空间程序(简称uevent helper)?

上面介绍kobject_uevent_env的内部动作时,有提到,Uevent模块通过Kmod上报Uevent时,会通过call_usermodehelper函数,调用用户空间的可执行文件(或者脚本,简称uevent helper )处理该event。而该uevent helper的路径保存在uevent_helper数组中。

可以在编译内核时,通过CONFIG_UEVENT_HELPER_PATH配置项,静态指定uevent helper。但这种方式会为每个event fork一个进程,随着内核支持的设备数量的增多,这种方式在系统启动时将会是致命的(可以导致内存溢出等)。因此只有在早期的内核版本中会使用这种方式,现在内核不再推荐使用该方式。因此内核编译时,需要把该配置项留空。

在系统启动后,大部分的设备已经ready,可以根据需要,重新指定一个uevent helper,以便检测系统运行过程中的热拔插事件。这可以通过把helper的路径写入到"/sys/kernel/uevent_helper”文件中实现。实际上,内核通过sysfs文件系统的形式,将uevent_helper数组开放到用户空间,供用户空间程序修改访问,具体可参考"./kernel/ksysfs.c”中相应的代码,这里不再详细描述。

3.实例分析(/article/8095194.htmlheadphone_event 上报事件的分析
本文章讲解插入headphone的时候,向上层上报event函数的整个过程

#ifdef CONFIG_I_LOVE_PBJ30

void headphone_event(int state)

{

switch_set_state(&wired_switch_dev, state);

}

EXPORT_SYMBOL_GPL(headphone_event);

#endif

headphone_event 函数会调用switch_set_state函数进行上报事件

接下来会调用kobject_uevent_env函数进行上报事件。

最终调用add_uevent_var()将用户空间需要的参数添加到环境变量中去,如

retval = add_uevent_var(env,"ACTION=%s", action_string);

if (retval)

goto exit;

retval = add_uevent_var(env,"DEVPATH=%s", devpath);

if (retval)

goto exit;

retval = add_uevent_var(env,"SUBSYSTEM=%s", subsystem);

if (retval)

goto exit;

4.实例分析2

Uevent 是内核通知Android有状态变化的一种方法,比如USB线插入、拔出,电池电量变化等等。其本质是内核发送(可以通过socket)一个字符串,应用层(android)接收并解释该字符串,获取相应信息。

(一)、Kernel侧:UEVENT的发起在Kernel端,主要是通过函数

intkobject_uevent_env(struct kobject*kobj, enum kobject_action action,char*envp_ext[])

该函数的主要功能是根据参数组合一个字符串并发送。一个典型的字符串如下:change@/devices/platform/msm-battery/power_supply/usb纮ACTION=change纮DEVPATH=/devices/platform/msm-battery/power_supply/usb纮SUBSYSTEM=power_supply纮POWER_SUPPLY_NAME=usb纮POWER_SUPPLY_ONLINE=0纮SEQNUM=1486纮

上面这块来自网上,这段内容是否有问题,待考究。

下面看这个函数: int kobject_uevent_env(structkobject *kobj, enum kobject_action action,char *envp_ext[])

static const char *kobject_actions[] ={

[KOBJ_ADD] = "add",

[KOBJ_REMOVE] = "remove",

[KOBJ_CHANGE] = "change",

[KOBJ_MOVE] = "move",

[KOBJ_ONLINE] = "online",

[KOBJ_OFFLINE] = "offline",

};

//以上为kobject标准的动作,调用时需要传入相应的enum值

///以下是获取subsystem信息

if (uevent_ops&&uevent_ops->name)

subsystem =uevent_ops->name(kset, kobj);

else

subsystem =kobject_name(&kset->kobj);

if (!subsystem) {

pr_debug("kobject: '%s' (%p):%s: unset subsystem caused the "

"event to drop!\n",kobject_name(kobj), kobj,

__func__);

return 0;

}

//下面准备要传递的信息数据

retval =
add_uevent_var(env, "ACTION=%s",action_string);

if (retval)

goto exit;

retval =
add_uevent_var(env, "DEVPATH=%s",devpath);

if (retval)

goto exit;

retval =
add_uevent_var(env, "SUBSYSTEM=%s",subsystem);

if (retval)

goto exit;

//envp_ext[i]是传进来的参数,为该event时携带的一些自定义的信息

if (envp_ext) {

for (i = 0; envp_ext[i]; i++){

retval =
add_uevent_var(env, "%s", envp_ext[i]);

if (retval)

goto exit;

//下面通过网络socket将数据发送出去

mutex_lock(&uevent_sock_mutex);

list_for_each_entry(ue_sk,&uevent_sock_list, list) {

struct sock *uevent_sock =ue_sk->sk;

struct sk_buff*skb;

size_t len;

NETLINK_CB(skb).dst_group =1;//下面开始发送数据

retval =netlink_broadcast_filtered(uevent_sock, skb,

0, 1, GFP_KERNEL,

kobj_bcast_filter,

kobj);

}

(二)、Android侧:

private finalUEventObserver mUEventObserver = newUEventObserver(){
@Override
public void onUEvent(UEventObserver.UEventevent) {
if(DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());

String state =event.get("USB_STATE");
String accessory =event.get("ACCESSORY");

//Added for USB Develpment debug, more logfor more debuging help
if(DEBUG) Log.w(TAG, "mUEventObserver:onUEvent: state = " + state);
//Added for USB Develpment debug, more logfor more debuging help

if(state != null) {
mHandler.updateState(state);
}else if ("START".equals(accessory)) {
if(DEBUG) Slog.d(TAG, "got accessory start");
setCurrentFunction(UsbManager.USB_FUNCTION_ACCESSORY,false);
}
}
};

/////在类初始化时会调用下面的动作,启动监听动作。
mUEventObserver.startObserving(USB_STATE_MATCH);

//////最终会调用到UEventObserver的addObserver:

privateArrayList<Object> mObservers = newArrayList<Object>();

public voidaddObserver(String match, UEventObserver observer) {

synchronized(mObservers){

mObservers.add(match);

mObservers.add(observer);

}

}

private static final String USB_STATE_MATCH =

"DEVPATH=/devices/virtual/android_usb/android0";

该函数最终会将”DEVPATH=/devices/virtual/android_usb/android0”增加到匹配序列中,当kernel发送具有该字符串的数据时,就返回匹配成功,然后调用mUEventObserver的onUEvent函数;

UeventObserver.Java
private static class UEventThread extendsThread {

privateArrayList<Object> mObservers = newArrayList<Object>();

UEventThread() {
super("UEventObserver");
}

public void run() {
native_setup();

byte[] buffer = new byte[1024];
int len;
while (true) {
len = next_event(buffer);
if(len > 0) {
String bufferStr = new String(buffer, 0,len); // easier to search a String
synchronized (mObservers) {
for (int i = 0; i <mObservers.size(); i += 2) {
if(bufferStr.indexOf((String)mObservers.get(i)) != -1) {
((UEventObserver)mObservers.get(i+1))
.onUEvent(newUEvent(bufferStr));
}
}

二、Input子系统

refer: http://www.cnblogs.com/myblesh/articles/2367648.html http://blog.csdn.net/bingqingsuimeng/article/details/7950543
//1. Input 子系统--概述

Android、X windows、qt等众多应用对于linux系统中键盘、鼠标、触摸屏等输入设备的支持都通过、或越来越倾向于标准的input输入子系统。

因为input子系统已经完成了字符驱动的文件操作接口,所以编写驱动的核心工作是完成input系统留出的接口,工作量不大。但如果你想更灵活的应用它,就需要好好的分析下input子系统了。








1. 输入子系统由驱动层、输入子系统核心、事件处理层三部分组成:

一个输入事件,如鼠标移动、键盘按下等通过Driver->Inputcore->Event handler->userspace的顺序到达用户控件的应用程序。

2. input子系统仍然是字符设备驱动程序,但是代码量减少很多,input子系统只需要完成两个工作:初始化和事件报告。

3. (1)上报的大致过程:设备驱动层->核心层->事件处理层->应用层

(2)具体调用的函数(以evdev为例):

input_event()->input_handle_event() ->input_pass_event() ->handle->handler->event(handle,type, code, value)

->evdev_event() ->evdev_pass_event() ,

然后通过client->buffer[client->head++]= *event赋值给上层client(是struct evdev_client)

//2. Input 子系统之一--框架结构(初始化)

1. 第一层

===============================================================================

用户空间访问 <User space> 设备节点访问(略)

@@@@@@xx_test.c

2. 第二层

===============================================================================

事件处理层<Event Handler>

/*主要是和用户空间交互。

(Linux中在用户空间将所有的设备都当初文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)*/

@@@@@@evdev.c等。(以evdev_handler为例)

(1)===>>module_init(evdev_init); module_exit(evdev_exit);

(2)===>>static int __init evdev_init(void)

{

return input_register_handler(&evdev_handler);///

/*static struct input_handler evdev_handler = {

.event = evdev_event,

.connect = evdev_connect,

.disconnect = evdev_disconnect,

.fops = &evdev_fops, ////evdev的文件操作方法

.minor = EVDEV_MINOR_BASE,

.name = "evdev",

.id_table = evdev_ids,*/

};

}

(3)===>>int input_register_handler(struct input_handler *handler)//注册一个新的event handler

{

struct input_dev *dev;

list_add_tail(&handler->node, &input_handler_list);

list_for_each_entry(dev, &input_dev_list, node);

input_attach_handler(dev, handler);////将这个新的event handler 与其兼容的input dev绑定在一起

}

(4)===>>static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)

{

id = input_match_device(handler, dev);//匹配规则:Input_dev和input_handler匹配后调用input_handler的connect。

error = handler->connect(handler, dev, id);//

}

(5)===>>创建新的evdev字符设备节点

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,

const struct input_device_id *id)

{

struct evdev *evdev;

int minor;

for (minor = 0; minor < EVDEV_MINORS; minor++)//寻找未使用的minor

if (!evdev_table[minor])

break;

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

evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);//创建字符设备节点event%d

}

3. 第三层

===============================================================================

核心层<Input Core>

/*承上启下。为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;在/Proc下产生相应的设备信息*/

@@@@@@input.c

(1)===>>subsys_initcall(input_init); module_exit(input_exit);

(2)===>>static int __init input_init(void)

{

//创建 sysfs 系统文件/proc/bus/input:devices && handlers

err = class_register(&input_class);/// struct class input_class = {

.name = "input",

.devnode = input_devnode,//kasprintf(GFP_KERNEL, "input/%s", dev_name(dev));

};

//创建proc系统文件/proc/bus/input:devices && handlers

err = input_proc_init();

if (err)

goto fail1;

//注册字符设备的文件操作input_fops

err = register_chrdev(INPUT_MAJOR, "input", &input_fops);

static const struct file_operations input_fops = {

.owner = THIS_MODULE,

.open = input_open_file,///

.llseek = noop_llseek,}

}

(3)===>>static int input_open_file(struct inode *inode, struct file *file)

{

struct input_handler *handler;

/* No load-on-demand here? */

handler = input_table[iminor(inode) >> 5];

if (handler)

new_fops = fops_get(handler->fops);

file->f_op = new_fops;/////根据传入的inode节点(即:event handler),设置相应的文件操作方法

err = new_fops->open(inode, file);////打开文件操作方法

}

4. 第四层

===============================================================================

设备驱动层<Input driver>(略)

/*将底层的硬件输入转化为统一事件形式,想输入核心(Input Core)汇报。*/

/*实现设备驱动核心工作是:向系统报告按键、触摸屏等输入事件(event,通过input_event结构描述),不再需要关心文件操作接口。

驱动报告事件经过inputCore和Eventhandler到达用户空间。*/

@@@@@@mtk_tpd.c && ft5206_driver.c

===============================================================================

/////3. Input 子系统之二--使用方式1:来自驱动层(硬件出发,如触屏等)向上层汇报事件

1. 在第四层:Input driver中需要建立一个input设备以供使用(分配、注册、注销input设备)

@@@mtk_tpd.c

(refer:http://www.csdn123.com/html/itweb/20130901/93318_93317_93316.htm)

/* global variable definitions */----------必加内容1

struct tpd_device *tpd = 0;


static int tpd_probe(struct platform_device *pdev)

{

if((tpd=(struct tpd_device*)kmalloc(sizeof(struct tpd_device), GFP_KERNEL))==NULL) return -ENOMEM;

memset(tpd, 0, sizeof(struct tpd_device));

/* allocate input device */

if((tpd->dev=input_allocate_device())==NULL) { kfree(tpd); return -ENOMEM; }----------必加内容2

/*设置input设备支持的事件类型、事件码、事件值的范围、input_id等信息*/

/*一个设备可以支持一个或多个事件类型。每个事件类型下面还需要设置具体的触发事件码。比如:EV_KEY事件,需要定义其支持哪些按键事件码。*/

/*事件类型(包括EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)

而当事件类型为EV_KEY时,按键类型keybit包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等

*/

set_bit(ABS_MT_TRACKING_ID, tpd->dev->absbit);

set_bit(ABS_MT_TOUCH_MAJOR, tpd->dev->absbit);

set_bit(ABS_MT_TOUCH_MINOR, tpd->dev->absbit);

set_bit(ABS_MT_POSITION_X, tpd->dev->absbit);

set_bit(ABS_MT_POSITION_Y, tpd->dev->absbit);

input_set_abs_params(tpd->dev, ABS_MT_POSITION_X, 0, TPD_RES_X, 0, 0);

input_set_abs_params(tpd->dev, ABS_MT_POSITION_Y, 0, TPD_RES_Y, 0, 0);

input_set_abs_params(tpd->dev, ABS_MT_TOUCH_MAJOR, 0, 100, 0, 0);

input_set_abs_params(tpd->dev, ABS_MT_TOUCH_MINOR, 0, 100, 0, 0);

if(input_register_device(tpd->dev))----------必加内容3

TPD_DMESG("input_register_device failed.(tpd)\n");

else

tpd_register_flag = 1;

初始化函数定义了input设备struct tpd_device *tpd结构体,它用于描述一个输入子系统设备。

任何驱动设备如果想标明自己是输入设备,都应该通过初始化这样的结构体,并且调用input_allocate_device()函数进行注册。这个函数的功能是为新添加的输入设备分配内存,如果成功,将返回input_dev *的指针结构,因此在写驱动的时候应该接受返回值,作为驱动层获得了一个新的输入设备操作的接口。通过input_allocate_device()函数,我们设备驱动现在持有的input_dev里面就被赋予了input的“形象”,但是还需要我们去充实一下“内在”,因此,设备驱动程序,还需要为自己的设备增加自己的特性,才能创造独有的设备“形象”。

input_allocate_device这部分完成了输入设备的初始化工作。但是这仅是初始化自己的“特点”,还需要通知输入子系统有这样一个新设备诞生了,这就需要调用输入子系统的注册函数input_register_device([u][b]tpd)[/b][/u]来完成。input_register_device()用于注册一个输入设备。那么注册过程是怎样的呢?这是一个重点,在下面的代码中进行注释分析:

1 int input_register_device(struct input_dev *dev)
2 {
3     /* 用于记录输入设备名称的索引值 */
4     static atomic_t input_no = ATOMIC_INIT(0);
5     /* 输入事件的处理接口指针,用于和设备的事件类型进行匹配 */
6     struct input_handler *handler;
7     const char *path;
8     int error;
9
10     /* 默认所有的输入设备都支持EV_SYN同步事件 */
11     set_bit(EV_SYN, dev->evbit);
12
13     /*
14      * 如果设备驱动没有指定重复按键(连击),系统默认提供以下的支持
15      * 其中init_timer为连击产生的定时器,时间到调用input_repeat_key函数
16      * 上报,REP_DELAY用于设置重复按键的键值,REP_PERIOD设置延时时间
17      */
18     init_timer(&dev->timer);
19     if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
20         dev->timer.data = (long) dev;
21         dev->timer.function = input_repeat_key;
22         dev->rep[REP_DELAY] = 250;
23         dev->rep[REP_PERIOD] = 33;
24     }
25
26     /* 如果设备驱动没有设置自己的获取键值的函数,系统默认 */
27     if (!dev->getkeycode)
28         dev->getkeycode = input_default_getkeycode;
29
30     /* 如果设备驱动没有指定按键重置函数,系统默认 */
31     if (!dev->setkeycode)
32         dev->setkeycode = input_default_setkeycode;
33
34     /* 重要,把设备挂到全局的input子系统设备链表input_dev_list上 */
35     list_add_tail(&dev->node, &input_dev_list);
36
37     /* 动态获取input设备的ID号,名称为input*,其中后面的“*”动态获得,唯一的 */
38     snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),
39          "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);
40
41     /* 如果这个值没有设置,系统把输入设备挂入设备链表 */
42     if (!dev->cdev.dev)
43         dev->cdev.dev = dev->dev.parent;
44
45     /* 在/sys目录下创建设备目录和文件 */
46     error = class_device_add(&dev->cdev);
47     if (error)
48         return error;
49
50     /* 获取并打印设备的绝对路径名称 */
51     path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);
52     printk(KERN_INFO "input: %s as %s\n",
53         dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
54     kfree(path);
55
56     /* 核心重点,input设备在增加到input_dev_list链表上之后,会查找
57      * input_handler_list事件处理链表上的handler进行匹配,这里的匹配
58      * 方式与设备模型的device和driver匹配过程很相似,所有的input
59      * 都挂在input_dev_list上,所有类型的事件都挂在input_handler_list
60      * 上,进行“匹配相亲”*/
61     list_for_each_entry(handler, &input_handler_list, node)
62         input_attach_handler(dev, handler);
63
64     input_wakeup_procfs_readers();
65
66     return 0;
67 }

上面的代码主要的功能有以下几个功能,也是设备驱动注册为输入设备委托内核做的事情:

进一步初始化输入设备,例如连击事件;
注册输入设备到input类中;
把输入设备挂到输入设备链表input_dev_list中;
查找并匹配输入设备对应的事件处理层,通过input_handler_list链表

  我们需要再分析下这个匹配的过程,但是需要注意的是下面分析的代码是我们暂时无法分析的,因为那样会使得情况变得更加复杂,当我们从应用层往下分析的时候一切都会明白。input_attach_handler匹配过程如下:



1 const struct input_device_id *id;
2     int error;
3
4 /* 如果handler的blacklist被赋值了并且则优先匹配 */
5     if (handler->blacklist && input_match_device(handler->blacklist, dev))
6         return -ENODEV;
7
8     /* 否则利用handler->id_table和dev进行匹配,后面讲述匹配什么和过程 */
9     id = input_match_device(handler->id_table, dev);
10     if (!id)
11         return -ENODEV;
12
13     /* 这是一根“红线”,虽然你可能觉的是黑色的,但不可否认,他们真的匹配上了
14          * 调用handler->connnect函数进行匹配,匹配详细过程后面讲述
15         */
16     error = handler->connect(handler, dev, id);
17     if (error && error != -ENODEV)
18         printk(KERN_ERR
19             "input: failed to attach handler %s to device %s, "
20             "error: %d\n",
21             handler->name, kobject_name(&dev->cdev.kobj), error);
22
23     return error;


View Code

  先来看下input_match_device()函数,看一下这个匹配的条件是什么,如何匹配的过程是怎样的,匹配的结果会是什么?



1 /* 事件处理层中的对应flags如果设置或者driver_info被设置则进行匹配 */
2     for (; id->flags || id->driver_info; id++) {
3         /* 以下通过flags中设置的位来匹配设备的总线类型、经销商、生产ID和版本ID
4           如果没有匹配上将进行MATCH_BIT匹配 */
5         if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
6             if (id->bustype != dev->id.bustype)
7                 continue;
8
9         if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
10             if (id->vendor != dev->id.vendor)
11                 continue;
12
13         if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
14             if (id->product != dev->id.product)
15                 continue;
16
17         if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
18             if (id->version != dev->id.version)
19                 continue;
20
21         /* MATCH_BIT用于匹配设备驱动中是否设置了这些为,MATCH_BIT的宏
22         * 被定义在input.c中,我们在设备驱动中设置的事件类型会与事件链表中的
23         * 所有事件类型进行比较,匹配成功了将返回id,证明真的很合适,否则NULL
24         */
25         MATCH_BIT(evbit,  EV_MAX);
26         MATCH_BIT(keybit, KEY_MAX);
27         MATCH_BIT(relbit, REL_MAX);
28         MATCH_BIT(absbit, ABS_MAX);
29         MATCH_BIT(mscbit, MSC_MAX);
30         MATCH_BIT(ledbit, LED_MAX);
31         MATCH_BIT(sndbit, SND_MAX);
32         MATCH_BIT(ffbit,  FF_MAX);
33         MATCH_BIT(swbit,  SW_MAX);
34
35         return id;
36     }
37
38     return NULL;


View Code

  既然证明是合适的,接下来就应该登记注册,并公证了。还记得handler->connect(handler, dev, id)函数吧。

  当input_match_device()找到最合适的事件处理层驱动时,便执行handler->connect函数进行公证了,看下面这部分代码(假如说找到了evdev类型的驱动,在input/evdev.c中):



1 struct evdev *evdev;
2     struct class_device *cdev;
3     dev_t devt;
4     int minor;
5     int error;
6
7     /* EVDEV_MINORS为32,代表共能容纳32个evdev事件层设备,下面代码在找到空的地方,用于保存evdev事件层的数据,即上面定义的evdev */
8     for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);
9     /* 这说明内核已经没办法再分配这种类型的设备了 */
10     if (minor == EVDEV_MINORS) {
11         printk(KERN_ERR "evdev: no more free evdev devices\n");
12         return -ENFILE;
13     }
14     /* 开始给evdev事件层驱动分配空间了 */
15     evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
16     if (!evdev)
17         return -ENOMEM;
18
19     /* 初始化client_list列表和evdev_wait队列,后面介绍 */
20     INIT_LIST_HEAD(&evdev->client_list);
21     init_waitqueue_head(&evdev->wait);
22
23     /* 初始化evdev结构体,其中handle为输入设备和事件处理的关联接口 */
24     evdev->exist = 1;
25     evdev->minor = minor;
26     evdev->handle.dev = dev;
27     evdev->handle.name = evdev->name;
28     evdev->handle.handler = handler;
29     evdev->handle.private = evdev;
30     sprintf(evdev->name, "event%d", minor);
31
32     /* 重要,上层访问时通过次设备号找到事件处理的接口 */
33     evdev_table[minor] = evdev;
34
35     /* evdev事件设备的此设备号的基准值INPUT_MAJOR, EVDEV_MINOR_BASE */
36     devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
37
38     /* 创建用户事件驱动层设备访问接口/dev/input/event* */
39     cdev = class_device_create(&input_class, &dev->cdev, devt,
40                    dev->cdev.dev, evdev->name);
41     if (IS_ERR(cdev)) {
42         error = PTR_ERR(cdev);
43         goto err_free_evdev;
44     }
45
46     /* 提供/sys目录的用户空间接口 */
47     error = sysfs_create_link(&input_class.subsys.kobj,
48                   &cdev->kobj, evdev->name);
49     if (error)
50         goto err_cdev_destroy;
51
52     /* input_dev设备驱动和handler事件处理层的关联,由handle完成 */
53     error = input_register_handle(&evdev->handle);


View Code

  通过上述代码的执行,最终,输入设备在input_register_handle()的关联下与已经匹配上的handler结合



1 struct input_handler *handler = handle->handler;
2     /* 将d_node链接到输入设备的h_list,h_node链接到事件层的h_list链表上
3     * 因此,在handle中是输入设备和事件层的关联结构体,通过输入设备可以
4     * 找到对应的事件处理层接口,通过事件处理层也可找到匹配的输入设备
5     */
6     list_add_tail(&handle->d_node, &handle->dev->h_list);
7     list_add_tail(&handle->h_node, &handler->h_list);
8
9     /* 如果start函数有定义则调用,但是evdev结构体中并未初始化这个函数 */
10     if (handler->start)
11         handler->start(handle);


  

以上是输入设备驱动注册的全过程,牵涉的代码比较多,需要从宏观上理顺。

  纵观整个过程:

  输入设备驱动最终的目的就是能够与事件处理层的事件驱动相互匹配,但是在drivers/input目录下有evdev.c事件驱动、mousedev.c事件驱动、joydev.c事件驱动等等,我们的输入设备产生的事件应该最终上报给谁,然后让事件驱动再去处理呢?

  知道了这么个原因再看上面代码就会明白,其实evdev.c、mousedev.c等根据硬件输入设备的处理方式的不同抽象出了不同的事件处理接口帮助上层去调用,而我们写的设备驱动程序只不过是完成了硬件寄存器中数据的读写,但提交给用户的事件必须是经过事件处理层的封装和同步才能够完成的,事件处理层提供给用户一个统一的界面来操作。

  由于以上的这些原因,才有了上述代码的关联过程,看一下整个关联注册的过程:



通过上图我们可以看到input输入设备匹配关联的关键过程以及涉及到的关键函数和数据。
  以上主要是从input设备驱动程序的角度去看输入子系统的注册过程和三层之间的关联。

2. 在第四层:在发生输入事件时,向子系统报告事件

@@@ft5206_driver.c

static void tpd_down(int x, int y, int p,int finger_id) {

// input_report_abs(tpd->dev, ABS_PRESSURE, p);

input_report_key(tpd->dev, BTN_TOUCH, 1);

input_report_abs(tpd->dev, ABS_MT_TOUCH_MAJOR, 1);

input_report_abs(tpd->dev, ABS_MT_POSITION_X, x);

input_report_abs(tpd->dev, ABS_MT_POSITION_Y, y);

input_report_abs(tpd->dev, ABS_MT_TRACKING_ID, finger_id);

//printk("D[%4d %4d %4d] ", x, y, p);

input_mt_sync(tpd->dev);

}

用于报告EV_KEY、EV_REL、EV_ABS等事件的函数有:

void input_report_key(struct input_dev *dev, unsigned int code, int value)

void input_report_rel(struct input_dev *dev, unsigned int code, int value)

void input_report_abs(struct input_dev *dev, unsigned int code, int value)

@@@第三层:input.c

注意:如果你觉得麻烦,你也可以只记住1个函数(因为上述函数都是通过它实现的):input core中的如下函数:

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);

最终调用到如下函数:

/*

* Pass event first through all filters and then, if event has not been

* filtered out, through all open handles. This function is called with

* dev->event_lock held and interrupts disabled.

*/

static void input_pass_event(struct input_dev *dev,

unsigned int type, unsigned int code, int value)

{

struct input_handler *handler;

struct input_handle *handle;

rcu_read_lock();

handle = rcu_dereference(dev->grab);

if (handle)

handle->handler->event(handle, type, code, value);

else {

bool filtered = false;

list_for_each_entry_rcu(handle, &dev->h_list, d_node) {

if (!handle->open)

continue;

handler = handle->handler;

if (!handler->filter) {

if (filtered)

break;

handler->event(handle, type, code, value);///////

} else if (handler->filter(handle, type, code, value))

filtered = true;

}

}

rcu_read_unlock();

}

@@@第二层: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,

};

/*

* Pass incoming event to all connected clients.

*/

static void evdev_event(struct input_handle *handle,

unsigned int type, unsigned int code, int value)

{

rcu_read_lock();

client = rcu_dereference(evdev->grab);///得到上层的client

if (client)

evdev_pass_event(client, &event, time_mono, time_real);////

else

list_for_each_entry_rcu(client, &evdev->client_list, node);

evdev_pass_event(client, &event, time_mono, time_real);////

rcu_read_unlock();

}

static void evdev_pass_event(struct evdev_client *client,

struct input_event *event,

ktime_t mono, ktime_t real)

{

event->time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ?

mono : real);

/* Interrupts are disabled, just acquire the lock. */

spin_lock(&client->buffer_lock);

client->buffer[client->head++] = *event;////将事件赋值给上层client(是struct evdev_client)

}

/////4. Input 子系统之二--使用方式2:来自用户空间 对 设备节点的访问(/dev/input/event%d)

(1)关于设备节点的查询方式

读取并显示/dev/input/eventX事件

$getevent -l



查看设备的major:/proc/devices/* && 访问设备节点:/dev/*

/proc/devices/中的设备是通过insmod加载到内核的,它可产生一个major(主设备号)供mknod作为 参数。

这个文件列出字符和块设备的主设备号,以及分配到这些设备号的设备名称。





/dev/* 是通过mknod加上去的(使用major),格式:mknod device1 c/b major minor 如:mknod /dev/ttyS0 c 4 64,用户通过此设备名来访问你的驱动。

/*在每一行都可以看到设备文件、设备编号(主、次);

对于每种硬件设备,系统内核有相应的设备驱动程序负责对它的处理。而在Unix 中,使用设备文件的方式来表示硬件设备,每种设备驱动程序都被抽象 为设备文件的形式,这样就给应用程序一个一致的文件界面,方便应用程序和操作系统之间的通信。

习惯上,所有的设备文件 都放置在/dev 目录下*/



查看input设备节点(1):/dev/input/*

( /dev/input目录下的事件都是在驱动中调用input_register_device(struct input_dev *dev)产生的。

每个event将上报指定的事件,如G-Sensor、触摸屏、Mouse、按键等。)



查看input设备节点(2):与/dev/input/*的event对应的相关设备信息:/proc/bus/input/devices



(2)下面将从应用层的角度分析事件的接受过程和处理过程以及三层之间是如何配合处理输入事件的。

从应用层的角度出发看input子系统

以上部分已经借助input子系统把input设备驱动层与事件驱动层进行了关联,以s3c2440_ts.c(输入设备层驱动)和evdev.c(事件处理层驱动)为例,来分析这一过程。

由于s3c2440_ts.c中上报的事件类型为按键、绝对值坐标,而evdev事件驱动程序是全匹配的,因此早在s3c2440_ts.c注册的过程中,就会创建设备节点/dev/input/event0(假设内核中没有其他的event类型的输入设备,这里就是event0)

  我们知道,应用层使用设备的第一步,是open(“/dev/event0”),因此这里event0的主设备号成为关键,因为主设备号将表明你是什么设备,我们ls -l查看/dev/event0发现:

crw-r-----1 root root 13, 64 2012-07-26 14:32 /dev/input/event0


  由此可见主设备是13,输入命令cat /proc/devices查看主设备为13的是input设备,因此可以确定当我们执行open函数打开event0设备的时候,会调用input设备的open驱动函数,这个函数在input.c中,为了说明这一问题,需要从input驱动注册过程开始,还是input.c文件:



1 /* 输入设备初始化函数 */
2 static int __init input_init(void)
3 {
4        class_register(&input_class);
5        input_proc_init();
6        register_chrdev(INPUT_MAJOR,"input", &input_fops);
7 }


View Code

  可以看到,输入设备初始化的过程首先建立了input类,初始化input在proc下的节点,然后注册input设备,设备名称为input,操作接口是input_fops,主设备号是INPUT_MAJOR=13。

  由以上可知,只要是主设备号为13的设备驱动程序,都是用input_fops接口,即当event0设备使用open函数打开时,会调用到input_fops接口中的open驱动函数,这个结构体的初始化为:



1 static const struct file_operations input_fops = {
2        .owner = THIS_MODULE,
3        .open = input_open_file,
4 };


  可以看到,只实现了一个open功能字段,再看input_open_file的实现:



1 static int input_open_file(struct inode *inode, struct file *file)
2 {
3        struct input_handler *handler =input_table[iminor(inode) >> 5];
4        const struct file_operations *old_fops,*new_fops = NULL;
5        if (!handler || !(new_fops =fops_get(handler->fops)))
6               return -ENODEV;
7        old_fops = file->f_op;
8        file->f_op = new_fops;
9        new_fops->open(inode, file);
10 }


  以上代码的功能为找到对应事件驱动层的fops,即进行fops的接口转换,指向对应设备的事件处理接口。

  其中input_table[iminor(inode)]>>5的input_table是一个全局的input_handler类型的数组,iminor(inode)取得次设备号,并且右移5位索引input_table表中对应的位置,为什么这样做呢?这是因为这个表格中填写的就是事件处理的指针,待会分析。

  继续查看下面的代码。if中将判断是否为空并且事件处理层中的fops有没有初始化,如果没有就不能进行接口转换,报出设备不存在的错误,如果设备存在则把input设备的f_op驱动接口指向input_table表中存在的接口,并调用其open函数。

  那么这个input_table里面到底存放了什么呢?我们还是拿触摸屏驱动来讲解。由于触摸屏驱动已经完成了和evdev.c事件处理层的匹配,且次设备号为64,设备名称为/dev/event0,这是我们通过分析驱动注册中获得的内容,既然input核心设备注册了,s3c2440触摸屏驱动也注册了,那会不会evdev设备也会注册了呢?答案是肯定的,要想知道input_table里面放了什么,必须要去查看evdev设备的注册过程,打开input/evdev.c查看它的注册过程:



1 static struct input_handler evdev_handler = {
2        .event =   evdev_event,                               //事件处理
3        .connect =      evdev_connect,                    //设备连接
4        .disconnect =  evdev_disconnect,                //注销连接
5        .fops =           &evdev_fops,                      //驱动功能接口
6        .minor =  EVDEV_MINOR_BASE,              //evdev的值为64
7        .name =          "evdev",                              //设备名称
8        .id_table =      evdev_ids,                           //用于匹配设备驱动的数组
9 };
10
11 static int __init evdev_init(void)
12 {
13        return input_register_handler(&evdev_handler);          //evdev设备驱动注册
14 }


View Code

  由以上的内容可以知道evdev_handler也被作为一个设备来操作,但是它属于input handler事件处理设备,然而我们在evdev_handler结构体的.fops字段又发现它的驱动接口为字符设备类型,在input中,如果input_table匹配到了evdev_handler,将会把file->f_op=&evdev_fops,那么如果使用read、write等函数操作,将会调用到evdev_fops中的read、write。

  为了进一步查看input_table表中的内容是如何填充的,还需要查看这个注册的过程:



1 int input_register_handler(struct input_handler *handler)
2 {
3     ……
4            input_table[handler->minor>> 5] = handler;
5     ……
6 }


View Code

  当然这个注册过程并不是只有这么一句话,看到这条语句,相信应该知道什么意思了。

  在input的open函数执行之前,即我们的open代码打开之前,input_table中的字段已经被事件处理层填充了。

  由于evdev的次设备号在初始化的时候就设置成了64,因此这里相当于:



input_table[2]=&evdev_handler;


View Code

  回到input_open_file函数查看new_fops->open(inode, file)便知道了调用的是:



evdev_handler.evdev_fops.open(inode, file);


View Code

  

  在分析open函数之前,解释一下为什么要右移5位?

  这说明一个问题,次设备号的低5位被忽略,这说明evdev的最大支持的输入设备驱动个数为2^5次方等于32个,你可能会看到你的/dev目录下面有event0、event1、event2等设备,他们的次设备号分别为64、65、66等等。但最大是64+32-1,因此input_table为这些输入设备增加的一个统一接口,通过上层打开设备时,只要次设备号在64+32-1之间的设备都会重新定位到evdev_handler中,即event*设备打开后执行的底层函数将被重新定义到evdev_handler中。

  相信上面的问题已经描述清楚,如果还是不明白,最起码应该知道的是,input设备中的open函数只是一个接口,通过次设备号才找到了真正的事件处理接口。接下来要看新的open接口的实现了,evdev_handler-> fops->open实现如下:



1 /*evdev字符设备驱动接口 */
2 static const struct file_operations evdev_fops = {
3        .owner = THIS_MODULE,
4        .read =           evdev_read,
5        .write =   evdev_write,
6        .poll =            evdev_poll,
7        .open =          evdev_open,
8        .release = evdev_release,
9        .unlocked_ioctl = evdev_ioctl,
10 #ifdef CONFIG_COMPAT
11        .compat_ioctl =      evdev_ioctl_compat,
12 #endif
13        .fasync = evdev_fasync,
14        .flush =   evdev_flush
15 };
16 /*evdev设备open函数的实现过程 */
17 static int evdev_open(struct inode *inode, struct file *file)
18 {
19        struct evdev_client *client;
20        struct evdev *evdev;
21        /* 如果是event0,对于evdev设备来说,次设备号当然是0 */
22        int i = iminor(inode) - EVDEV_MINOR_BASE;
23        int error;
24        /* 如果大于32,说明超出了evdev能够容纳的最大输入设备个数 */
25        if (i >= EVDEV_MINORS)
26               return -ENODEV;
27        /* 由于evdev中能容纳32个输入设备,因此通过设备号event0中的0定位到是要处理的是哪一个输入设备,evdev_table中的内容在输入设备驱动注册时通过evdev_connect填充 */
28        evdev = evdev_table[i];
29        /* 判断是否设备接口存在,evdev_exist也是在evdev_connect填充为1 */
30        if (!evdev || !evdev->exist)
31               return -ENODEV;
32        /* 存在则分配evdev中的client来处理event* */
33        client = kzalloc(sizeof(struct evdev_client),GFP_KERNEL);
34        if (!client)
35               return -ENOMEM;
36
37        /* 把event*中的接口指向evdev_table中对应项 */
38        client->evdev = evdev;
39        /* 把client->node链接到evdev子集中 */
40        list_add_tail(&client->node,&evdev->client_list);
41        /* 如果open是第一个打开,则会执行input_open_device*/
42        if (!evdev->open++ &&evdev->exist) {
43               error =input_open_device(&evdev->handle);
44               if (error) {
45                      list_del(&client->node);
46                      kfree(client);
47                      return error;
48               }
49        }
50        /* 将file私有指针指向client*/
51        file->private_data = client;
52        return 0;
53 }
54 //由上的代码可以看出,最终是要执行input_open_device去执行设备驱动程序中的代码,然而我们在定义设备驱动的时候并没有给input_dev中的open字段填充内容,因此可以看到input_open_device函数的执行过程:
55 if(!dev->users++ && dev->open)
56               err = dev->open(dev);
57
58        if (err)
59               handle->open--;


View Code

  上面截取了片段,并没有执行到open函数,open进行自减操作,表示没有调用过open,这个值主要是为了close中判断open为0时释放资源使用。

  不仅如此,我们在触摸屏驱动中也没有定义read、write,那当触摸屏上报事件时,是如何处理的呢?

  我们需要先到触摸屏驱动程序中找到上报事件的函数再做进一步分析。

输入设备上报事件的处理过程

  触摸屏驱动程序上报事件的函数为:



1 input_report_abs(dev,ABS_X, s3c2440_ts->tc.xp);
2 input_report_abs(dev,ABS_Y, s3c2440_ts->tc.yp);
3 input_report_abs(dev,ABS_PRESSURE, s3c2440_ts->tc.pressure);
4 input_report_key(dev,BTN_TOUCH, s3c2440_ts->pendown);
5 input_sync(dev);


View Code

  然而他们其实是input_event函数的封装,调用的都是input_event函数,这一函数在input.c中实现如下:



1 void input_event(struct input_dev *dev, unsigned int type, unsigned int code, intvalue)
2 {
3        struct input_handle *handle;
4
5        if (type > EV_MAX || !test_bit(type,dev->evbit))
6               return;
7        switch (type) {
8               case EV_SYN:
9                      switch (code) {
10                             case SYN_CONFIG:
11                                    if(dev->event)
12                                           dev->event(dev,type, code, value);
13                                    break;
14                             case SYN_REPORT:
15                                    if(dev->sync)
16                                           return;
17                                    dev->sync= 1;
18                                    break;
19                      }
20                      break;
21               case EV_KEY:
22               case EV_SW:
23               case EV_ABS:
24               case EV_REL:
25               case EV_MSC:
26               case EV_LED:
27               case EV_SND:
28               case EV_REP:
29               case EV_FF:
30        }
31
32        if (type != EV_SYN)
33               dev->sync = 0;
34
35        if (dev->grab)
36               dev->grab->handler->event(dev->grab,type, code, value);
37        else
38               list_for_each_entry(handle,&dev->h_list, d_node)
39                      if (handle->open)
40                             handle->handler->event(handle,type, code, value);
41 }


View Code

  代码被做了精简,其中就是在匹配上报的事件,并根据事件的类型调用驱动程序中相应的函数来完成,但是由于我们并没有定义过这些函数,因此执行最后的handle_handler_event函数,由事件处理层evdev_event函数来完成事件的保存工作,具体过程如下:



1 list_for_each_entry(client,&evdev->client_list, node) {
2     client->buffer[client->head].type= type;
3     client->buffer[client->head].code= code;
4     client->buffer[client->head].value= value;
5     client->head= (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);
6 }


View Code

  这里列举了关键代码,即上报的事件被保存到了client_buffer中,其中client_buffer是一个循环缓冲区,client->head表示当前数据的位置,因此每次都写到client->head的位置,而读数据时需要到client_tail中读取。因为在open的时候,client已经被链入到了evdev->client_list中,因此通过可以通过list_for_each_entry重evdev->client_list中找到对应的client。

  事件的上报都会把数据保存到client->buffer中,以便上层通过read和write进行读去和写入。

通过设备节点读取输入事件

事件的上报都会把数据保存到client->buffer中,以便上层通过read和write进行读去和写入。

  还是以触摸屏驱动程序和evdev事件处理层驱动来分析:



1 static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t*ppos)
2 {
3        struct evdev_client *client =file->private_data;
4        struct evdev *evdev = client->evdev;
5        int retval;
6
7        /* 判断用户给的count是否能够容纳事件数据的大小*/
8        if (count < evdev_event_size())
9               return -EINVAL;
10
11        /* 如果数据不为空并且设备存在并且是阻塞访问方式才能继续执行 */
12        if (client->head == client->tail&& evdev->exist && (file->f_flags & O_NONBLOCK))
13               return -EAGAIN;
14
15        /* 如果数据为空,设置进程等待底层驱动层上报事件到client->buffer中 */
16        retval =wait_event_interruptible(evdev->wait,
17               client->head != client->tail|| !evdev->exist);
18        if (retval)
19               return retval;
20
21        if (!evdev->exist)
22               return -ENODEV;
23
24        /* 循环读取数据 */
25        while (client->head != client->tail&& retval + evdev_event_size() <= count) {
26
27               struct input_event *event =(struct input_event *) client->buffer + client->tail;
28
29               if (evdev_event_to_user(buffer +retval, event))
30                      return -EFAULT;
31
32               client->tail = (client->tail+ 1) & (EVDEV_BUFFER_SIZE - 1);
33               retval += evdev_event_size();
34        }
35
36        return retval;
37 }


View Code

  这里如果没有数据,进程会睡眠,那由谁来唤醒呢?细心的话可以发现,当设备驱动层调用input_event上报事件调用相应的event函数进行事件写入时,是会唤醒阻塞等待的进程的。

通过设备节点写入输入事件

事件的上报都会把数据保存到client->buffer中,以便上层通过read和write进行读去和写入。

  写入过程:



1 static ssize_t evdev_write(struct file *file, const char __user *buffer, size_t count,loff_t *ppos)
2 {
3        /* 循环写入,调用input_inject_event函数 */
4        while (retval < count) {
5
6               if (evdev_event_from_user(buffer +retval, &event))
7                      return -EFAULT;
8               input_inject_event(&evdev->handle,event.type, event.code, event.value);
9               retval += evdev_event_size();
10        }
11
12        return retval;
13 }


View Code

  上述代码中的event是input_event数组,包含了事件的类型、键值,通过input_inject_event把数据写入循环数组client->buffer中,input_inject_event调用的是input_event函数。

总结

  对input子系统的整个过程做了分析,并从两个角度进行考虑.

  对于写输入设备驱动程序的来说,需要掌握的是设备应该上报事件的类型,这样才能匹配到对应的事件层驱动帮助你保存对应的数据.

  而对于设备上层开发者来说,应该先使用cat /proc/bus/input/devices查看你操作的设备类型和处理接口,以帮助你更好的对设备操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: