Linux驱动:输入子系统 分析

参考:

介绍

设计背景

以前我们写一些输入设备(键盘、鼠标等)的驱动都是采用字符设备、混杂设备处理的。问题由此而来,Linux开源社区的大神们看到了这大量输入设备如此分散不堪,实现了一种机制,可以对分散的、不同类别的输入设备进行统一的驱动,这也就是输入子系统。

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

例如,在终端系统中,我们不需要去管有多少个键盘,多少个鼠标。应用程序只要从输入子系统中去取对应的事件(按键,鼠标移位等)就可以了。而底层设备也不需要直接对接应用程序,只要处理并处理对应的事件即可。

引入输入子系统的好处:

  • 统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论PS/2、USB、还是蓝牙,都被同样处理。
  • 提供了用于分发输入报告给用户应用程序的简单的事件(event)接口。你的驱动不必创建、管理/dev节点以及相关的访问方法。因此它能够很方便的调用输入API以发送鼠标移动、键盘按键,或触摸事件给用户空间。X windows这样的应用程序能够无缝地运行于输入子系统提供的event接口之上。
  • 抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。例如,输入子系统提供了一个底层驱动(成为serio)的集合,支持对串口和键盘控制器等硬件输入的访问。

一句话概括,输入子系统是所有I/O设备驱动与应用程序之间的中间层。

框架

linux输入子系统(linux input subsystem)由三层实现,分别为:

  • 输入子系统 设备驱动层(Driver):

对于输入子系统设备驱动层而言,主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。

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

  • 输入子系统 核心层(Input Core):

对于核心层而言,为设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。

它承上启下:

  • 为驱动层提供输入设备注册与操作接口,如:input_register_device;
  • 通知事件处理层对事件进行处理;在/proc下产生相应的设备信息。
  • 输入子系统 事件处理层(Event Handler)。

对于事件处理层而言,则是用户编程的接口(设备节点),并处理驱动层提交的数据处理。

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

/dev/input/下显示的是已经注册在内核中的设备编程接口,用户通过open这些设备文件来打开不同的输入设备进行硬件操作。

事件处理层为不同硬件类型提供了用户访问及处理接口。例如当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler来处理输入事件,这也使得设备驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件处理的方法。

输入子系统由内核代码drivers/input/input.c构成,它的存在屏蔽了用户到设备驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的统一界面。

由上图可知输入子系统核心层提供的支持以及如何上报事件到input event drivers。

内核:v3.10

核心层Input Core

路径:deivers/input/input.c

核心层 实现了:

  • 在入口函数中申请主设备号,注册进内核
  • 提供input_register_device用于注册deviceinput_register_handler函数注册handler处理器;
  • 提供input_register_handle函数用于注册一个事件处理,代表一个成功配对的input_dev和input_handler;

入口函数

申请主设备号的事情就是input_init完成的;毕竟,输入子系统是作为一个驱动模块存在。

// include/uapi/linux/major.h:29:#define INPUT_MAJOR		13

static int __init input_init(void)
{
int err; err = class_register(&input_class); //(1)注册类,放在/sys/class
if (err) {
pr_err("unable to register input_dev class\n");
return err;
} err = input_proc_init(); //在/proc下面建立相关的文件
if (err)
goto fail1; err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES, "input"); // //(2)注册驱动,主设备号为13
if (err) {
pr_err("unable to register char major %d", INPUT_MAJOR);
goto fail2;
} return 0; fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
} // ... ... subsys_initcall(input_init);
module_exit(input_exit);

注册class

class_register时用到了这个类,最终会在/sys/class/input中出现。

struct class input_class = {
.name = "input",
.devnode = input_devnode,
};

至于input core只注册了一个类,而没有继续注册对应的设备;是因为:

核心层作为一个中转层存在,不涉及具体硬件设备的注册,倒是更符合他存在的逻辑。

设备的注册到底会在Input driver 还是Event hanlder呢?

注册proc

这里用到了proc注册的接口

static const struct file_operations input_handlers_fileops = {
.owner = THIS_MODULE,
.open = input_proc_handlers_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
}; static int __init input_proc_init(void)
{
struct proc_dir_entry *entry; proc_bus_input_dir = proc_mkdir("bus/input", NULL);
if (!proc_bus_input_dir)
return -ENOMEM; entry = proc_create("devices", 0, proc_bus_input_dir,
&input_devices_fileops);
if (!entry)
goto fail1; entry = proc_create("handlers", 0, proc_bus_input_dir,
&input_handlers_fileops);
if (!entry)
goto fail2; return 0; fail2: remove_proc_entry("devices", proc_bus_input_dir);
fail1: remove_proc_entry("bus/input", NULL);
return -ENOMEM;
}

只是纯粹的打印信息,之前的一些操作已经没有在proc子系统中进行处理了。

接口函数

功能 接口
设备驱动层 向核心层注册一个输入设备input device input_register_device
事件处理层 注册一个输入事件处理器 input handler input_register_handler
事件处理层 向内核注册一个handle结构 input_register_handle

device与handler匹配

input core 层在中间,承上启下完成了 device 与 handler的匹配。

我们看看是怎么实现的。

驱动层注册device

input_dev对象

struct input_dev {      

