<什么是Linux输入设备>
➤简介 
    Linux输入设备总类繁杂,常见的包括有按键、键盘、触摸屏、鼠标、摇杆等等,他们本身就是字符设备,而linux内核将这些设备的共同性抽象出来,简化驱动开发建立了一个input子系统。子系统共分为三层,如图1所示。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
图1  input输入子系统
➣驱动层
    驱动层和硬件相关,直接捕捉和获取硬件设备的数据信息等(包括触摸屏被按下、按下位置、鼠标移动、键盘按下等等),然后将数据信息报告到核心层。
➣核心层
    核心层负责连接驱动层和事件处理层,设备驱动(device driver)和处理程序(handler)的注册需要通过核心层来完成,核心层接收来自驱动层的数据信息,并将数据信息选择对应的handler去处理,最终handler将数据复制到用户空间。
➤重要的结构体input_dev、input_handler、input_handle。
➣input_dev
struct input_dev {
void *private;
const char *name;
const char *phys;
const char *uniq;
struct inBITS(KEY_MAX)]; //按键事件支持的子事件类型
unsigned long relbit[NBITS(REL_MAX)];
unsigned long absbit[NBITS(ABS_Mput_id id; //与input_handler匹配用的id
unsigned long evbit[NBITS(EV_MAX)]; //设备支持的事件类型
unsigned long keybit[NAX)]; //绝对坐标事件支持的子事件类型
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 + ];
int rep[REP_MAX + ];
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 + ]; //绝对坐标事件的最大键值
int absmin[ABS_MAX + ]; //绝对坐标事件的最小键值
int absfuzz[ABS_MAX + ];
int absflat[ABS_MAX + ];
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结构体桥接,具体后面说明。
 
➣input_handler
 
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中有定义
➣input_handle
 
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所示。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
图2  input_dev,input_handler,input_handle关系图
<input device的注册>
      Input device的注册实际上仅仅只有几行代码,因为在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[] = BIT(EV_KEY) | BIT(EV_ABS) | BIT(EV_SYN); //设置支持的事件类型
tsdev ->keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH);
input_set_abs_params(tsdev, ABS_X, , 0x400, , ); //限定绝对坐标X的取值范围
input_set_abs_params(tsdev, ABS_Y, , 0x400, , ); //同上
input_set_abs_params(tsdev, ABS_PRESSURE, , , , ); //触摸屏压力值范围
… 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(); //定义原子变量,禁止线程并发访问
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 : "<Unknown>");
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] = ;
dev->rep[REP_PERIOD] = ;
}
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) - );
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 ;
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函数
 
