linux内核驱动子系统,ARM Linux内核Input输入子系统浅解

--以触摸屏驱动为例

第一章、了解linux input子系统

Linux输入设备总类繁杂,常见的包括有按键、键盘、触摸屏、鼠标、摇杆等等,他们本身就是字符设备,而linux内核将这些设备的共同性抽象出来,简化驱动开发建立了一个input子系统。子系统共分为三层,如图1所示。

e04e566a400e7342a594ecaecfbc6ae8.png

图1input输入子系统

驱动层和硬件相关,直接捕捉和获取硬件设备的数据信息等(包括触摸屏被按下、按下位置、鼠标移动、键盘按下等等),然后将数据信息报告到核心层。核心层负责连接驱动层和事件处理层,设备驱动(device driver)和处理程序(handler)的注册需要通过核心层来完成,核心层接收来自驱动层的数据信息,并将数据信息选择对应的handler去处理,最终handler将数据复制到用户空间。

先了解三个定义在/linux/input.h下重要的结构体input_dev、input_handler、input_handle。

struct input_dev {

void *private;

const char *name;

const char *phys;

const char *uniq;

struct input_id id;//与input_handler匹配用的id

unsigned long evbit[NBITS(EV_MAX)];//设备支持的事件类型

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)];

int ff_effects_max;

unsigned int keycodemax;

unsigned int keycodesize;

void *keycode;

unsigned int repeat_key;

struct timer_list timer;

struct pt_regs *regs;

int state;

int sync;

int abs[ABS_MAX + 1];

int rep[REP_MAX + 1];

unsigned long key[NBITS(KEY_MAX)];

unsigned long led[NBITS(LED_MAX)];

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 (*accept)(struct input_dev *dev, struct file *file);

int (*flush)(struct input_dev *dev, struct file *file);

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

int (*upload_effect)(struct input_dev *dev, struct ff_effect *effect);

int (*erase_effect)(struct input_dev *dev, int effect_id);

struct input_handle *grab;//当前占有该设备的handle

struct mutex mutex;      /* serializes open and close operations */

unsigned int users;            //打开该设备的用户量

struct class_device cdev;

struct device *dev;      /* will be removed soon */

int dynalloc;      /* temporarily */

struct list_head      h_list;      //该链表头用于链接该设备所关联的input_handle

struct list_head      node;      //该链表头用于将设备链接到input_dev_list

};

Input_dev是一个很强大的结构体,它把所有的input设备(触摸屏、键盘、鼠标等)的信息都考虑到了,对于触摸屏来说只用到它里面的一部分而已,尤其是加粗的部分,注意该结构体中最后两行定义的两个list_head结构体,list_head在/linux/list.h中有定义,深入跟踪

struct list_head {

struct list_head *next, *prev;

};

该结构体内部并没有定义数据而只定义了两个指向本身结构体的指针,预先说明一下,所有的input device在注册后会加入一个input_dev_list(输入设备链表),所有的eventhandler在注册后会加入一个input_handler_list(输入处理程序链表),这里的list_head主要的作用是作为input_dev_list和input_handler_list的一个节点来保存地址。Input_dev_list和input_handler_list之间的对应关系由input_handle结构体桥接,具体后面说明。

struct input_handler {

void *private;

void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);

struct input_handle* (*connect)(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id);

void (*disconnect)(struct input_handle *handle);

const struct file_operations *fops;      //提供给用户对设备操作的函数指针

int minor;

char *name;

struct input_device_id *id_table;//与input_dev匹配用的id

struct input_device_id *blacklist;      //标记的黑名单

struct list_head      h_list;//用于链接和该handler相关的handle

struct list_head      node;            //用于将该handler链入input_handler_list

};

input_handler顾名思义,它是用来处理input_dev的一个结构体,相关的处理函数在结构里内部都有定义,最后两行定义的list_head结构体作用同input_dev所定义的一样,这里不再说明。

注:input_device_id结构体在/linux/mod_devicetable.h中有定义

struct input_handle {

void *private;

int open;      //记录设备打开次数

char *name;

struct input_dev *dev;      //指向所属的input_dev

struct input_handler *handler;      //指向所属的input_handler

struct list_head      d_node;//用于链入所指向的input_dev的handle链表

struct list_head      h_node;//用于链入所指向的input_handler的handle链表

};

可以看到input_handle中拥有指向input_dev和input_handler的指针,即input_handle是用来关联input_dev和input_handler。为什么用input_handle来关联input_dev和input_handler而不将input_dev和input_handler直接对应呢?因为一个device可以对应多个handler,而一个handler也可处理多个device。就如一个触摸屏设备可以对应event handler也可以对应tseve handler。

input_dev、input_handler、input_handle的关系如下图2所示。

898c49e69dc1c558a33ff5c560421005.png

图2input_dev,input_handler,input_handle关系图

第二章、inputdevice的注册

Inputdevice的注册实际上仅仅只有几行代码,因为在input.c中已经将大量的代码封装好了,主需要调用几个关键的函数就能完成对input device的注册。

在xxx_ts.c中预先定义全局变量struct input_dev  tsdev;然后进入到初始化函数

static int __init xxx_probe(struct platform_device *pdev)

{

if (!(tsdev = input_allocate_device()))

{

printk(KERN_ERR "tsdev: not enough memory\n");

err = -ENOMEM;

goto fail;

}

tsdev->name = "xxx TouchScreen";            //xxx为芯片型号

tsdev ->phys = "xxx/event0";

tsdev ->id.bustype = BUS_HOST;//设备id,用于匹配handler的id

tsdev ->id.vendor  = 0x0005;

tsdev ->id.product = 0x0001;

tsdev ->id.version = 0x0100;

tsdev ->open    = xxx_open;

tsdev ->close   =xxx_close;

tsdev ->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS) | BIT(EV_SYN);            //设置支持的事件类型