    void *private;          //输入设备私有指针,一般指向用于描述设备驱动层的设备结构
const char *name; // 提供给用户的输入设备的名称
const char *phys; // 提供给编程者的设备节点的名称 文件路径,比如 input/buttons
const char *uniq; // 指定唯一的ID号,就像MAC地址一样
struct input_id id;//输入设备标识ID,用于和事件处理层进行匹配 unsigned long evbit[NBITS(EV_MAX)]; //位图,记录设备支持的事件类型(可以多选)
/*
* #define EV_SYN 0x00 //同步事件
* #define EV_KEY 0x01 //按键事件
* #define EV_REL 0x02 //相对坐标
* #define EV_ABS 0x03 //绝对坐标
* #define EV_MSC 0x04 //其它
* #define EV_SW 0x05 //开关事件
* #define EV_LED 0x11 //LED事件
* #define EV_SND 0x12
* #define EV_REP 0x14 //重复上报
* #define EV_FF 0x15
* #define EV_PWR 0x16
* #define EV_FF_STATUS 0x17
* #define EV_MAX 0x1f
*/ 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)]; //位图,记录设备支持的开关功能 unsigned int keycodemax; //设备支持的最大按键值个数
unsigned int keycodesize; //每个按键的字节大小
void *keycode; //指向按键池,即指向按键值数组首地址
int (*setkeycode)(struct input_dev *dev, int scancode, int keycode); //修改按键值
int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode); //获取按键值 struct ff_device *ff; unsigned int repeat_key; //支持重复按键
struct timer_list timer; //设置当有连击时的延时定时器 int state; int sync; //同步事件完成标识,为1说明事件同步完成 int abs[ABS_MAX + 1]; //记录坐标的值
int rep[REP_MAX + 1]; //记录重复按键的参数值 unsigned long key[NBITS(KEY_MAX)]; //位图,按键的状态
unsigned long led[NBITS(LED_MAX)]; //位图,led的状态
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 (*flush)(struct input_dev *dev, struct file *file); //输入设备断开后刷新函数
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); //事件处理 struct input_handle *grab; struct mutex mutex; //用于open、close函数的连续访问互斥
unsigned int users; struct class_device cdev; //输入设备的类信息
union { //设备结构体
struct device *parent;
} dev; struct list_head h_list; //handle链表
struct list_head node; //input_dev链表
};

input_register_device

还记得之前提到过的:在Linux驱动中使用input子系统,一般的驱动使用输入子系统只需要使用以下的接口。

struct input_dev *input_allocate_device(void);
// 分配输入设备函数 int input_register_device(struct input_dev *dev);
// 注册输入设备函数 void input_unregister_device(struct input_dev *dev);
// 注销输入设备函数 void __set_bit();
// 事件支持(初始化),告诉input输入子系统支持哪些事件,哪些按键 void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
// 在发生输入事件时,向子系统报告事件。 void input_sync();
// 同步用于告诉input core子系统报告结束

其中,涉及到匹配的有关函数就是input_register_device

/**
* input_register_device - register device with input core
* @dev: device to be registered
*
* This function registers device with input core. The device must be
* allocated with input_allocate_device() and all it's capabilities
* set up before registering.
* If function fails the device must be freed with input_free_device().
* Once device has been successfully registered it can be unregistered
* with input_unregister_device(); input_free_device() should not be
* called in this case.
*
* Note that this function is also used to register managed input devices
* (ones allocated with devm_input_allocate_device()). Such managed input
* devices need not be explicitly unregistered or freed, their tear down
* is controlled by the devres infrastructure. It is also worth noting
* that tear down of managed input devices is internally a 2-step process:
* registered managed input device is first unregistered, but stays in
* memory and can still handle input_event() calls (although events will
* not be delivered anywhere). The freeing of managed input device will
* happen later, when devres stack is unwound to the point where device
* allocation was made.
*/
int input_register_device(struct input_dev *dev)
{
static atomic_t input_no = ATOMIC_INIT(0);
struct input_devres *devres = NULL;
struct input_handler *handler;
unsigned int packet_size;
const char *path;
int error; if (dev->devres_managed) {
devres = devres_alloc(devm_input_device_unregister,
sizeof(struct input_devres), GFP_KERNEL);
if (!devres)
return -ENOMEM; devres->input = dev;
} /* 默认所有的输入设备都支持EV_SYN同步事件 */
__set_bit(EV_SYN, dev->evbit); /* 阻止 KEY_RESERVED 事件传递到用户空间 */
__clear_bit(KEY_RESERVED, dev->keybit); /* 确保 没有用到的掩码 是空的 */
input_cleanse_bitmasks(dev); packet_size = input_estimate_events_per_packet(dev);
if (dev->hint_events_per_packet < packet_size)
dev->hint_events_per_packet = packet_size; dev->max_vals = max(dev->hint_events_per_packet, packet_size) + 2;
dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
if (!dev->vals) {
error = -ENOMEM;
goto err_devres_free;
} /*
* 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;
} /* 没有定义设备的getkeycode函数,则使用默认的获取键值函数 */
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
/*没有定义设备的setkeycode函数,则使用默认的设定键值函数*/
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode; dev_set_name(&dev->dev, "input%ld",
(unsigned long) atomic_inc_return(&input_no) - 1); /* 重点1:添加设备 */
error = device_add(&dev->dev);
if (error)
goto err_free_vals; /* 获取并打印设备的绝对路径名称 */
path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
pr_info("%s as %s\n",
dev->name ? dev->name : "Unspecified device",
path ? path : "N/A");
kfree(path); error = mutex_lock_interruptible(&input_mutex);
if (error)
goto err_device_del; /* 重点2: 把设备挂到全局的input子系统设备链表 input_dev_list 上 */
list_add_tail(&dev->node, &input_dev_list); /* 重点3: nput设备在增加到input_dev_list链表上之后,会查找
* input_handler_list事件处理链表上的handler进行匹配,这里的匹配
* 方式与设备模型的device和driver匹配过程很相似,所有的input devicel
* 都挂在input_dev_list上,所有类型的事件都挂在input_handler_list
* 上,进行“匹配相亲”
*/
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler); input_wakeup_procfs_readers(); mutex_unlock(&input_mutex); if (dev->devres_managed) {
dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n",
__func__, dev_name(&dev->dev));
devres_add(dev->dev.parent, devres);
}
return 0; err_device_del:
device_del(&dev->dev);
err_free_vals:
kfree(dev->vals);
dev->vals = NULL;
err_devres_free:
devres_free(devres);
return error;
}
EXPORT_SYMBOL(input_register_device);

