文章目录
- 前言
- Input子系统简介
- Input子系统代码实现框架
- Linux Input子系统支持的数据类型
- input核心层
- 设备驱动层
- input_allocate_device 与 函数
- input_set_capability
- input_register_device 函数
- input_unregister_device 与 input_free_device 函数
- 事件处理层
- input_attach_handler函数
- input_match_device 函数
- evdev_connect 函数
- input_register_handle 函数
- 参考链接
前言
本片文章是从设备驱动程序层面整理总结,目的是想记录大佬的知识,为了后面能够学以致用。
<阿杰。>写的真不错。
Input子系统简介
input子系统就是管理输入的子系统,和pinctrl和gpio子系统一样,都是Linux内核针对某一类设备而创建的框架。
input子系统分为input驱动层、input核心层、input事件处理层,最终给用户空间提供可访问的设备节点。
对于用户空间,所有的输入设备以文件的形式供用户应用程序使用。
Input子系统代码实现框架
驱动层:输入设备的具体驱动程序,比如按键驱动程序。主要是实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。如:input_register_device; 通知事件处理层对事件进行处理;在/Proc下产生相应的设备信息。 设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。
事件层:主要是和用户空间交互。用户编程的接口(设备节点),并处理驱动层提交的数据处理。(Linux中在用户空间将所有的设备都当初文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev/input下生成相应的设备文件node,这些操作在输入子系统中由事件处理层完成)。
Linux Input子系统支持的数据类型
EV_SYN | 0x00 | 同步事件 |
EV_SYN | 0x00 | 同步事件 |
EV_KEY | 0x01 | 按键事件 |
EV_REL | 0x02 | 相对坐标(如:鼠标移动,报告相对最后一次位置的偏移) |
EV_ABS | 0x03 | 绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置) |
EV_MSC | 0x04 | 其它 |
EV_SW | 0x05 | 开关 |
EV_LED | 0x11 | 按键/设备灯 |
EV_SND | 0x12 | 声音/警报 |
EV_REP | 0x14 | 重复 |
EV_FF | 0x15 | 力反馈 |
EV_PWR | 0x16 | 电源 |
EV_FF_STATUS | 0x17 | 力反馈状态 |
EV_MAX | 0x1f | 事件类型最大个数和提供位掩码支持 |
input核心层
CODE PATH : drivers/input/input.c
input核心层会向Linux内核注册一个input类,系统启动后会在/sys/class目录下生成一个input子目录
static int __init input_init(void)
{
int err;
err = class_register(&input_class); // 创建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), // 向linux内核注册input字符设备
INPUT_MAX_CHAR_DEVICES, "input");
.....
}
static int __init input_proc_init(void)
{
struct proc_dir_entry *entry;
proc_bus_input_dir = proc_mkdir("bus/input", NULL); // 在proc目录下创建bus/input目录
if (!proc_bus_input_dir)
return -ENOMEM;
entry = proc_create("devices", 0, proc_bus_input_dir, //input目录下创建devices文件
&input_devices_proc_ops);
if (!entry)
goto fail1;
entry = proc_create("handlers", 0, proc_bus_input_dir, // input目录下创建handlers文件
&input_handlers_proc_ops);
if (!entry)
goto fail2;
.....
CODE PATH : kernel-4.19/include/linux/input.h // 头文件中有注释结构体成员含义
struct input_dev {
const char *name; //提供给用户的输入设备名称
const char *phys; //提供给编程者的设备节点名称
const char *uniq; //指定唯一的ID号,就像MAC地址一样
struct input_id id; //输入设备标识ID,用于和事件处理层进行匹配
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
// 下面的成员是用来表示该input设备能够上报的事件类型有哪些,用位的方式来表示
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
int (*open)(struct input_dev *dev); // 设备的open函数
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); // 上报事件
设备驱动层
在驱动加载模块中,设置input设备支持的事件类型
注册中断处理函数,例如键盘设备需要编写按键的抬起、放下,触摸屏设备需要编写按下、抬起、绝对移动,鼠标设备需要编写单击、抬起、相对移动,并且需要在必要的时候提交硬件数据(键值/坐标/状态等等)
将输入设备注册到输入子系统中(在使用input子系统时,只需要注册一个input设备)。
input_allocate_device 与 函数
(1)使用input_allocate_device函数申请一个input_dev
struct input_dev *input_allocate_device(void)
{
struct input_dev *dev;
动态申请内存,使用GFP_KERNEL方式,注意GFP_KERNEL可能导致睡眠,不能在中断中调用这个函数
dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
if (dev) {
dev->dev.type = &input_dev_type;
dev->dev.class = &input_class; //添加进input类设备模型中
device_initialize(&dev->dev);
mutex_init(&dev->mutex); //初始化互斥锁
spin_lock_init(&dev->event_lock); //初始化自旋锁
INIT_LIST_HEAD(&dev->h_list); //初始化handle链表
INIT_LIST_HEAD(&dev->node); //初始化输入设备链表
__module_get(THIS_MODULE);
}
return dev;
}
input_set_capability
(2)初始化input_dev的事件类型以及事件值(设置输入设备可以上报哪些输入事件)
input_register_device 函数
(3)input_dev初始化完成以后就需要向Linux内核注册input_dev了,需要用到input_register_device函数。
定义在drivers/input/input.c文件中。
int input_register_device(struct input_dev *dev)
{
static atomic_t input_no = ATOMIC_INIT(0);
struct input_handler *handler;
const char *path;
int error;
/* Every input device generates EV_SYN/SYN_REPORT events. */
__set_bit(EV_SYN, dev->evbit);
/* KEY_RESERVED is not supposed to be transmitted to userspace. */
__clear_bit(KEY_RESERVED, dev->keybit);
/* Make sure that bitmasks not mentioned in dev->evbit are clean. */
input_cleanse_bitmasks(dev);
/* 以下四个dev成员,如果设备驱动没有指定的函数,将赋予系统默认的函数 */
if (!dev->hint_events_per_packet)
dev->hint_events_per_packet =
input_estimate_events_per_packet(dev);
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;
}
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
/* 动态获取input设备的ID号,名称为input*, *为id号 */
/* 例如:input5 */
dev_set_name(&dev->dev, "input%ld",
(unsigned long) atomic_inc_return(&input_no) - 1);
/* 在/sys目录下创建设备目录和文件 */
/* 例如:/sys/devices/virtual/input/input5/ */
error = device_add(&dev->dev);
if (error)
return error;
/* 在终端上打印设备的绝对路径名称 */
/* 例如:input: keyinput as /devices/virtual/input/input5 */
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) {
device_del(&dev->dev);
return error;
}
/* 把设备挂到全局的input子系统设备链表input_dev_list上 */
list_add_tail(&dev->node, &input_dev_list);
/* 核心重点,input设备在增加到input_dev_list链表上之后,会查找
* input_handler_list事件处理链表上的handler进行匹配,这里的匹配
* 方式与设备模型的device和driver匹配过程很相似,所有的input
* 都挂在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);
return 0;
}
input_unregister_device 与 input_free_device 函数
(4)卸载input驱动的时候需要先使用input_unregister_device函数注销掉注册的input_dev,然后使用input_free_device函数释放掉前面申请的input_dev
struct input_dev *inputdev; /* input 结构体变量 */
/* 驱动入口函数 */
static int __init xxx_init(void)
{
......
inputdev = input_allocate_device(); /* 申请 input_dev */
inputdev->name = "test_inputdev"; /* 设置 input_dev 名字 */
/*********第一种设置事件和事件值的方法***********/
__set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
__set_bit(EV_REP, inputdev->evbit); /* 重复事件 */
__set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */
/************************************************/
/*********第二种设置事件和事件值的方法***********/
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
/************************************************/
/*********第三种设置事件和事件值的方法***********/
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
/************************************************/
/* 注册 input_dev */
input_register_device(inputdev);
......
return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
input_unregister_device(inputdev); /* 注销 input_dev */
input_free_device(inputdev); /* 删除 input_dev */
}
事件处理层
input_attach_handler函数
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
id = input_match_device(handler, dev); //匹配事件驱动,事件驱动一共有三种evdev,mousedev,joydev
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id); //调用事件驱动的connect函数进行匹配
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_attach_handler 函数里面有两个函数比较重要,input_match_device 和 handler->connect, 看看具体是怎样实现的。
input_match_device 函数
static const struct input_device_id *input_match_device(struct input_handler *handler,
struct input_dev *dev)
{
const struct input_device_id *id;
int i;
for (id = handler->id_table; id->flags || id->driver_info; id++) {
/* 以下通过flags中设置的位来匹配设备的总线类型、经销商、生产ID和版本ID
* 如果没有匹配上将进行MATCH_BIT匹配 */
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
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用于匹配设备驱动中是否设置了这些位,MATCH_BIT的宏
* 被定义在input.c中,我们在设备驱动中设置的事件类型会与事件链表中的
* 所有事件类型进行比较,匹配成功了将返回id,证明真的很合适,否则NULL
*/
MATCH_BIT(evbit, EV_MAX);
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);
if (!handler->match || handler->match(handler, dev))
return id;
}
return NULL;
}
handler 是事件驱动结构体的指针,事件驱动一般有三种evdev,mousedev,joydev,分别对应三个结构体evdev_handler ,mousedev_handler ,joydev_handler ,如果匹配成功了会返回id,再回看 input_attach_handler 函数,若id不为NULL,就会调用 handler->connect 函数。假如现在匹配到 evdev_handler 这个事件驱动,然后就会调用evdev_handler->connect 函数,下面来看看evdev_handler->connect 函数做了什么。
evdev_connect 函数
drivers/input/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,
};
在注册evdev驱动时,evdev_handler结构体的connect成员指向evdev_connect函数,所以evdev_handler->connect 就是 evdev_connect 函数了,evdev_connect 函数也是定义在drivers/input/evdev.c文件中,内容如下:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int error;
for (minor = 0; minor < EVDEV_MINORS; minor++)
if (!evdev_table[minor])
break;
if (minor == EVDEV_MINORS) {
pr_err("no more free evdev devices\n");
return -ENFILE;
}
/* 给evdev事件层驱动分配空间 ,
* 可以不要关心 evdev ,只看 evdev->handle 即可,这里构建了一个 handle ,
* 注意不是handler, handle 就是个中间件,可以理解成胶带,
* 它把 handler 与 dev 连在一起 */
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev)
return -ENOMEM;
INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait);
dev_set_name(&evdev->dev, "event%d", minor);
evdev->exist = true;
evdev->minor = minor;
evdev->hw_ts_sec = -1;
evdev->hw_ts_nsec = -1;
/* 第一次建立联系,在 handle 中记录 dev 与 handle 的信息,
* 这样通过handle就可以找到dev与handler, 即是实现
* handle -> dev , handle -> hander 的联系 */
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, EVDEV_MINOR_BASE + minor); // 申请设备号
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
error = input_register_handle(&evdev->handle); // 注册 handle
if (error)
goto err_free_evdev;
error = evdev_install_chrdev(evdev);
if (error)
goto err_unregister_handle;
error = device_add(&evdev->dev); //在 /dev/input 类下面创建设备节点, 名字为event%d
if (error)
goto err_cleanup_evdev;
......
}
内容里面比较重要就是evdev->handle这个成员了,它的作用就是把 hander 和 dev 连在一起,hander 表示事件驱动层,dev表示设备驱动层,这样 事件驱动层 和 设备驱动层 之间就通过handle这个 ”红娘“ 建立了关系了。现在可以通过 handle 找到 hander 或者 dev,不过还差一步,就是实现双向性,通过hander 或者 dev 也能找到 handle ,这样才能实现 handle —hander — dev 三者之间畅通无阻,而这一实现就体现在 handle 注册函数input_register_handle里面。
input_register_handle 函数
int input_register_handle(struct input_handle *handle)
{
/* 第二次建立联系 */
struct input_handler *handler = handle->handler;
struct input_dev *dev = handle->dev;
int error;
error = mutex_lock_interruptible(&dev->mutex);
if (error)
return error;
if (handler->filter)
list_add_rcu(&handle->d_node, &dev->h_list);
else
list_add_tail_rcu(&handle->d_node, &dev->h_list); // 将handle 记录在 dev->h_list 链表中
mutex_unlock(&dev->mutex);
list_add_tail_rcu(&handle->h_node, &handler->h_list); // 将handle 记录在 handler->h_list 链表中
if (handler->start)
handler->start(handle);
return 0;
}
input_register_handle函数注册handle时,把handle作为节点分别加入到dev->h_list 链表和handler->h_list 链表中去,至此,dev 与 hander 也可以找到handle了,dev <-> handle <-> handler 之间畅通无阻了。
通过上图可以看到input输入设备匹配关联的关键过程,以及涉及到的关键函数和数据。
以上主要是从input设备驱动程序的角度去看输入子系统的注册过程和三层之间的关联。
参考链接
http://t.zoukankan.com/zhaobinyouth-p-6257662.html
https://blog.csdn.net/qq_17639223/article/details/119489552
https://blog.csdn.net/qq_43286311/article/details/117437595