tsdev ->keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH);

input_set_abs_params(tsdev, ABS_X, 0, 0x400, 0, 0);            //限定绝对坐标X的取值范围

input_set_abs_params(tsdev, ABS_Y, 0, 0x400, 0, 0);            //同上

input_set_abs_params(tsdev, ABS_PRESSURE, 0, 1000, 0, 0);      //触摸屏压力值范围

If(input_register_device(tsdev) == error)//注册设备

goto fail;

fail:

input_free_device(tsdev);

printk(“ts probe failed\n”);

return err;

}

先看该函数中的tsdev = input_allocate_device(),深入最终,该函数在input.c中有定义

struct input_dev *input_allocate_device(void)

{

struct input_dev *dev;

dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);

if (dev) {

dev->dynalloc = 1;

dev->cdev.class = &input_class;

class_device_initialize(&dev->cdev);

INIT_LIST_HEAD(&dev->h_list);

INIT_LIST_HEAD(&dev->node);

}

return dev;

}

学过C语言应该都知道malloc函数(开辟内存空间),而这里的kzalloc也类似于C语言中的malloc一样,是linux内核空间分配内存函数,后面的GFP_KERNEL标志意为常规的内存分配,更多的分配标志可参阅先关资料(我也不懂)。再回到前面的函数中来,接着后面的代码为对tsdev结构体中的成员进行赋值初始化,赋值完成后就要开始进入主题:注册设备了,进入到函数input_register_device(tsdev),该函数在input.c中有定义。

int input_register_device(struct input_dev *dev)

{

static atomic_t input_no = ATOMIC_INIT(0);      //定义原子变量,禁止线程并发访问

struct input_handle *handle;            //定义一些变量备后文使用

struct input_handler *handler;

struct input_device_id *id;

const char *path;

int error;

if (!dev->dynalloc) {

printk(KERN_WARNING "input: device %s is statically allocated, will not register\n"

"Please convert to input_allocate_device() or contact dtor_core@ameritech.net\n",

dev->name ? dev->name : "");

return -EINVAL;

}

mutex_init(&dev->mutex);            //互斥锁初始化,防止临界区代码被并发访问

set_bit(EV_SYN, dev->evbit);            //设置支持同步事件,input设备全部默认支持同步事件

/*

* If delay and period are pre-set by the driver, then autorepeating

* is handled by the driver itself and we don't do it in input.c.

*/

init_timer(&dev->timer);

if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {

dev->timer.data = (long) dev;

dev->timer.function = input_repeat_key;

dev->rep[REP_DELAY] = 250;

dev->rep[REP_PERIOD] = 33;

}

INIT_LIST_HEAD(&dev->h_list);      //初始化需要关联的handle链表头

list_add_tail(&dev->node, &input_dev_list);      //将设备添加到input_dev_list中

dev->cdev.class = &input_class;

snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),

"input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);

error = class_device_add(&dev->cdev);

if (error)

return error;

error = sysfs_create_group(&dev->cdev.kobj, &input_dev_attr_group);

if (error)

goto fail1;

error = sysfs_create_group(&dev->cdev.kobj, &input_dev_id_attr_group);

if (error)

goto fail2;

error = sysfs_create_group(&dev->cdev.kobj, &input_dev_caps_attr_group);

if (error)

goto fail3;

__module_get(THIS_MODULE);

path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);

printk(KERN_INFO "input: %s as %s\n",

dev->name ? dev->name : "Unspecified device", path ? path : "N/A");

kfree(path);

/***遍历input_handler_list上全部的handler,寻找与该设备匹配的handler  ***/

list_for_each_entry(handler, &input_handler_list, node)

if (!handler->blacklist || !input_match_device(handler->blacklist, dev))

if ((id = input_match_device(handler->id_table, dev)))

if ((handle = handler->connect(handler, dev, id)))

input_link_handle(handle);

input_wakeup_procfs_readers();

return 0;

fail3:      sysfs_remove_group(&dev->cdev.kobj, &input_dev_id_attr_group);

fail2:      sysfs_remove_group(&dev->cdev.kobj, &input_dev_attr_group);

fail1:      class_device_del(&dev->cdev);

return error;

}

先看函数中前面代码加粗的部分mutex_init(&dev->mutex),与互斥锁相关的东西(我也不太懂),set_bit(EV_SYN, dev->evbit)设置支持同步事件,linux的input子系统默认要支持同步事件。

接着看中间代码加粗的部分,有关list操作的在/linux/list.h中有定义

static inline void INIT_LIST_HEAD(struct list_head *list)

{

list->next = list;

list->prev = list;

}

static inline void list_add_tail(struct list_head *new, struct list_head *head)

{

__list_add(new, head->prev, head);

}

static inline void __list_add(struct list_head *new,

struct list_head *prev,

struct list_head *next)

{

next->prev = new;

new->next = next;

new->prev = prev;

prev->next = new;

}

可以看得出INIT_LIST_HEAD(struct list_head *list)就是让list指向结构体的成员再指向其本身完成初始化操作,list_add_tail(struct list_head *new, struct list_head *head)是将new所指向的结构体作为一节点插入到head所指向链表节点之前。因此函数中的list_add_tail(&dev->node, &input_dev_list)就是将该设备添加到input_dev_list中。而input_dev_list这个双向链表在什么时候被定义了呢,且看input.c源代码开始部分全局部分有定义:

static LIST_HEAD(input_dev_list);

static LIST_HEAD(input_handler_list);

而LIST_HEAD的宏定义:

#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \

struct list_head name = LIST_HEAD_INIT(name)

显然这在最开始就已经定义了input_dev_list和input_handler_list这两个链表,且这两个链表都只有一个节点,而在device和handler注册的时候会在这两条链表中加入节点,list_add_tail(&dev->node, &input_dev_list)就是将该设备添加到input_dev_list中。

注意最后代码加粗的部分,该部分完成了input_dev和input_handler的桥接。先看list_for_each_entry(handler, &input_handler_list, node),list_for_each_entry在list.h中有定义,其作用相当于一个for循环。其约等价于:(个人理解)

for(handler = input_handler_list表头所属的input_handler结构体地址;

handler != input_handler_list表尾所属的input_handler结构体地址;

handler++)

即handler第一次指向input_handler_list的头部所在的input_handler地址。每循环一次handler就沿着input_handler_list移动到下一个节点,得到下一节点所属的handler地址,直到input_handler_list的结尾。而每次的循环需要做什么呢?要做的就是遍历input_handler_list上的每一个handler,看有哪些handler能与该设备匹配的上。匹配过程:

if (!handler->blacklist || !input_match_device(handler->blacklist, dev))//判断该handler没有被列入黑名单或者黑名单匹配不成功的话则继续

if ((id = input_match_device(handler->id_table, dev)))//将设备id与handler的id进行匹配,成功则继续往下

if ((handle = handler->connect(handler, dev, id)))//链接device与handler,成功则继续往下

input_link_handle(handle);//将handle链入input_handler_list和input_dev_list

继续跟踪进这些函数

static struct input_device_id *input_match_device(struct input_device_id *id, struct input_dev *dev)

{

int i;

for (; id->flags || id->driver_info; id++) {

if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)      //匹配handler和device  id的flag标志位

if (id->bustype != dev->id.bustype)

continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)

if (id->vendor != dev->id.vendor)

continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)

if (id->product != dev->id.product)

continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)

if (id->version != dev->id.version)

continue;

MATCH_BIT(evbit,  EV_MAX);            //匹配id相关标志位

MATCH_BIT(keybit, KEY_MAX);

MATCH_BIT(relbit, REL_MAX);

MATCH_BIT(absbit, ABS_MAX);

MATCH_BIT(mscbit, MSC_MAX);

MATCH_BIT(ledbit, LED_MAX);

MATCH_BIT(sndbit, SND_MAX);

MATCH_BIT(ffbit,  FF_MAX);

MATCH_BIT(swbit,  SW_MAX);

return id;

}

return NULL;

}

该函数用于匹配input_dev结构体和input_handler结构体里面定义的input_device_id,若两者里面有任何相关变量不一样则匹配失败(条件真苛刻)。

再看handle = handler->connect(handler, dev, id),connect函数即为static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id),为什么呢会是这个呢,我们先看evdev.c中的input_handler结构体定义:

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,

};

可只这里input_handler结构体里边的connect函数即为evdev_connect函数

static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id)

{

1      struct evdev *evdev;//定义一个evdev结构体指针

struct class_device *cdev;

int minor;

2      for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);

if (minor == EVDEV_MINORS) {

printk(KERN_ERR "evdev: no more free evdev devices\n");

return NULL;

}

3if (!(evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL)))

return NULL;

INIT_LIST_HEAD(&evdev->list);

init_waitqueue_head(&evdev->wait);

evdev->exist = 1;

evdev->minor = minor;

evdev->handle.dev = dev;

evdev->handle.name = evdev->name;

evdev->handle.handler = handler;

evdev->handle.private = evdev;

sprintf(evdev->name, "event%d", minor);

evdev_table[minor] = evdev;

cdev = class_device_create(&input_class, &dev->cdev,

MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),

dev->cdev.dev, evdev->name);

/* temporary symlink to keep userspace happy */

sysfs_create_link(&input_class.subsys.kset.kobj, &cdev->kobj,

evdev->name);

return &evdev->handle;

}

先看1处,这里有个定义在evdev.c里边的新面孔

struct evdev {

int exist;

int open;

int minor;

char name[16];

struct input_handle handle;      //关联input_handler和input_dev的input_handle

wait_queue_head_t wait;

struct evdev_list *grab;

struct list_head list;

};

evdev这个结构体就是拿来应用开发操作的,在这里就是触摸屏对应的设备文件实体,结构体前边定义了记录设备的一些信息(设备号,打开状态、设备名字等),这里还定义了一个input_handle的实体handle,没错这个handle就是要用来关联input_dev和input_handler的,后面还有一行加粗的部分后面再做介绍。

再看2处,evdev_table[]是一个全局变量的数组,在evdev.c中有定义

#define EVDEV_MINORS            32

static struct evdev *evdev_table[EVDEV_MINORS];

在前面已经说明了,一个device可以对应多个handler,而一个handler也可处理多个device,这里体现出了后者。既然evdev这个结构体是对应的设备文件实体,因为这个handler可能会处理多个device,因此该handler要处理n个device就会应该有n个evdev实体,而这些实体的地址存放在evdev_table[]这个指针数组中,也就是说该handler最多只能处理EVDEV_MINORS个device,而2处的这几句代码就是要遍历evdev_table[]这个数组看还有没有空着的位置,有的话才会继续进行下面的程序。