上面的函数实现了:

  • 添加设备
  • 把输入设备挂到输入设备链表input_dev_list
  • 遍历input_handler_list链表,查找并匹配输入设备对应的事件处理层,如果匹配上了,就调用handlerconnnect函数进行连接。

设备就是在此时注册的,下面分析handler就清晰了。 (input_attach_handler放到分析handler时再做讲解,更容易理解。)

事件层注册handler

事件层通过input_register_handler来完成注册。

不同的事件处理层主要是用来支持输入设备并与用户空间交互,这部分代码一般由供应商编写,不需要我们自己去编写。

因为Linux内核已经自带有一些事件处理器,可以支持大部分输入设备,比如Evdev.c、mousedev.c、joydev.c等。

对于Event handler:根据事件注册一个handler,将handler挂到链表input_handler_list下,然后遍历input_dev_list链表。查找并匹配输入设备对应的事件处理层,如果匹配上了,就调用connect函数进行连接,并创建input_handle结构。

Linux中默认的事件层不多;默认地,有evdev.c(事件设备),tsdev.c(触摸屏设备),joydev.c(joystick操作杆设备),keyboard.c(键盘设备),mousedev.c(鼠标设备) 这5个内核自带的设备处理函数注册(input_register_handler)到input子系统中。

$ cd drivers/input
$ grep -w -nR "input_register_handler"
misc/keychord.c:301: ret = input_register_handler(&kdev->input_handler);
keycombo.c:225: ret = input_register_handler(&state->input_handler);
apm-power.c:113: return input_register_handler(&apmpower_handler);
evbug.c:110: return input_register_handler(&evbug_handler);
evdev.c:1117: return input_register_handler(&evdev_handler);
joydev.c:947: return input_register_handler(&joydev_handler);
input.c:2155: * input_register_handler - register a new input handler
input.c:2162:int input_register_handler(struct input_handler *handler)
input.c:2183:EXPORT_SYMBOL(input_register_handler);
mousedev.c:1107: error = input_register_handler(&mousedev_handler);

下面,我们以evdev.c(事件驱动)为例子进行讲解。

evdev.c实现了对evdev的管理,根据Docuentation/input/input.txt的描述,evdev is the generic input event interface. It passes the events generated in the kernel straight to the program, with timestamps.

因此,evdev只是对输入设备这一类设备的管理,并不涉及具体如鼠标、键盘以及游戏摇杆等设备的管理,但是驱动中完全可以使用关于evdev的API直接给用户空间程序上报事件。

input_handler对象

// include/linux/input.h
struct input_handler { void *private; void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
void (*events)(struct input_handle *handle,
const struct input_value *vals, unsigned int count);
bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
bool (*match)(struct input_handler *handler, struct input_dev *dev);
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
void (*disconnect)(struct input_handle *handle);
void (*start)(struct input_handle *handle); bool legacy_minors;
int minor;
const char *name; const struct input_device_id *id_table; struct list_head h_list;
struct list_head node;
};

input_register_handler

事件处理层的处理器会调用input_register_handler来注册。

// drivers/input/evdev.c
static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.legacy_minors = true,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
}; static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
} static void __exit evdev_exit(void)
{
input_unregister_handler(&evdev_handler);
} module_init(evdev_init);
module_exit(evdev_exit);

evdev_init直接调用input_register_handler 注册一个evdev_handler的input_handler对象。

/**
* input_register_handler - register a new input handler
* @handler: handler to be registered
*
* This function registers a new input handler (interface) for input
* devices in the system and attaches it to all input devices that
* are compatible with the handler.
*/
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int error; error = mutex_lock_interruptible(&input_mutex);
if (error)
return error; INIT_LIST_HEAD(&handler->h_list); /* 重点1 : 把设备处理器挂到全局的input子系统设备链表input_handler_list上 */
list_add_tail(&handler->node, &input_handler_list); /* 重点2 : 遍历input_dev_list,试图与每一个input_dev进行匹配 */
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler); input_wakeup_procfs_readers(); mutex_unlock(&input_mutex);
return 0;
}
EXPORT_SYMBOL(input_register_handler);

这个注册过程和上面驱动层注册device(input_register_device)的过程非常相识。

注册过程中的匹配判断

你注意到了吗,无论是驱动层还是事件层,它们各自在向core注册的时候,都会调用到一个input_attach_handler

我们看看input_attach_handler是如何匹配dev与handler的。

全局变量

在device与handler注册的时候,都用到了下面的两个链表,其实这两个链表是用来俩俩匹配的。

static LIST_HEAD(input_dev_list);
static LIST_HEAD(input_handler_list);

对于handler和device,分别用链表input_handler_listinput_dev_list进行维护。当handler或者device增加或减少的时候,分别往这两链表增加或删除节点,这两条都是全局链表。

input_attach_handler

实际上就做2个事情,先match(匹配),再connect(连接)

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error; /* 利用handler->id_table和dev进行匹配 */
id = input_match_device(handler, dev);
if (!id)
return -ENODEV; /*匹配成功,则调用handler->connect函数进行连接*/
error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
pr_err("failed to attach handler %s to device %s, error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error); return error;
}
input_match_device

还记得吗,struct input_dev中也有一个struct input_id id。只是我们当时没有提到。 id用于匹配条件中,对于属性的要求。

设备和handler的匹配条件:只要handler的id中evbit、keybit等等中的某一位设置了,input设备也得具备这个条件。

简单来说,必须具有相同ID属性的devive和handler才能匹配在一起。