➣"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(再input_device 中定义并初始化该结构体,再input_handler中查找该结构体,并注册到其中),没错这个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结构体的内存空间
    后面的代码就是为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搭配的一个过程,接下来讨论一下input_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 = }, /* 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_dev的注册很相似,大同小异罢了,在evdev.c源码中显示定义并初始化了一个input_handler结构体并直接给相关的成员变量赋值了,就是本章开始所将的部分,然后再初始化函数中注册一个input_handler:
static int __init evdev_init(void)
{
input_register_handler(&evdev_handler);
return ;
}
这里只调用了一个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 >> ] = 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子系统数据结构图。
 
     
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
图3  input子系统数据结构图
    进行到这里,又多冒出来了一个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中数据的下标位置
struct fasync_struct *fasync;
struct evdev *evdev;
struct list_head node;
};
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
 
 
 
 
static int evdev_open(struct inode * inode, struct file * file)
{
struct evdev_list *list; //定义一个evdev_list结构体
int i = iminor(inode) - EVDEV_MINOR_BASE;
int accept_err; if (i >= EVDEV_MINORS || !evdev_table[ i] || !evdev_table[ i]->exist)
return -ENODEV; if ((accept_err = input_accept_process(&(evdev_table[ i]->handle), file)))
return accept_err; if (!(list = kzalloc(sizeof(struct evdev_list), GFP_KERNEL))) //开辟evdev_list结构体内存空间
return -ENOMEM; list->evdev = evdev_table[ i]; //令结构体中的evdev指针指向evdec_table[ i]
list_add_tail(&list->node, &evdev_table[ i]->list); //加入链表
file->private_data = list; if (!list->evdev->open++) //如果设备没有被open,则继续if操作
if (list->evdev->exist)
input_open_device(&list->evdev->handle); //打开设备 return ;
}
 
 
 
int input_open_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev;
int err; err = mutex_lock_interruptible(&dev->mutex);
if (err)
return err;
handle->open++; //handle的内部成员open++
if (!dev->users++ && dev->open)
err = dev->open(dev);
if (err)
handle->open--; mutex_unlock(&dev->mutex); return err;
}
 
 
 
 
input_report_key(tsdev, BTN_TOUCH, );      //报告按键被按下事件
input_report_abs(tsdev, ABS_X, x); //报告触摸屏被按下的x坐标值
input_report_abs(tsdev, ABS_Y, y); //报告触摸屏被按下的y坐标值
input_report_abs(tsdev, ABS_PRESSURE, ); //报告触摸屏被按下的压力值(0或者1)
input_sync(tsdev); //报告同步事件,表示一次事件结束
 
 
 
input_report_key(tsdev, BTN_TOUCH, );      //报告按键被松开事件
input_report_abs(tsdev, ABS_PRESSURE, ); //报告触摸屏被按下的压力值(0或者1)
input_sync(tsdev); //报告同步事件,表示一次事件结束
 
 
 
 
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_ABS, code, value);
}
 
 
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct input_handle *handle; if (type > EV_MAX || !test_bit(type, dev->evbit))
return; add_input_randomness(type, code, value); switch (type) { … case EV_KEY: //判断为按键事件 if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value)
return; if (value == )
break; change_bit(code, dev->key); if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) {
dev->repeat_key = code;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
} case EV_ABS: //判断为绝对坐标事件 if (code > ABS_MAX || !test_bit(code, dev->absbit))
return; if (dev->absfuzz[code]) {
if ((value > dev->abs[code] - (dev->absfuzz[code] >> )) &&
(value < dev->abs[code] + (dev->absfuzz[code] >> )))
return; if ((value > dev->abs[code] - dev->absfuzz[code]) &&
(value < dev->abs[code] + dev->absfuzz[code]))
value = (dev->abs[code] * + value) >> ; if ((value > dev->abs[code] - (dev->absfuzz[code] << )) &&
(value < dev->abs[code] + (dev->absfuzz[code] << )))
value = (dev->abs[code] + value) >> ;
} if (dev->abs[code] == value)
return; dev->abs[code] = value;
break; … } if (type != EV_SYN)
dev->sync = ; if (dev->grab) //判断有没有声明自定义的处理函数(初始化过程中显然没有定义)
dev->grab->handler->event(dev->grab, type, code, value);
else
list_for_each_entry(handle, &dev->h_list, d_node) //遍历handle链表
if (handle->open) //如果某节点上的处理程序被打开了
handle->handler->event(handle, type, code, value); //调用处理函数
}
 
 
 
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
struct evdev *evdev = handle->private;
struct evdev_list *list; if (evdev->grab) { //显然grab并没有被设置,该条件为假
list = evdev->grab; do_gettimeofday(&list->buffer[list->head].time);
list->buffer[list->head].type = type;
list->buffer[list->head].code = code;
list->buffer[list->head].value = value;
list->head = (list->head + ) & (EVDEV_BUFFER_SIZE - ); kill_fasync(&list->fasync, SIGIO, POLL_IN);
} else
list_for_each_entry(list, &evdev->list, node) { do_gettimeofday(&list->buffer[list->head].time); //给buffer成员赋值
list->buffer[list->head].type = type;
list->buffer[list->head].code = code;
list->buffer[list->head].value = value;
list->head = (list->head + ) & (EVDEV_BUFFER_SIZE - ); kill_fasync(&list->fasync, SIGIO, POLL_IN);
} wake_up_interruptible(&evdev->wait); //用来唤醒一个等待队列(我也不懂)
}
 
 
 
 
static ssize_t evdev_read(struct file * file, char __user * buffer, size_t count, loff_t *ppos)
{
struct evdev_list *list = file->private_data;
int retval; if (count < evdev_event_size())//每次读取的字节数至少是input_event的大小
return -EINVAL;
if (list->head == list->tail && list->evdev->exist && (file->f_flags & O_NONBLOCK)) //是否满足读取条件
return -EAGAIN;
retval = wait_event_interruptible(list->evdev->wait,
list->head != list->tail || (!list->evdev->exist)); //等待唤醒,和前面说的等待队列对应(我也不懂)
if (retval)
return retval;
if (!list->evdev->exist)
return -ENODEV;
while (list->head != list->tail && retval + evdev_event_size() <= count) {
struct input_event *event = (struct input_event *) list->buffer + list->tail;
if (evdev_event_to_user(buffer + retval, event)) //复制数据到用户空间
return -EFAULT;
list->tail = (list->tail + ) & (EVDEV_BUFFER_SIZE - );
retval += evdev_event_size();
}
return retval;
}
 
 
 
 
 
static int evdev_event_to_user(char __user *buffer, const struct input_event *event)
{
if (copy_to_user(buffer, event, sizeof(struct input_event)))
return -EFAULT; return ;
}
 
 
 
 

<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">

 
 
 
 

Linux输入设备详解的更多相关文章

  1. Linux命令详解之—tail命令

    tail命令也是一个非常常用的文件查看类的命令,今天就为大家介绍下Linux tail命令的用法. 更多Linux命令详情请看:Linux命令速查手册 Linux tail命令主要用来从指定点开始将文 ...

  2. Linux命令详解之—less命令

    Linux下还有一个与more命令非常类似的命令--less命令,相比于more命令,less命令更加灵活强大一些,今天就给大家介绍下Linux下的less命令. 更多Linux命令详情请看:Linu ...

  3. Linux命令详解之—more命令

    Linux more命令同cat命令一样,多用来查看文件内容,本文就为大家介绍下Linux more命令的用法. 更多Linux命令详情请看:Linux命令速查手册 Linux的more命令类似 ca ...

  4. 【转】linux命令详解:md5sum命令

    [转]linux命令详解:md5sum命令 转自:http://blog.itpub.net/29320885/viewspace-1710218/ 前言 在网络传输.设备之间转存.复制大文件等时,可 ...

  5. Linux命令详解之—cat命令

    cat命令的功能是连接文件或标准输入并打印,今天就为大家介绍下Linux中的cat命令. 更多Linux命令详情请看:Linux命令速查手册 Linux 的cat命令通常用来显示文件内容,也可以用来将 ...

  6. Linux命令详解之—pwd命令

    Linux的pwd命令也是一个非常常用的命令,本文为大家介绍下Linux中pwd命令的用法. 更多Linux命令详情请看:Linux命令速查手册 Linux pwd命令用于显示工作目录. 执行pwd指 ...

  7. Linux命令详解之–cd命令

    cd命令是linux实际使用当中另一个非常重要的命令,本文就为大家介绍下Linux中cd命令的用法. 更多Linux命令详情请看:Linux命令速查手册 Linux cd命令用于切换当前工作目录至 d ...

  8. Linux命令详解之–ls命令

    今天开始为大家介绍下Linux中常用的命令,首先给大家介绍下Linux中使用频率最高的命令--ls命令. 更多Linux命令详情请看:Linux命令速查手册 linux ls命令用于显示指定工作目录下 ...

  9. Linux 系统结构详解

    Linux 系统结构详解 Linux系统一般有4个主要部分: 内核.shell.文件系统和应用程序.内核.shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序.管理文件并使用系统 ...

随机推荐

  1. python3字符串与文本处理

    每个程序都回涉及到文本处理,如拆分字符串.搜索.替换.词法分析等.许多任务都可以通过内建的字符串方法来轻松解决,但更复杂的操作就需要正则表达式来解决. 1.针对任意多的分隔符拆分字符串 In [1]: ...

  2. 在分析nginx日志时常用命令总结【转】

    1. 利用grep ,wc命令统计某个请求或字符串出现的次数 比如统计GET /app/kevinContent接口在某天的调用次数,则可以使用如下命令: [root@Fastdfs_storage_ ...

  3. 关于windows2008r2系统80端口被system进程占用的问题

    80端口被system占用的问题   今天启动tomcat的时候发现无法启动80端口被占用 通过netstat -ano查看,发现被pid=4的进程占用 检查进程发现是system进程pid=4给占用 ...

  4. 目标检测--SSD: Single Shot MultiBox Detector(2015)

    SSD: Single Shot MultiBox Detector 作者: Wei Liu, Dragomir Anguelov, Dumitru Erhan, Christian Szegedy, ...

  5. 无法下载apk等格式的文件的解决方案---ASP .NET Core 2.0 MVC 发布到IIS上以后无法下载apk等格式的文件的解决方案

    ASP .NET Core MVC 发布到  IIS 上以后 无法下载apk等格式的文件 使用.NET Core MVC创建了一个站点,其他文件可以下载,但是后来又需求,就把手机端的apk合适的文件上 ...

  6. wap页面缩放

    html{font-size: 100%;}.in-main{ min-width:320px; max-width:640px; margin:0 auto; font-size:14px; bac ...

  7. webpack 4.0.0-beta.0 新特性介绍

    webpack 可以看做是模块打包机.它做的事情是:分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式 ...

  8. Unicode转义序列

    声明: web前端学习笔记,欢迎大神指点.联系QQ:1522025433. Javascipt 定义了一种特殊序列,使用6位ASCII字符代表任意16Unicode内码.这些Unicode转义序列均以 ...

  9. ERP合同审核流程处理(二十九)

    合同审批流程: 前端的代码: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind=" ...

  10. 设置Eclipse的类文件和xml文件代码自动补全

    原文:https://blog.csdn.net/erlian1992/article/details/53706736 我们在平常编写代码的时候,不会记住大多数的类和文件的属性,方法等等,这就需要我 ...