再看3处,开辟一个evdev结构体的内存空间,前面有说明过kzalloc函数,这里不再说明。

后面的代码就是为evdev结构体变量赋初始值了,其中init_waitqueue_head(&evdev->wait)初始化等待队列,具体介绍结合/linux/wait.h和查看相关资料(本人不懂)。

函数最后得到evdev结构体内的hanlde地址并返回,此时返回到我们的int input_register_device(struct input_dev *dev)函数里面,最后一句

input_link_handle(handle),进入到该函数中发现

static void input_link_handle(struct input_handle *handle)

{

list_add_tail(&handle->d_node, &handle->dev->h_list);

list_add_tail(&handle->h_node, &handle->handler->h_list);

}

在该函数中也只是将handle中的d_node和h_node分别接入到input_dev和input_handler的h_list中,此时input_dev、input_handler、input_handle三者形成了如图2所示的关系。

至此设备注册过程算是全部完成了,但是貌似还有点乱。在整个设备的注册过程中函数的嵌套一个接着一个,不仅函数嵌套,结构体也使用了结构体和函数嵌套等,在各个函数内也用了大量的结构体指针等等。但是在整个过程中input_dev、input_handler、input_handle这三个结构体变量的实体也都只有一个。其中input_dev结构体实体在xxx_ts.c中直接定义了一个input_dev的指针全局变量:

struct input_dev *tsdev;

并在初始化函数中开辟了一个input_dev的内存空间并让tsdev指针指向它:

w55fa95_dev = input_allocate_device();

input_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,

};

而关联input_dev和input_handler的input_handle则是在调用链接连接函数static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id)的时候,在该函数的内部调用evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL)开辟了一个evdev结构体的内存空间时创建了,input_handle就是使用了evdev结构体内部定义的input_handle。

一个完整input设备系统不仅要有设备,还需要有处理程序input_handler,而上文中主要介绍的是设备注册、生成、以及和handler搭配的一个过程,而handler在何时生成的?

第三章、input_handler的注册。

Input_handler是要和用户层打交道的,在evdev.c中直接定义了一个input_handler结构体并初始化了一些内部成员变量。

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,            //指向一个evedev的指针数组

};

先看第一行加粗的代码,evedev_fops结构体的定义如下

static struct file_operations evdev_fops = {

.owner =      THIS_MODULE,

.read =            evdev_read,

.write =      evdev_write,

.poll =            evdev_poll,

.open =            evdev_open,

.release =      evdev_release,

.unlocked_ioctl = evdev_ioctl,

#ifdef CONFIG_COMPAT

.compat_ioctl =      evdev_ioctl_compat,

#endif

.fasync =      evdev_fasync,

.flush =      evdev_flush

};

相信做过linux设备驱动编程的对这都很熟悉了,就是一大堆的用户接口函数,包括对设备的open、close、read、write、ioctl等函数。

再看第二行代码加粗的部分.id_table =      evdev_ids,id.table就是前面所说过要和input_dev的id匹配的这么一个结构体,这里让它初始化为evdev_ids,在看evdev_ids的定义:

static struct input_device_id evdev_ids[] = {

{ .driver_info = 1 },      /* Matches all devices */

{ },                  /* Terminating zero entry */

};

MODULE_DEVICE_TABLE(input, evdev_ids);

这里是一个结构体数组,令数组中第一个结构体的该成员变量driver_info的值为1,其他成员变量均未定义,说明这个handler对所有device的id都能匹配得上。数组中的第二个结构体为空表示结束,用来标识结束配合下面的MODULE_DEVICE_TABLE(input, evdev_ids)使用,关于MODULE_DEVICE_TABLE宏定义介绍自行查看相关文献(我也不懂)。

接下来进入正题,input_handler的注册!

Input_handler的注册和input_dev的注册很相似,大同小异罢了,在evdev.c源码中显示定义并初始化了一个input_handler结构体并直接给相关的成员变量赋值了,就是本章开始所将的部分,然后再初始化函数中注册一个input_handler:

static int __init evdev_init(void)

{

input_register_handler(&evdev_handler);

return 0;

}

这里只调用了一个input_register_handler()函数,看起来应该是很简单的样子(相比input_dev的注册),继续跟踪进入到该函数中:

void input_register_handler(struct input_handler *handler)

{

struct input_dev *dev;

struct input_handle *handle;

struct input_device_id *id;

if (!handler) return;

INIT_LIST_HEAD(&handler->h_list);

if (handler->fops != NULL)

input_table[handler->minor >> 5] = handler;

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

list_for_each_entry(dev, &input_dev_list, node)

if (!handler->blacklist || !input_match_device(handler->blacklist, dev))

if ((id = input_match_device(handler->id_table, dev)))

if ((handle = handler->connect(handler, dev, id)))

input_link_handle(handle);

input_wakeup_procfs_readers();

}

该函数中代码加粗的部分,与input_register_device()函数里的如出一辙,这里不再做说明。

至此input_handler的注册已经结束。

第四章、input子系统数据结构

下图3是以触摸屏设备为例子的input子系统数据结构图。

97cbceaa2a49811949013a41db747604.png

图3input子系统数据结构图

进行到这里,又多冒出来了一个struct evdev_list的结构体,这在之前并没有提到过,因为在注册input_dev和input_handler过程中并没有用到过,查看该结构体:

struct evdev_list {

struct input_event buffer[EVDEV_BUFFER_SIZE];      //存放设备数据信息

int head;      //buffer的下标,标识从设备中过来要存放到buffer的数据的位置

int tail;            //buffer的下标,标识用户读取buffer中数据的下标位置

第一章、了解linux input子系统

Linux输入设备总类繁杂,常见的包括有按键、键盘、触摸屏、鼠标、摇杆等等,他们本身就是字符设备,而linux内核将这些设备的共同性抽象出来,简化驱动开发建立了一个input子系统。子系统共分为三层,如图1所示。

e04e566a400e7342a594ecaecfbc6ae8.png

图1input输入子系统

驱动层和硬件相关,直接捕捉和获取硬件设备的数据信息等(包括触摸屏被按下、按下位置、鼠标移动、键盘按下等等),然后将数据信息报告到核心层。核心层负责连接驱动层和事件处理层,设备驱动(device driver)和处理程序(handler)的注册需要通过核心层来完成,核心层接收来自驱动层的数据信息,并将数据信息选择对应的handler去处理,最终handler将数据复制到用户空间。

先了解三个定义在/linux/input.h下重要的结构体input_dev、input_handler、input_handle。

struct input_dev {

void *private;

const char *name;

const char *phys;

const char *uniq;

struct input_id id;//与input_handler匹配用的id

unsigned long evbit[NBITS(EV_MAX)];//设备支持的事件类型

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)];

int ff_effects_max;

unsigned int keycodemax;

unsigned int keycodesize;

void *keycode;

unsigned int repeat_key;

struct timer_list timer;

struct pt_regs *regs;

int state;

int sync;

int abs[ABS_MAX + 1];

int rep[REP_MAX + 1];

unsigned long key[NBITS(KEY_MAX)];

unsigned long led[NBITS(LED_MAX)];

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 (*accept)(struct input_dev *dev, struct file *file);

int (*flush)(struct input_dev *dev, struct file *file);

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

int (*upload_effect)(struct input_dev *dev, struct ff_effect *effect);

int (*erase_effect)(struct input_dev *dev, int effect_id);

struct input_handle *grab;//当前占有该设备的handle

struct mutex mutex;      /* serializes open and close operations */

unsigned int users;            //打开该设备的用户量

struct class_device cdev;

struct device *dev;      /* will be removed soon */

int dynalloc;      /* temporarily */

struct list_head      h_list;      //该链表头用于链接该设备所关联的input_handle

struct list_head      node;      //该链表头用于将设备链接到input_dev_list

};

Input_dev是一个很强大的结构体,它把所有的input设备(触摸屏、键盘、鼠标等)的信息都考虑到了,对于触摸屏来说只用到它里面的一部分而已,尤其是加粗的部分,注意该结构体中最后两行定义的两个list_head结构体,list_head在/linux/list.h中有定义,深入跟踪

struct list_head {

struct list_head *next, *prev;

};

该结构体内部并没有定义数据而只定义了两个指向本身结构体的指针,预先说明一下,所有的input device在注册后会加入一个input_dev_list(输入设备链表),所有的eventhandler在注册后会加入一个input_handler_list(输入处理程序链表),这里的list_head主要的作用是作为input_dev_list和input_handler_list的一个节点来保存地址。Input_dev_list和input_handler_list之间的对应关系由input_handle结构体桥接,具体后面说明。

struct input_handler {

void *private;

void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);

struct input_handle* (*connect)(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id);

void (*disconnect)(struct input_handle *handle);

const struct file_operations *fops;      //提供给用户对设备操作的函数指针

int minor;

char *name;

struct input_device_id *id_table;//与input_dev匹配用的id

struct input_device_id *blacklist;      //标记的黑名单

struct list_head      h_list;//用于链接和该handler相关的handle

struct list_head      node;            //用于将该handler链入input_handler_list

};

input_handler顾名思义,它是用来处理input_dev的一个结构体,相关的处理函数在结构里内部都有定义,最后两行定义的list_head结构体作用同input_dev所定义的一样,这里不再说明。

注:input_device_id结构体在/linux/mod_devicetable.h中有定义

struct input_handle {

void *private;

int open;      //记录设备打开次数

char *name;

struct input_dev *dev;      //指向所属的input_dev

struct input_handler *handler;      //指向所属的input_handler

struct list_head      d_node;//用于链入所指向的input_dev的handle链表

struct list_head      h_node;//用于链入所指向的input_handler的handle链表

};

可以看到input_handle中拥有指向input_dev和input_handler的指针,即input_handle是用来关联input_dev和input_handler。为什么用input_handle来关联input_dev和input_handler而不将input_dev和input_handler直接对应呢?因为一个device可以对应多个handler,而一个handler也可处理多个device。就如一个触摸屏设备可以对应event handler也可以对应tseve handler。

input_dev、input_handler、input_handle的关系如下图2所示。

898c49e69dc1c558a33ff5c560421005.png

图2input_dev,input_handler,input_handle关系图

第二章、inputdevice的注册

Inputdevice的注册实际上仅仅只有几行代码,因为在input.c中已经将大量的代码封装好了,主需要调用几个关键的函数就能完成对input device的注册。

在xxx_ts.c中预先定义全局变量struct input_dev  tsdev;然后进入到初始化函数

static int __init xxx_probe(struct platform_device *pdev)

{

if (!(tsdev = input_allocate_device()))

{

printk(KERN_ERR "tsdev: not enough memory\n");

err = -ENOMEM;

goto fail;

}

tsdev->name = "xxx TouchScreen";            //xxx为芯片型号

tsdev ->phys = "xxx/event0";

tsdev ->id.bustype = BUS_HOST;//设备id,用于匹配handler的id

tsdev ->id.vendor  = 0x0005;

tsdev ->id.product = 0x0001;

tsdev ->id.version = 0x0100;

tsdev ->open    = xxx_open;

tsdev ->close   =xxx_close;

tsdev ->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS) | BIT(EV_SYN);            //设置支持的事件类型