//kernel/include/linux/mod_devicetable.h
struct input_device_id { kernel_ulong_t flags; __u16 bustype;
// ...
__u16 version; kernel_ulong_t evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1];
kernel_ulong_t keybit[INPUT_DEVICE_ID_KEY_MAX / BITS_PER_LONG + 1];
// ...
kernel_ulong_t swbit[INPUT_DEVICE_ID_SW_MAX / BITS_PER_LONG + 1]; kernel_ulong_t driver_info;
}; // 与设备匹配的要求。 static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev)
{
const struct input_device_id *id; for (id = handler->id_table; id->flags || id->driver_info; id++) { if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
continue;
// ... if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
continue; if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
continue; if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
continue; // ... if (!handler->match || handler->match(handler, dev))
return id;
} return NULL;
}
evdev_ids

由于 evdev.c 中的evdev_ids只有一个成员初始化,其他都为0(NULL):

// drivers/input/evdev.c
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};

因此,上面的if中,只有最后一个if满足条件,所以最后会以!handler->match而返回这个id。

这也就是为什么注释中说的,evdev.c 中的 handler 可以适用于任何的 devices(Matches all devices);因为evdev对device的要求很低。

handler->connect

对于evdev.c中的handler所对应的connect接口是evdev_connect

evdev_connect()主要是创建一个新的event设备,相应的设备节点文件是/dev/input/eventX

1、要注册一个新的evdev设备,首先要获取一个次设备号(之前说过,主设备号是13),失败便不再往下执行。

成功以后,这个次设备号还会作为一个相对值dev_no,用于决定/dev/input/eventX中的X的值。

2、一个evdev可以有多个client,所以初始化client_list。

3、而操作client的时候需要互斥,所以初始化client_lock,等待队列用于实现用户空间读取时的阻塞等待,exist成员置为true表示新的evdev已经存在,minor是新的evdev在evdev_table中的索引,而设备节点文件/dev/input/eventx中的x就是这里minor决定的。

4、另外一项重要工作就是用handle绑定一组匹配成功的input_devinput_handler

只要是input_devinput_handler匹配了,handle中的handler成员指向了配对成功的handler:dev成员指向了配对成功的device,而private成员则指向了evdev设备本身。

struct input_handle {

    void *private;

    int open;
const char *name; struct input_dev *dev; // 匹配成功的 一对 dev-handler中的 dev
struct input_handler *handler; // 匹配成功的 一对 dev-handler中的 handler struct list_head d_node;
struct list_head h_node;
};

通过上文我们知道,链表input_handler_listinput_dev_list保存了handlerdevice,这两条链表都是全局链表。

那么input_hande 保存在哪里?实际上,而input_hande 没有一个全局的链表。它注册的时候将自己分别挂在了input_dev_listinput_handler_listh_list上了;

从此,建立好了三者的铁三角关系,通过input_handler_listinput_dev_list以及input_hande 中任何一方,都可以找到彼此。

最终:设备(/dev/input/eventX)被创建,我们最终就能够通过基于struct file_operationsopenread等方式在应用层获取数据。

evdev_connect

对于connect函数,每种事件处理器的实现都有差异,但原理都相同。

我们结合evdev.c中的connect看看是不是这样子的:

static struct input_handler evdev_handler = {
// ...
.connect = evdev_connect,
// ...
}; /*
* Create new evdev device. Note that input core serializes calls
* to connect and disconnect.
*/
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int dev_no;
int error; /* 申请一个新的次设备号 */
minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true); evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); /* 初始化client_list列表和evdev_wait队列 */
INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait);
evdev->exist = true; dev_no = minor;
/* Normalize device number if it falls into legacy range */
if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
dev_no -= EVDEV_MINOR_BASE; /*设置设备节点名称,eventX 就是在此时设置 */
dev_set_name(&evdev->dev, "event%d", dev_no); /* 重点:初始化evdev结构体,其中handle为输入设备和事件处理的关联接口 */
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev; /*设置设备号,应用层就是通过设备号,找到该设备的*/
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev); /* input_dev设备驱动和handler事件处理层的关联,就在这时由handle完成 */
error = input_register_handle(&evdev->handle); cdev_init(&evdev->cdev, &evdev_fops);
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1); /* 将设备加入到Linux设备模型,它的内部将找到它的bus,然后让它的bus
给它找到它的driver,在驱动或者总线的probe函数中,一般会在/dev/目录
先创建相应的设备节点,这样应用程序就可以通过该设备节点来使用设备了,
/dev/eventX 设备节点就是在此时生成
*/
error = device_add(&evdev->dev); return 0;
}

事件层fops代码解析

fops接口在输入子系统中由事件处理层完成。

当应用程序执行open("/dev/input/eventX", O_RDWR)时等调用的时候,系统调用经过文件系统一系列操作后就会执行file_operations中的成员函数。

这些函数会从事件处理层到input core层再到驱动程序的input_dev对应的方法(例如open)依次执行下去。

evdev.c为例:

static const 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,
.llseek = no_llseek,
};

这些file_operations对于子系统都比较重要。

evdev_open

evdev_open函数代表注册一个client,此后准备接收数据。

static int evdev_open(struct inode *inode, struct file *file)
{
// 首先,是找到 evdev设备
struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);
unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
unsigned int size = sizeof(struct evdev_client) +
bufsize * sizeof(struct input_event);
struct evdev_client *client;
int error; // 第二,给evdev_client结构分配内存空间。
client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
if (!client)
client = vzalloc(size); client->clkid = CLOCK_MONOTONIC;
client->bufsize = bufsize;
spin_lock_init(&client->buffer_lock);
snprintf(client->name, sizeof(client->name), "%s-%d",
dev_name(&evdev->dev), task_tgid_vnr(current)); // 第三,将client的evdev指向当前evdev,并且将client挂接到evdev的链表上
client->evdev = evdev;
evdev_attach_client(evdev, client); // 第四,调用evdev_open_device()
error = evdev_open_device(evdev); file->private_data = client;
// 第五,设置文件的模式
nonseekable_open(inode, file); return 0;
}

找到edev

第一件事,是找到 evdev设备,获取的方式很kernel:

struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);

还记得吗,前面evdev_connect中申请了一个evdev对象。并通过cdev_add(&evdev->cdev, evdev->dev.devt, 1);加入到了字符设备中。