tsdev ->keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH);

input_set_abs_params(tsdev, ABS_X, 0, 0x400, 0, 0);            //限定绝对坐标X的取值范围

input_set_abs_params(tsdev, ABS_Y, 0, 0x400, 0, 0);            //同上

input_set_abs_params(tsdev, ABS_PRESSURE, 0, 1000, 0, 0);      //触摸屏压力值范围

If(input_register_device(tsdev) == error)//注册设备

goto fail;

fail:

input_free_device(tsdev);

printk(“ts probe failed\n”);

return err;

}

先看该函数中的tsdev = input_allocate_device(),深入最终,该函数在input.c中有定义

struct input_dev *input_allocate_device(void)

{

struct input_dev *dev;

dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);

if (dev) {

dev->dynalloc = 1;

dev->cdev.class = &input_class;

class_device_initialize(&dev->cdev);

INIT_LIST_HEAD(&dev->h_list);

INIT_LIST_HEAD(&dev->node);

}

return dev;

}

学过C语言应该都知道malloc函数(开辟内存空间),而这里的kzalloc也类似于C语言中的malloc一样,是linux内核空间分配内存函数,后面的GFP_KERNEL标志意为常规的内存分配,更多的分配标志可参阅先关资料(我也不懂)。再回到前面的函数中来,接着后面的代码为对tsdev结构体中的成员进行赋值初始化,赋值完成后就要开始进入主题:注册设备了,进入到函数input_register_device(tsdev),该函数在input.c中有定义。

int input_register_device(struct input_dev *dev)

{

static atomic_t input_no = ATOMIC_INIT(0);      //定义原子变量,禁止线程并发访问

struct input_handle *handle;            //定义一些变量备后文使用

struct input_handler *handler;

struct input_device_id *id;

const char *path;

int error;

if (!dev->dynalloc) {

printk(KERN_WARNING "input: device %s is statically allocated, will not register\n"

"Please convert to input_allocate_device() or contact dtor_core@ameritech.net\n",

dev->name ? dev->name : "");

return -EINVAL;

}

mutex_init(&dev->mutex);            //互斥锁初始化,防止临界区代码被并发访问

set_bit(EV_SYN, dev->evbit);            //设置支持同步事件,input设备全部默认支持同步事件

/*

* If delay and period are pre-set by the driver, then autorepeating

* is handled by the driver itself and we don't do it in input.c.

*/

init_timer(&dev->timer);

if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {

dev->timer.data = (long) dev;

dev->timer.function = input_repeat_key;

dev->rep[REP_DELAY] = 250;

dev->rep[REP_PERIOD] = 33;

}

INIT_LIST_HEAD(&dev->h_list);      //初始化需要关联的handle链表头

list_add_tail(&dev->node, &input_dev_list);      //将设备添加到input_dev_list中

dev->cdev.class = &input_class;

snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),

"input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);

error = class_device_add(&dev->cdev);

if (error)

return error;

error = sysfs_create_group(&dev->cdev.kobj, &input_dev_attr_group);

if (error)

goto fail1;

error = sysfs_create_group(&dev->cdev.kobj, &input_dev_id_attr_group);

if (error)

goto fail2;

error = sysfs_create_group(&dev->cdev.kobj, &input_dev_caps_attr_group);

if (error)

goto fail3;

__module_get(THIS_MODULE);

path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);

printk(KERN_INFO "input: %s as %s\n",

dev->name ? dev->name : "Unspecified device", path ? path : "N/A");

kfree(path);

/***遍历input_handler_list上全部的handler,寻找与该设备匹配的handler  ***/

list_for_each_entry(handler, &input_handler_list, node)

if (!handler->blacklist || !input_match_device(handler->blacklist, dev))

if ((id = input_match_device(handler->id_table, dev)))

if ((handle = handler->connect(handler, dev, id)))

input_link_handle(handle);

input_wakeup_procfs_readers();

return 0;

fail3:      sysfs_remove_group(&dev->cdev.kobj, &input_dev_id_attr_group);

fail2:      sysfs_remove_group(&dev->cdev.kobj, &input_dev_attr_group);

fail1:      class_device_del(&dev->cdev);

return error;

}

先看函数中前面代码加粗的部分mutex_init(&dev->mutex),与互斥锁相关的东西(我也不太懂),set_bit(EV_SYN, dev->evbit)设置支持同步事件,linux的input子系统默认要支持同步事件。

接着看中间代码加粗的部分,有关list操作的在/linux/list.h中有定义

static inline void INIT_LIST_HEAD(struct list_head *list)

{

list->next = list;

list->prev = list;

}

static inline void list_add_tail(struct list_head *new, struct list_head *head)

{

__list_add(new, head->prev, head);

}

static inline void __list_add(struct list_head *new,

struct list_head *prev,

struct list_head *next)

{

next->prev = new;

new->next = next;

new->prev = prev;

prev->next = new;

}

可以看得出INIT_LIST_HEAD(struct list_head *list)就是让list指向结构体的成员再指向其本身完成初始化操作,list_add_tail(struct list_head *new, struct list_head *head)是将new所指向的结构体作为一节点插入到head所指向链表节点之前。因此函数中的list_add_tail(&dev->node, &input_dev_list)就是将该设备添加到input_dev_list中。而input_dev_list这个双向链表在什么时候被定义了呢,且看input.c源代码开始部分全局部分有定义:

static LIST_HEAD(input_dev_list);

static LIST_HEAD(input_handler_list);

而LIST_HEAD的宏定义:

#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \

struct list_head name = LIST_HEAD_INIT(name)

显然这在最开始就已经定义了input_dev_list和input_handler_list这两个链表,且这两个链表都只有一个节点,而在device和handler注册的时候会在这两条链表中加入节点,list_add_tail(&dev->node, &input_dev_list)就是将该设备添加到input_dev_list中。

注意最后代码加粗的部分,该部分完成了input_dev和input_handler的桥接。先看list_for_each_entry(handler, &input_handler_list, node),list_for_each_entry在list.h中有定义,其作用相当于一个for循环。其约等价于:(个人理解)

for(handler = input_handler_list表头所属的input_handler结构体地址;

handler != input_handler_list表尾所属的input_handler结构体地址;

handler++)

即handler第一次指向input_handler_list的头部所在的input_handler地址。每循环一次handler就沿着input_handler_list移动到下一个节点,得到下一节点所属的handler地址,直到input_handler_list的结尾。而每次的循环需要做什么呢?要做的就是遍历input_handler_list上的每一个handler,看有哪些handler能与该设备匹配的上。匹配过程:

if (!handler->blacklist || !input_match_device(handler->blacklist, dev))//判断该handler没有被列入黑名单或者黑名单匹配不成功的话则继续

if ((id = input_match_device(handler->id_table, dev)))//将设备id与handler的id进行匹配,成功则继续往下

if ((handle = handler->connect(handler, dev, id)))//链接device与handler,成功则继续往下

input_link_handle(handle);//将handle链入input_handler_list和input_dev_list

继续跟踪进这些函数

static struct input_device_id *input_match_device(struct input_device_id *id, struct input_dev *dev)

{

int i;

for (; id->flags || id->driver_info; id++) {

if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)      //匹配handler和device  id的flag标志位

if (id->bustype != dev->id.bustype)

continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)

if (id->vendor != dev->id.vendor)

continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)

if (id->product != dev->id.product)

continue;

if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)

if (id->version != dev->id.version)

continue;

MATCH_BIT(evbit,  EV_MAX);            //匹配id相关标志位

MATCH_BIT(keybit, KEY_MAX);

MATCH_BIT(relbit, REL_MAX);

MATCH_BIT(absbit, ABS_MAX);

MATCH_BIT(mscbit, MSC_MAX);

MATCH_BIT(ledbit, LED_MAX);

MATCH_BIT(sndbit, SND_MAX);

MATCH_BIT(ffbit,  FF_MAX);

MATCH_BIT(swbit,  SW_MAX);

return id;

}

return NULL;

}

该函数用于匹配input_dev结构体和input_handler结构体里面定义的input_device_id,若两者里面有任何相关变量不一样则匹配失败(条件真苛刻)。

再看handle = handler->connect(handler, dev, id),connect函数即为static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id),为什么呢会是这个呢,我们先看evdev.c中的input_handler结构体定义:

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,

};

可只这里input_handler结构体里边的connect函数即为evdev_connect函数

static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id)

{

1      struct evdev *evdev;//定义一个evdev结构体指针

struct class_device *cdev;

int minor;

2      for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);

if (minor == EVDEV_MINORS) {

printk(KERN_ERR "evdev: no more free evdev devices\n");

return NULL;

}

3if (!(evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL)))

return NULL;

INIT_LIST_HEAD(&evdev->list);

init_waitqueue_head(&evdev->wait);

evdev->exist = 1;

evdev->minor = minor;

evdev->handle.dev = dev;

evdev->handle.name = evdev->name;

evdev->handle.handler = handler;

evdev->handle.private = evdev;

sprintf(evdev->name, "event%d", minor);

evdev_table[minor] = evdev;

cdev = class_device_create(&input_class, &dev->cdev,

MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),

dev->cdev.dev, evdev->name);

/* temporary symlink to keep userspace happy */

sysfs_create_link(&input_class.subsys.kset.kobj, &cdev->kobj,

evdev->name);

return &evdev->handle;

}

先看1处,这里有个定义在evdev.c里边的新面孔

struct evdev {

int exist;

int open;

int minor;

char name[16];

struct input_handle handle;      //关联input_handler和input_dev的input_handle

wait_queue_head_t wait;

struct evdev_list *grab;

struct list_head list;

};

evdev这个结构体就是拿来应用开发操作的,在这里就是触摸屏对应的设备文件实体,结构体前边定义了记录设备的一些信息(设备号,打开状态、设备名字等),这里还定义了一个input_handle的实体handle,没错这个handle就是要用来关联input_dev和input_handler的,后面还有一行加粗的部分后面再做介绍。

再看2处,evdev_table[]是一个全局变量的数组,在evdev.c中有定义

#define EVDEV_MINORS            32

static struct evdev *evdev_table[EVDEV_MINORS];

在前面已经说明了,一个device可以对应多个handler,而一个handler也可处理多个device,这里体现出了后者。既然evdev这个结构体是对应的设备文件实体,因为这个handler可能会处理多个device,因此该handler要处理n个device就会应该有n个evdev实体,而这些实体的地址存放在evdev_table[]这个指针数组中,也就是说该handler最多只能处理EVDEV_MINORS个device,而2处的这几句代码就是要遍历evdev_table[]这个数组看还有没有空着的位置,有的话才会继续进行下面的程序。

再看3处,开辟一个evdev结构体的内存空间,前面有说明过kzalloc函数,这里不再说明。