因此,在这里能够通过container_of将其取出。取出以后是为了继续操作。

evdev的原型是这样子的:

struct evdev {
int open; // open,当用户open此设备时,open的值加1。
struct input_handle handle; // 包括了匹配的 dev 与 handler
wait_queue_head_t wait; // 等待队列
struct evdev_client __rcu *grab; // 可以为evdev实例指定一个struct evdev_client实例,这样在传递Input消息时就只会传递给这一个struct evdev_client实例,而不会传递给所有的struct evdev_client实例。每open一次就会生成一个struct evdev_client实例。
struct list_head client_list; // 用来把所有的struct client_list实例连接在一起
spinlock_t client_lock; /* protects client_list */
struct mutex mutex; // 同步相关的锁
struct device dev; // dev,用来嵌入到设备模型中。
struct cdev cdev; //
bool exist; // struct evdev被成功实例化后,exist的值就为true
};

新建evdev_client对象

    client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
if (!client)
client = vzalloc(size); client->clkid = CLOCK_MONOTONIC;
client->bufsize = bufsize;
spin_lock_init(&client->buffer_lock);
snprintf(client->name, sizeof(client->name), "%s-%d",
dev_name(&evdev->dev), task_tgid_vnr(current));
evdev_client原型

这里非常有必要说一下evdev_client结构,此结构定义如下:

struct evdev_client {
unsigned int head;
unsigned int tail;
unsigned int packet_head; /* [future] position of the first element of next packet */
spinlock_t buffer_lock; /* protects access to buffer, head and tail */
struct wake_lock wake_lock;
bool use_wake_lock;
char name[28];
struct fasync_struct *fasync;
struct evdev *evdev;
struct list_head node;
int clkid;
unsigned int bufsize;
struct input_event buffer[];
};

可以看到这个结构中多数成员都是关于buffer的。

这完全可以理解,一个输入设备要上报输入事件,从代码上来说,肯定有存储事件的缓冲区,而且有时候一个设备会上报多个事件,那么我们需要能够存储多个事件的缓冲区。

bufsize
    unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
unsigned int size = sizeof(struct evdev_client) +
bufsize * sizeof(struct input_event); // ---------------------
static unsigned int evdev_compute_buffer_size(struct input_dev *dev)
{
unsigned int n_events =
max(dev->hint_events_per_packet * EVDEV_BUF_PACKETS,
EVDEV_MIN_BUFFER_SIZE); return roundup_pow_of_two(n_events);
}

bufsize成员:表示当前输入设备的输入事件缓冲区最多可以存放事件的个数。

值得注意的是,输入事件缓冲区的大小不是静态的,这里采用的是基于柔型数组进行动态申请;

在计算bufsize的时候,进行向上对齐2的幂。这样子在判断的时候可以做一个小的优化,例如:

client->head &= client->bufsize - 1; // 相当于 取模%,但比%快。

将client挂接到evdev的链表上

第三步,将client的evdev指向当前evdev,并且将client挂接到evdev的链表上。

    client->evdev = evdev;
evdev_attach_client(evdev, client);
-----------------------------
static void evdev_attach_client(struct evdev *evdev,
struct evdev_client *client)
{
spin_lock(&evdev->client_lock);
list_add_tail_rcu(&client->node, &evdev->client_list);
spin_unlock(&evdev->client_lock);
}

既然这里有挂接链表的操作,说明一个evdev可以对应着多个client,这一点在evdev_disconnect()中已经体现:evdev需要遍历自己的client_list,给所有的client发信号。

static void evdev_disconnect(struct input_handle *handle)
{
struct evdev *evdev = handle->private; device_del(&evdev->dev);
evdev_cleanup(evdev);
input_free_minor(MINOR(evdev->dev.devt));
input_unregister_handle(handle);
put_device(&evdev->dev);
} void input_unregister_handle(struct input_handle *handle)
{
struct input_dev *dev = handle->dev; list_del_rcu(&handle->h_node); /*
* Take dev->mutex to prevent race with input_release_device().
*/
mutex_lock(&dev->mutex);
list_del_rcu(&handle->d_node);
mutex_unlock(&dev->mutex); synchronize_rcu();
}

一张简单的描述evdev是怎么把client连接起来的图:

调用evdev_open_device()

第四步,调用evdev_open_device(),并且将file的private_data初始化为client。

    error = evdev_open_device(evdev);

    file->private_data = client;

evdev_open_device()代码如下:

static int evdev_open_device(struct evdev *evdev)
{
int retval; retval = mutex_lock_interruptible(&evdev->mutex); if (!evdev->exist)
retval = -ENODEV;
else if (!evdev->open++) {
retval = input_open_device(&evdev->handle);
if (retval)
evdev->open--;
} mutex_unlock(&evdev->mutex);
return retval;
}

此函数比较简单,上锁、检查参数后调用input_open_device(),代码如下:

int input_open_device(struct input_handle *handle)
{
struct input_dev *dev = handle->dev; // 找到 对应的dev,还记得吗,3者可以互相找到对方
int retval; retval = mutex_lock_interruptible(&dev->mutex); if (dev->going_away) {
retval = -ENODEV;
goto out;
} handle->open++; if (!dev->users++ && dev->open)
retval = dev->open(dev); if (retval) {
dev->users--;
if (!--handle->open) {
/*
* Make sure we are not delivering any more events
* through this handle
*/
synchronize_rcu();
}
} out:
mutex_unlock(&dev->mutex);
return retval;
}

input_open_device()的核心是对handle->open和dev->users成员自增,调用dev->open()。

将handle的open计数加1,表示此handle已经打开。

但是我没搞懂这里的逻辑,为什么只有dev->users为零的时候才会调用dev->open()?还有,dev->open是在哪里操作的?dev->open就是我们使用input_register_device()之前自己赋值操作的方法。

到这里就分析完了evdev_open(),evdev_poll()就是调用poll_wait(),evdev_fasync()就是调用fasync_helper(),这里不多说了,这两个函数就说明我们可以在应用层使用poll()或者select()实现输入设备的异步阻塞IO以及异步非阻塞IO操作。

设置文件的模式

nonseekable_open函数不说了,就是设置文件的模式。

    nonseekable_open(inode, file);
//-------------------
int nonseekable_open(struct inode *inode, struct file *filp)
{
filp->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE);
return 0;
}

evdev_read

首先思考个问题,在应用层调用read函数,是如何读取到实际硬件的按键值的?

接下来分析evdev_read(),它的作用是从已经打开的evdev中读取输入事件,如果缓冲区中有事件则传递给用户空间,否则阻塞等待:

1、从file->private_data中获取evdev_client结构,接着根据client获取当前的evdev设备。

2、判断读取长度是否合法。如果用户空间要读取的长度小于一个事件的长度,那么直接返回;如果当前事件缓冲区为空,并且设备存在,同时当前文件的操作为非阻塞IO方式,那么直接返回-EAGAIN即可。

3、将数据拷贝给应用空间。我们在下面再介绍这个。

4、判断当前是否读到了数据,退出或者等待。

  • 等待:调用wait_event_interruptible(),条件为:“输入事件缓冲区非空 或 evdev设备不存在了”,如果是因为设备不存在了,那么直接返回-ENODEV即可,否则读取缓冲区中的事件,传递给用户空间即可。
  • 停止:如果已经传递给用户空间的数据长度已经不小于count或者输入事件缓冲区已经空了,就退出。
static ssize_t evdev_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
// 1、获取对应的实例,准备操作。
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
struct input_event event;
size_t read = 0;
int error; // 2、判断读取长度是否合法
if (count != 0 && count < input_event_size())
return -EINVAL; for (;;) {
// 设备不存在
if (!evdev->exist || client->revoked)
return -ENODEV; // 不允许非阻塞读取
if (client->packet_head == client->tail &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN; /*
* count == 0 is special - no IO is done but we check
* for error conditions (see above).
*/
if (count == 0)
break; // 3、将数据拷贝给应用空间。
while (read + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) { // 相当于 copy_to_user(),把取出来的事件拷贝给用户空间。
if (input_event_to_user(buffer + read, &event))
return -EFAULT; read += input_event_size();
} // 4、判断当前是否读到了数据,读到数据则结束,不能读则等待。
if (read)
break;
if (!(file->f_flags & O_NONBLOCK)) {
error = wait_event_interruptible(evdev->wait,
client->packet_head != client->tail ||
!evdev->exist || client->revoked);
if (error)
return error;
}
} return read;
}

evdev_fetch_next_event

此函数就是从输入事件缓冲区中取出一个事件。

static int evdev_fetch_next_event(struct evdev_client *client,
struct input_event *event)
{
int have_event; // 1、加锁
spin_lock_irq(&client->buffer_lock); // 2、取出事件
have_event = client->packet_head != client->tail;
if (have_event) {
*event = client->buffer[client->tail++];
client->tail &= client->bufsize - 1;
if (client->use_wake_lock &&
client->packet_head == client->tail)
wake_unlock(&client->wake_lock);
} spin_unlock_irq(&client->buffer_lock); return have_event;
}

1、对client加锁

2、根据client->headclient->tail判断缓冲区是否有事件,若两者不相等那么有,否则没有。

3、取出事件。由于tail指向将要处理的事件,若要取事件,那么就根据tail就可以得到之。

evdev_event

evdev_read()是读取事件,我们知道read的时候是不能 非阻塞读取的,如果读取时没有数据呢,那么是在哪里来唤醒?换句话说,如果读取操作阻塞在了wait队列上,那么我们在哪里将其唤醒呢?

读取时使用wait_event_interruptible()实现阻塞IO。

答案在本节揭晓。

当产生了一个输入事件以后,evdev_event会被调用。

谁在调用wake_up_interruptible?

搜索这个evdev->wait这个等待队列变量,找到evdev_event函数里唤醒:

static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct input_value vals[] = { { type, code, value } }; evdev_events(handle, vals, 1);
---> evdev_pass_values(client, vals, count, ev_time);
---> wake_up_interruptible(&evdev->wait);
}

其中evdev_event()evdev.c(事件处理层) 的evdev_handler->event成员,,

static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
// ...
};
调用关系统计

那么是谁调用了evdev_event()(即evdev_handler->event())?

答案:设备中使用input_event进行了事件上报。

input.c中试搜下handler->eventhandler.event下函数调用:

/*
input_event;
-->input_handle_event;
---->input_pass_values
------>input_to_handler
-------->handler->events(handle, vals, count);
*/ void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags; if (is_event_supported(type, dev->evbit, EV_MAX)) { spin_lock_irqsave(&dev->event_lock, flags);
// 这里
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
} static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition; disposition = input_get_disposition(dev, type, code, value); if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value); if (!dev->vals)
return; if (disposition & INPUT_PASS_TO_HANDLERS) {
struct input_value *v; if (disposition & INPUT_SLOT) {
v = &dev->vals[dev->num_vals++];
v->type = EV_ABS;
v->code = ABS_MT_SLOT;
v->value = dev->mt->slot;
} v = &dev->vals[dev->num_vals++];
v->type = type;
v->code = code;
v->value = value;
} if (disposition & INPUT_FLUSH) {
if (dev->num_vals >= 2)
// 这里
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
} else if (dev->num_vals >= dev->max_vals - 2) {
dev->vals[dev->num_vals++] = input_value_sync;
// 这里
input_pass_values(dev, dev->vals, dev->num_vals);
dev->num_vals = 0;
} } static void input_pass_values(struct input_dev *dev,
struct input_value *vals, unsigned int count)
{
struct input_handle *handle;
struct input_value *v; if (!count)
return; rcu_read_lock(); handle = rcu_dereference(dev->grab);
if (handle) {
count = input_to_handler(handle, vals, count);
} else {
// 遍历client_list链表,每找到一个client就调用evdev_pass_event函数
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (handle->open)
// 这里,注意,这里开始准备使用handle找到对应的handler。
count = input_to_handler(handle, vals, count);
} rcu_read_unlock(); add_input_randomness(vals->type, vals->code, vals->value); /* trigger auto repeat for key events */
for (v = vals; v != vals + count; v++) {
if (v->type == EV_KEY && v->value != 2) {
if (v->value)
input_start_autorepeat(dev, v->code);
else
input_stop_autorepeat(dev);
}
}
} static unsigned int input_to_handler(struct input_handle *handle,
struct input_value *vals, unsigned int count)
{
struct input_handler *handler = handle->handler;
struct input_value *end = vals;
struct input_value *v; for (v = vals; v != vals + count; v++) {
if (handler->filter &&
handler->filter(handle, v->type, v->code, v->value))
continue;
if (end != v)
*end = *v;
end++;
} count = end - vals;
if (!count)
return 0; if (handler->events)
// 这里
handler->events(handle, vals, count);
else if (handler->event)
for (v = vals; v != end; v++)
// 这里
handler->event(handle, v->type, v->code, v->value); return count;
}