后面的代码就是为evdev结构体变量赋初始值了,其中init_waitqueue_head(&evdev->wait)初始化等待队列,具体介绍结合/linux/wait.h和查看相关资料(本人不懂)。

函数最后得到evdev结构体内的hanlde地址并返回,此时返回到我们的int input_register_device(struct input_dev *dev)函数里面,最后一句

input_link_handle(handle),进入到该函数中发现

static void input_link_handle(struct input_handle *handle)

{

list_add_tail(&handle->d_node, &handle->dev->h_list);

list_add_tail(&handle->h_node, &handle->handler->h_list);

}

在该函数中也只是将handle中的d_node和h_node分别接入到input_dev和input_handler的h_list中,此时input_dev、input_handler、input_handle三者形成了如图2所示的关系。

至此设备注册过程算是全部完成了,但是貌似还有点乱。在整个设备的注册过程中函数的嵌套一个接着一个,不仅函数嵌套,结构体也使用了结构体和函数嵌套等,在各个函数内也用了大量的结构体指针等等。但是在整个过程中input_dev、input_handler、input_handle这三个结构体变量的实体也都只有一个。其中input_dev结构体实体在xxx_ts.c中直接定义了一个input_dev的指针全局变量:

struct input_dev *tsdev;

并在初始化函数中开辟了一个input_dev的内存空间并让tsdev指针指向它:

w55fa95_dev = input_allocate_device();

input_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,

};

而关联input_dev和input_handler的input_handle则是在调用链接连接函数static struct input_handle *evdev_connect(struct input_handler *handler, struct input_dev *dev, struct input_device_id *id)的时候,在该函数的内部调用evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL)开辟了一个evdev结构体的内存空间时创建了,input_handle就是使用了evdev结构体内部定义的input_handle。

一个完整input设备系统不仅要有设备,还需要有处理程序input_handler,而上文中主要介绍的是设备注册、生成、以及和handler搭配的一个过程,而handler在何时生成的?

第三章、input_handler的注册。

Input_handler是要和用户层打交道的,在evdev.c中直接定义了一个input_handler结构体并初始化了一些内部成员变量。

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,            //指向一个evedev的指针数组

};

先看第一行加粗的代码,evedev_fops结构体的定义如下

static struct file_operations evdev_fops = {

.owner =      THIS_MODULE,

.read =            evdev_read,

.write =      evdev_write,

.poll =            evdev_poll,

.open =            evdev_open,

.release =      evdev_release,

.unlocked_ioctl = evdev_ioctl,

#ifdef CONFIG_COMPAT

.compat_ioctl =      evdev_ioctl_compat,

#endif

.fasync =      evdev_fasync,

.flush =      evdev_flush

};

相信做过linux设备驱动编程的对这都很熟悉了,就是一大堆的用户接口函数,包括对设备的open、close、read、write、ioctl等函数。

再看第二行代码加粗的部分.id_table =      evdev_ids,id.table就是前面所说过要和input_dev的id匹配的这么一个结构体,这里让它初始化为evdev_ids,在看evdev_ids的定义:

static struct input_device_id evdev_ids[] = {

{ .driver_info = 1 },      /* Matches all devices */

{ },                  /* Terminating zero entry */

};

MODULE_DEVICE_TABLE(input, evdev_ids);

这里是一个结构体数组,令数组中第一个结构体的该成员变量driver_info的值为1,其他成员变量均未定义,说明这个handler对所有device的id都能匹配得上。数组中的第二个结构体为空表示结束,用来标识结束配合下面的MODULE_DEVICE_TABLE(input, evdev_ids)使用,关于MODULE_DEVICE_TABLE宏定义介绍自行查看相关文献(我也不懂)。

接下来进入正题,input_handler的注册!

Input_handler的注册和input_dev的注册很相似,大同小异罢了,在evdev.c源码中显示定义并初始化了一个input_handler结构体并直接给相关的成员变量赋值了,就是本章开始所将的部分,然后再初始化函数中注册一个input_handler:

static int __init evdev_init(void)

{

input_register_handler(&evdev_handler);

return 0;

}

这里只调用了一个input_register_handler()函数,看起来应该是很简单的样子(相比input_dev的注册),继续跟踪进入到该函数中:

void input_register_handler(struct input_handler *handler)

{

struct input_dev *dev;

struct input_handle *handle;

struct input_device_id *id;

if (!handler) return;

INIT_LIST_HEAD(&handler->h_list);

if (handler->fops != NULL)

input_table[handler->minor >> 5] = handler;

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

list_for_each_entry(dev, &input_dev_list, node)

if (!handler->blacklist || !input_match_device(handler->blacklist, dev))

if ((id = input_match_device(handler->id_table, dev)))

if ((handle = handler->connect(handler, dev, id)))

input_link_handle(handle);

input_wakeup_procfs_readers();

}

该函数中代码加粗的部分,与input_register_device()函数里的如出一辙,这里不再做说明。

至此input_handler的注册已经结束。

第四章、input子系统数据结构

下图3是以触摸屏设备为例子的input子系统数据结构图。

97cbceaa2a49811949013a41db747604.png

图3input子系统数据结构图

进行到这里,又多冒出来了一个struct evdev_list的结构体,这在之前并没有提到过,因为在注册input_dev和input_handler过程中并没有用到过,查看该结构体:

struct evdev_list {

struct input_event buffer[EVDEV_BUFFER_SIZE];      //存放设备数据信息

int head;      //buffer的下标,标识从设备中过来要存放到buffer的数据的位置

int tail;            //buffer的下标,标识用户读取buffer中数据的下标位置

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值