显然,就是input_dev通过输入核心为驱动层提供统一的接口input_event,来向事件处理层上报数据并唤醒。

分析

现在我们已经知道了通讯的流程,具体看看整个流程都做了什么事情。

传递事件
// linux/input.h
struct input_value {
__u16 type;
__u16 code;
__s32 value;
}; /*
* Pass incoming events to all connected clients.
*/
static void evdev_events(struct input_handle *handle,
const struct input_value *vals, unsigned int count)
{
struct evdev *evdev = handle->private;
struct evdev_client *client;
ktime_t time_mono, time_real; time_mono = ktime_get();
time_real = ktime_sub(time_mono, ktime_get_monotonic_offset()); rcu_read_lock(); // evdev的grab成员,这个成员代表exclusice access的含义
client = rcu_dereference(evdev->grab); if (client) // 如果grab不是NULL,那么这个evdev只能被一个client访问使用
evdev_pass_values(client, vals, count, time_mono, time_real);
else // 如果没有grab成员,那么此事件就需要给evdev->client_list上所有成员发消息,发消息的函数是evdev_pass_event(),
{
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_values(client, vals, count,
time_mono, time_real);
} rcu_read_unlock();
} static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct input_value vals[] = { { type, code, value } }; evdev_events(handle, vals, 1);
}

在新的版本中,evdev_event是对evdev_events的简单封装。

evdev_events的作用:把typecode以及value封装进input_value类型的vals中(相当于封装了一个代表的事件),每一个发送给当前evdev对应的client。

准备删除。最后,如果type是EV_SYN且code是SYN_REPORT,那么我们将调用wake_up_interruptible(),实现读取操作的同步,这也意味着,驱动程序中要显式调用report相关的函数才能解锁读取操作。

保存事件

这里重点看evdev_pass_values

// include/uapi/linux/input.h
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
}; static void evdev_pass_values(struct evdev_client *client,
const struct input_value *vals, unsigned int count,
ktime_t *ev_time)
{
struct evdev *evdev = client->evdev;
const struct input_value *v; // 迭代器
struct input_event event;
bool wakeup = false; if (client->revoked)
return; event.time = ktime_to_timeval(ev_time[client->clk_type]); /* Interrupts are disabled, just acquire the lock. */
spin_lock(&client->buffer_lock); // 对每一个事件进行处理,并发送到cline
for (v = vals; v != vals + count; v++) {
if (__evdev_is_filtered(client, v->type, v->code))
continue; if (v->type == EV_SYN && v->code == SYN_REPORT) {
/* drop empty SYN_REPORT */
if (client->packet_head == client->head)
continue; wakeup = true;
} event.type = v->type;
event.code = v->code;
event.value = v->value;
// 保存事件
__pass_event(client, &event);
} spin_unlock(&client->buffer_lock);
// 唤醒
if (wakeup)
wake_up_interruptible(&evdev->wait);
}
保存
struct evdev_client {
unsigned int head;
unsigned int tail;
unsigned int packet_head; /* [future] position of the first element of next packet */
spinlock_t buffer_lock; /* protects access to buffer, head and tail */
struct fasync_struct *fasync;
struct evdev *evdev;
struct list_head node;
unsigned int clk_type;
bool revoked;
unsigned long *evmasks[EV_CNT];
unsigned int bufsize;
struct input_event buffer[];
}; static void __pass_event(struct evdev_client *client,
const struct input_event *event)
{
// 1、保存事件
client->buffer[client->head++] = *event;
client->head &= client->bufsize - 1; // 2、 如果缓冲区满了
if (unlikely(client->head == client->tail)) {
/*
* This effectively "drops" all unconsumed events, leaving
* EV_SYN/SYN_DROPPED plus the newest event in the queue.
*/
client->tail = (client->head - 2) & (client->bufsize - 1); client->buffer[client->tail].time = event->time;
client->buffer[client->tail].type = EV_SYN;
client->buffer[client->tail].code = SYN_DROPPED;
client->buffer[client->tail].value = 0; client->packet_head = client->tail;
} // 3、异步通知。
if (event->type == EV_SYN && event->code == SYN_REPORT) {
client->packet_head = client->head;
kill_fasync(&client->fasync, SIGIO, POLL_IN);
}
}

1、既然每次产生一个输入事件,那么这个函数肯定要将产生的事件存储到事件队列环形缓冲区中。

上面代码对于client->head的操作为:存储了事件队列以后,还要确保head没超过bufsize。如果超过了bufsize,那么应该从零开始。

由于在evdev_open中,申请buffsize时对2的幂向上取整,因此只需要进行位与就可以完成相当于%的操作(小优化)。

2、如果存储了当前事件后,事件队列缓冲区满了怎么办?内核只留下两个事件:

  • 最新的一个事件其实不是事件,它的code为SYN_DROPPED;
  • 而另一个就是newest事件,接着把packet_head成员更新为tail,代表一系列事件的头部。

3、如果我们的事件type=EV_SYN,code=SYN_REPORT,那么通过kill_fasync()发送SIGIO。因此,如果我们的输入设备文件支持异步IO操作的话,应用层应该能通过异步通知的方式接收SIGIO,从而在信号回调函数中读取到输入事件。

通常,用input_sync来完成对输入事件的上报。

Linux驱动:输入子系统(input-subsystem) 分析的更多相关文章

  1. Linux输入子系统(Input Subsystem)

    Linux输入子系统(Input Subsystem) http://blog.csdn.net/lbmygf/article/details/7360084 input子系统分析  http://b ...

  2. 12.Linux之输入子系统分析(详解)

    版权声明:本文为博主原创文章,转载请标注出处:   在此节之前,我们学的都是简单的字符驱动,涉及的内容有字符驱动的框架.自动创建设备节点.linux中断.poll机制.异步通知.同步互斥/非阻塞.定时 ...

  3. linux驱动学习之Input输入子系统

    以前,看过国嵌关于input子系统的视频课程,说实话,我看完后脑子里很乱,给我的印象好像是input子系统驱动是一个全新的驱动架构,疑惑相当多.前几天在网上,看到有很多人介绍韦东山老师的linux驱动 ...

  4. linux内核输入子系统分析

    1.为何引入input system? 以前我们写一些输入设备(键盘.鼠标等)的驱动都是采用字符设备.混杂设备处理的.问题由此而来,Linux开源社区的大神们看到了这大量输入设备如此分散不堪,有木有可 ...

  5. 10. linux输入子系统/input 设备【转】

    转自:https://www.cnblogs.com/crmn/articles/6696819.html 按键事件信息之上报绝对事件信息之上报相对事件信息之上报功能键驱动编写多点触控事件的上报 只产 ...

  6. Linux 输入子系统 input

    一.输入子系统 针对输入设备设计:触摸屏.键盘.按键.传感器.鼠标...... 二.每种设备都属于字符设备驱动,程序的写法步骤也相同 1.实现入口函数 xxx_init() 和卸载函数 xxx_exi ...

  7. 输入子系统--event层分析【转】

    转自:http://blog.csdn.net/beyondioi/article/details/9186723 ########################################## ...

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

    本文转载自:http://blog.csdn.net/jscese/article/details/42099381 在前文Linux/Android——usb触摸屏驱动 - usbtouchscre ...

  9. 8、linux下输入子系统

    input_sync(button_dev);    /*通知接收者,一个报告发送完毕*/ 参考:http://www.51hei.com/bbs/dpj-27652-1.html  很详细说明 in ...

  10. arm Linux 驱动LED子系统 测试

    Linux内核在3.0以上引入了设备树概念(具体哪个版本不清楚)在编译内核后需要将与之对应的dtb文件也下载人板子上才能使内核与硬件关联起来. dtb文件是有dts文件编译后生成的:例如 /* * C ...

随机推荐

  1. Web Audio API 第6章 高级主题

    高级主题 这一章涵盖了非常重要的主题,但比本书的其他部分稍微复杂一些. 我们会深入对声音添加音效,完全不通过任何音频缓冲来计算合成音效, 模拟不同声音环境的效果,还有关于空 3D 空间音频. 重要理论 ...

  2. Solution Set - SAM

    讲解一些 SAM 经典的应用.可以结合 字 符 串 全 家 桶 中 SAM 的部分食用. 洛谷P2408 求不同子串个数.在 SAM 中,所有结点是一个等价类,包含的字符串互不相同.结点 \(u\) ...

  3. 如何阅读 Paper

    前言 论文(Paper)通常是新技术.算法.编程方法或软件工具的首次公布.通过阅读论文,我们可以了解最新的技术进展,保持自己的技能和知识是最新的. 同时,论文提供了对特定主题深入理解的机会.它们通常包 ...

  4. NASM中的伪指令

    伪指令不是真正的指令,而是为了方便NASM汇编器而存在,但是它们的地位与真正的指令相同: label: instruction operands ; comment instruction部分就可以是 ...

  5. ETSI GS MEC 012,无线网络信息服务 API

    目录 文章目录 目录 版本 功能理解 版本 ETSI GS MEC 012 V2.1.1 (2019-12) 功能理解 RNIS(Radio Network Information Service,无 ...

  6. Asp-Net-Core开发笔记:使用原生的接口限流功能

    前言 之前介绍过使用 AspNetCoreRateLimit 组件来实现接口限流 从 .Net7 开始,AspNetCore 开始内置限流组件,当时我们的项目还在 .Net6 所以只能用第三方的 现在 ...

  7. Istio(八):istio安全之认证,启用mTLS

    目录 一.模块概览 二.系统环境 三.istio认证 3.1 证书创建与轮换 3.2 对等认证和请求认证 3.2.1 对等认证 3.2.2 请求认证 3.3 mTLS 3.3.1 双向 TLS 3.3 ...

  8. CF527E Data Center Drama 题解

    目录 题目 题意 题解 思路 详解 注意事项 代码 AC 记录 尾声 题目 CF527E Data Center Drama · 戳这里 题意 给定一张 $n$ 个点 $m$ 条边的连通无向图. 你需 ...

  9. 利用Django实现文件上传

    一.form表单的形式上传文件 1.路由 urlpatterns = [ path("upload/", views.UploadView.as_view(),) ] 2.视图 f ...

  10. .NETCore Nuget 发布包含静态文件 content file

    .NETCore 在.csproj引用资源中标记pack配置 <pack>true</pack>1例如 <ItemGroup> <Content Includ ...