输入子系统原理分析和编程方法
一、输入子系统的作用和框架
1、什么是子系统?
内核是操作系统的核心。Linux内核提供很多基本功能,如虚拟内存、多任务、共享库、需求加载、共享写时拷贝(Copy-On-Write)以及网络功能等。增加各种不同功能导致内核代码不断增加。 Linux内核把不同功能分成不同的子系统的方法,通过一种整体的结构把各种功能集合在一起,提高了工作效率。同时还提供动态加载模块的方式,为动态修改内核功能提供了灵活性。
有多个输入设备需要驱动的时候,假如不考虑输入子系统,那么驱动步骤就是:创建设备号,创建文件,硬件初始化,实现fop,阻塞。每增加一个不同的输入设备就需要重复的写一个匹配的驱动,但是多个输入设备有共同点:获取到数据(操作硬件)不同的设备获取数据的方法不一样 ,这是差异化需要我们去做,但是输入设备的共同点目的就是产生数据,上报给用户(xxx_read, copy_to_user, 阻塞),多个输入设备,有部分差异,也有部分通用,内核就会考虑,将通用代码编写好,将差异化的代码留给驱动工程师。
输入设备总类繁杂,包括按键,键盘,触摸屏,鼠标,摇杆等等,它们本身都是字符设备,不过内核为了能将这些设备的共性抽象出来,简化驱动的开发,建立了一个Input子系统。Input子系统分为三层,从下至上分别是输入设备驱动层,输入核心层以及输入事件驱动层。这三层中的输入核心层和输入事件驱动层都是内核已经完成了的,因此需要我们完成的只有输入设备驱动层。
考虑输入设备主要的工作过程都是 动作产生(按键,触屏……)–>产生中断–>读取数值(键值,坐标……)–>将数值传递给应用程序。最后一个步骤就属于事件的处理,对于同一类设备,他们的处理方式都是相同的,因此内核已在事件驱动层为我们做好了,不需我们操心,而产生中断–>读取数值是因设备而异的,需要我们根据具体的设备来编写驱动。
一个大致的工作流程就是,input device向上层报告–>input core接收报告,并根据在注册input device时建立好的连接选择哪一类handler来处理事件–>通过handler将数据存放在相应的dev(evdev,mousedev…)实例的缓冲区中,等待应用程序来读取。当然,有时候也需要从应用层向设备层逆向传递,比如控制一些和设备相关的LED,蜂鸣器等。设备驱动层,输入核心层和事件处理层之间的关系可以用下图来阐释:

如果设计成输入子系统:使得应用编程人员和驱动编程人员编程的时候变得简单统一
- 兼容所有的输入设备 统一的编程驱动方法(实现差异化硬件操作) 统一的应用操作接口:/dev/input/event0,event1 open(“/dev/input/event0”);read(fd, struct input_event);struct input_event buff可以认为是一个统一的数据包
2、input子系统原理
linux输入子系统的体系结构可以分为三个层面,分别为:硬件驱动层、子系统核心层、事件处理层 ,意思就是每个层次只是负责单独的一个功能,无需参与其他的功能,有点类似函数的封装, 三个层面具体的功能如下:
硬件驱动层(input_device):其中硬件驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,需要驱动程序的作者来编写。抽象出一个对象,描述输入设备信息,初始化输入设备硬件,获取到数据,知道具体的数据是什么,但是不知道数据如何给用户。 子系统核心层(input_core):子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。 事件处理层(input handler):事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。完成fop:实现xxx_read(), xxx_open,将数据交给用户:数据从input device层,不知道具体数据是什么,只知道把数据给用户。

3、输入子系统的编程方式–学会最简单的输入子系统的开发方式
前提:input 核心层代码和input handler层需要在内核中必须有:
drivers/input/evdev.c // event handler
drivers/input/input.c // 核心层
make menuconfig
Device Drivers --->
Input device support --->
-*- Generic input layer (needed for keyboard, mouse, ...) // input.c
<*> Event interface //input handler层--evdev.c
编写步骤:
1,分配一个input device对象
2, 初始化input device对象
3,注册input device对象
二、输入子系统驱动的编写流程
- 分配一个input_device对象
- 初始化input_device对象的各个参数
- 注册input_device对象到内核
- 设置输入设备事件类型,主要是input_dev结构中的evbit和keybit
- 驱动事件报告
- 释放和注销input设备
1、分配input device对象
初始化函数定义了struct input_dev结构体,它用于描述一个输入子系统设备,任何驱动设备如果想标明自己是输入设备,都应该通过初始化这样的结构体,并且调用input_allocate_device()函数进行注册。
struct input_dev {
void *private; //输入设备私有指针,一般指向用于描述设备驱动层的设备结构
const char *name; //提供给用户的输入设备的名称
const char *phys; //提供给编程者的设备节点的名称
const char *uniq; //指定唯一的ID号,就像MAC地址一样
struct input_id id; //输入设备标识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)]; //位图,记录设备支持的开关功能
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; //类似私有指针,可以直接访问到事件处理接口event
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_allocate_device()函数,这个函数的功能是为新添加的输入设备分配内存,如果成功,将返回*input_dev **的指针结构,因此在写驱动的时候应该接受返回值,作为驱动层获得了一个新的输入设备操作的接口。
那么input_allocate_device()函数做了什么呢?打开函数看一下(input.c中实现):
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->cdev.class = &input_class; //支持热插拔的结构体
dev->cdev.groups = input_dev_attr_groups; //描述设备的硬件信息和支持的事件类型
class_device_initialize(&dev->cdev); //类设备初始化,添加进input类设备模型中
mutex_init(&dev->mutex); //初始化互斥锁
INIT_LIST_HEAD(&dev->h_list); //初始化handle链表
INIT_LIST_HEAD(&dev->node); //初始化输入设备链表
}
}
通过input_allocate_device()函数,我们设备驱动现在持有的input_dev里面就被赋予了input的“形象”,但是还需要我们去充实一下“内在”,因此,设备驱动程序,还需要为自己的设备增加自己的特性,才能创造独有的设备“形象”。
2、初始化input_device对象的各个参数
//添加设备信息--/sys/class/input/eventx/device/
inputdev->name = "simple input key";
inputdev->phys = "key/input/input0";
inputdev->uniq = "simple key0 for 4412";
inputdev->id.bustype = BUS_HOST;
input_dev->id.vendor = 0x0001;
input_dev->id.product = 0x0002;
input_dev->id.version = 0x0100;
input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);
input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN, PRESSURE_MAX, 0, 0);
完成了输入设备的初始化工作。但是这仅是初始化自己的“特点”,还需要通知输入子系统有这样一个新设备诞生了,这就需要调用输入子系统的注册函数input_register_device(input_dev)来完成。
3、注册input_device对象到内核
input_register_device()用于注册一个输入设备到内核中去。那么注册过程是怎样的呢?这是一个重点,我们在下面的代码中进行注释分析:
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;
/* 默认所有的输入设备都支持EV_SYN同步事件 */
set_bit(EV_SYN, dev->evbit);
/*
* 如果设备驱动没有指定重复按键(连击),系统默认提供以下的支持
* 其中init_timer为连击产生的定时器,时间到调用input_repeat_key函数
* 上报,REP_DELAY用于设置重复按键的键值,REP_PERIOD设置延时时间
*/
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子系统设备链表input_dev_list上 */
list_add_tail(&dev->node, &input_dev_list);
/* 动态获取input设备的ID号,名称为input*,其中后面的“*”动态获得,唯一的 */
snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),
"input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);
/* 如果这个值没有设置,系统把输入设备挂入设备链表 */
if (!dev->cdev.dev)
dev->cdev.dev = dev->dev.parent;
/* 在/sys目录下创建设备目录和文件 */
error = class_device_add(&dev->cdev);
if (error)
return error;
/* 获取并打印设备的绝对路径名称 */
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设备在增加到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();
return 0;
}
EXPORT_SYMBOL(input_register_handler);//导出驱动函数其他调用,其实input也是驱动模块
上面的代码主要的功能有以下几个功能,也是设备驱动注册为输入设备委托内核做的事情:
- 进一步初始化输入设备,例如连击事件;
- 注册输入设备到input类中;
- 把输入设备挂到输入设备链表input_dev_list中;
- 查找并匹配输入设备对应的事件处理层,通过input_handler_list链表
我们需要再分析下这个匹配的过程,“相亲”这种事情还是很有意思的,但是需要注意的是下面分析的代码是我们暂时无法分析的,因为那样会使得情况变得更加复杂,当我们从应用层往下分析的时候一切都会明白。input_attach_handler匹配过程如下:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
/* 如果handler的blacklist被赋值了并且则优先匹配 */
id = input_match_device(handler, dev);
if (!id)
return -ENODEV;
/* 否则利用handler->id_table和dev进行匹配,后面讲述匹配什么和过程 */
id = input_match_device(handler->id_table, dev);
if (!id)
return -ENODEV;
/* 这是一根“红线”,虽然你可能觉的是黑色的,但不可否认,他们真的匹配上了
* 调用handler->connnect函数进行匹配,匹配详细过程后面讲述
*/
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()函数,看一下这个匹配的条件是什么,如何匹配的过程是怎样的,匹配的结果会是什么? 新版本内核是input_match_device()函数中的子函数input_match_device_id来进行匹配的。
bool input_match_device_id(const struct input_dev *dev,
const struct input_device_id *id)
/* 以下通过flags中设置的位来匹配设备的总线类型、经销商、生产ID和版本ID
如果没有匹配上将进行MATCH_BIT匹配 */
{
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
return false;
if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
return false;
if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
return false;
if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
return false;
/* MATCH_BIT用于匹配设备驱动中是否设置了这些为,MATCH_BIT的宏
* 被定义在input.c中,我们在设备驱动中设置的事件类型会与事件链表中的
* 所有事件类型进行比较,匹配成功了将返回id,证明真的很合适,否则NULL
*/
if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX) ||
!bitmap_subset(id->keybit, dev->keybit, KEY_MAX) ||
!bitmap_subset(id->relbit, dev->relbit, REL_MAX) ||
!bitmap_subset(id->absbit, dev->absbit, ABS_MAX) ||
!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX) ||
!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX) ||
!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX) ||
!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX) ||
!bitmap_subset(id->swbit, dev->swbit, SW_MAX) ||
!bitmap_subset(id->propbit, dev->propbit, INPUT_PROP_MAX)) {
return false;
}
return true;
}
既然证明是合适的,接下来就应该登记注册,并公证了。还记得handler->connect(handler, dev, id)函数吧,当input_match_device()找到最合适的事件处理层驱动时,便执行handler->connect函数进行公证了,看下面这部分代码(假如说匹配找到了evdev类型的驱动,在内核代码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 dev_no;
int error;
/* EVDEV_MINORS为32,代表共能容纳32个evdev事件层设备,下面代码在找到空的地方,用于保存evdev事件层的数据,即上面定义的evdev */
minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
if (minor < 0) {
error = minor;
pr_err("failed to reserve new minor: %d\n", error);
return error;
}/* 这说明内核已经没办法再分配这种类型的设备了 */
/* 开始给evdev事件层驱动分配空间了 */
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev) {
error = -ENOMEM;
goto err_free_minor;
}
/* 初始化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;
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);
if (error)
goto err_free_evdev;
cdev_init(&evdev->cdev, &evdev_fops);
error = cdev_device_add(&evdev->cdev, &evdev->dev);
if (error)
goto err_cleanup_evdev;
return 0;
}
通过上述代码的执行,最终,输入设备在input_register_handle()的关联下与已经匹配上的handler结合,代码如下:
struct input_handler *handler = handle->handler;
/* 将d_node链接到输入设备的h_list,h_node链接到事件层的h_list链表上
* 因此,在handle中是输入设备和事件层的关联结构体,通过输入设备可以
* 找到对应的事件处理层接口,通过事件处理层也可找到匹配的输入设备
*/
list_add_tail(&handle->d_node, &handle->dev->h_list);
list_add_tail(&handle->h_node, &handler->h_list);
/* 如果start函数有定义则调用,但是evdev结构体中并未初始化这个函数 */
if (handler->start)
handler->start(handle);
以上是输入设备驱动注册的全过程,牵涉的代码比较多,需要从宏观上理顺。纵观整个过程,输入设备驱动最终的目的就是能够与事件处理层的事件驱动相互匹配,但是在drivers/input目录下有evdev.c事件驱动、mousedev.c事件驱动、joydev.c事件驱动等等,我们的输入设备产生的事件应该最终上报给谁,然后让事件驱动再去处理呢?知道了这么个原因再看上面代码就会明白,其实evdev.c、mousedev.c等根据硬件输入设备的处理方式的不同抽象出了不同的事件处理接口帮助上层去调用,而我们写的设备驱动程序只不过是完成了硬件寄存器中数据的读写,但提交给用户的事件必须是经过事件处理层的封装和同步才能够完成的,事件处理层提供给用户一个统一的界面来操作。
三、从应用层的角度出发看input子系统
以上部分已经借助input子系统把input设备驱动层与事件驱动层进行了关联,自己写的驱动XXX.c(输入设备层驱动)和evdev.c(事件处理层驱动)为例,来分析这一过程。
由于XXX.c中上报的事件类型为按键、绝对值坐标,而evdev事件驱动程序是全匹配的,因此早在XXX.c注册的过程中,就会创建设备节点/dev/input/event0(假设内核中没有其他的event类型的输入设备,这里就是event0),因此需要注意你的内核版本,以确定设备节点的位置,我们以“/dev/event0”为例来说明。
我们知道,应用层使用设备的第一步,是open(“/dev/event0”),因此这里event0的主设备号成为关键,因为主设备号将表明你是什么设备,我们ls -l查看/dev/event0发现:
crw-r—–1 root root 13, 64 2012-07-26 14:32 /dev/input/event0
由此可见主设备是13,输入命令cat /proc/devices查看主设备为13的是input设备,因此可以确定当我们执行open函数打开event0设备的时候,会调用input设备的open驱动函数,这个函数在input.c中,为了说明这一问题,需要从input驱动注册过程开始,还是input.c文件:
/* 输入设备初始化函数 */
static int __init input_init(void)
{
err = class_register(&input_class);
err = input_proc_init();
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES, "input");
}
input_proc_init()中
proc_create("handlers", 0, proc_bus_input_dir, &input_handlers_fileops);
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,
};
可以看到,输入设备初始化的过程首先建立了input类,初始化input在proc下的节点,然后注册input设备,设备名称为input,操作接口是input_handlers_fileops,主设备号是INPUT_MAJOR=13。
由以上可知,只要是主设备号为13的设备驱动程序,都是用input_handlers_fileops接口,即当event0设备使用open函数打开时,会调用到input_handlers_fileops接口中的input_proc_handlers_open驱动函数。
if中将判断是否为空并且事件处理层中的fops有没有初始化,如果没有就不能进行接口转换,报出设备不存在的错误,如果设备存在则把input设备的f_op驱动接口指向input_table表中存在的接口,并调用其open函数。那么这个input_table里面到底存放了什么呢?我们还是拿触摸屏驱动来讲解。由于触摸屏驱动已经完成了和evdev.c事件处理层的匹配,且次设备号为64,设备名称为/dev/event0,这是我们通过分析驱动注册中获得的内容,既然input核心设备注册了,XXX.C驱动也注册了,那会不会evdev设备也会注册了呢?答案是肯定的,要想知道input_table里面放了什么,必须要去查看evdev设备的注册过程,打开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, //evdev的值为64
.name = "evdev", //设备名称
.id_table = evdev_ids, //用于匹配设备驱动的数组
};
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler); //evdev设备驱动注册
}
由以上的内容可以知道evdev_handler也被作为一个设备来操作,但是它属于input handler事件处理设备,然而我们在evdev_handler结构体的.fops字段又发现它的驱动接口为字符设备类型,在input中,如果input_table匹配到了evdev_handler,将会把file->f_op=&evdev_fops,那么如果使用read、write等函数操作,将会调用到evdev_fops中的read、write。 在和input_dev的链表匹配成功,之后要调用handler中connect方法,实际就是event handler,实际调用了evdev_connect
evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
|
//找到一个没有被使用的次设备号, 从64开始, 65,66
minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
// 实例化 一个evdev对象
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
//初始化evdev对象
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;
dev_no -= EVDEV_MINOR_BASE; //减去了64
// 创建设备文件/dev/event0,1,2
dev_set_name(&evdev->dev, "event%d", dev_no);
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);// 12, 64
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev)
device_add(&evdev->dev);
//以上代码和device_create是一样
//利用handle记录input device和input handler
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
//你中有有我,我中有你
evdev->handle.private = evdev;
//将儿子关联到父亲(input handler)和母亲(input dev)
error = input_register_handle(&evdev->handle);
|
list_add_tail_rcu(&handle->d_node, &dev->h_list);
list_add_tail_rcu(&handle->h_node, &handler->h_list);
//初始化了cdev,完成了fops, 为用户提供文件io
cdev_init(&evdev->cdev, &evdev_fops);
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
至此设备的注册完成!对应event handler,在/dev中将多出一个event(x)设备文件,对应一个evdev实例,应用程序打开它的话也就意味着通过event handler来和设备驱动层传递事件。 再来看打开设备的过程,还是以event handler为例,假如打开一个event(x),则先执行:
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler;
const struct file_operations *old_fops, *new_fops = NULL;
int err;
lock_kernel();
/* No load-on-demand here? */
/*32个设备是共用一个handler的,通过此设备号得到设备对应的handler*/
handler = input_table[iminor(inode) >> 5];
if (!handler || !(new_fops = fops_get(handler->fops))) {
err = -ENODEV;
goto out;
}
/*
* That's _really_ odd. Usually NULL ->open means "nothing special",
* not "no device". Oh, well...
*/
if (!new_fops->open) {
fops_put(new_fops);
err = -ENODEV;
goto out;
}
old_fops = file->f_op;
/*定位fops*/
file->f_op = new_fops;
err = new_fops->open(inode, file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
out:
unlock_kernel();
return err;
}
通过此设备号所在的组(0~31),(32~63),(64~95)……就可以找到相应的handler,所有的handler都保存在input_table中,对于次设备号在64~95范围的设备,将定位到下标为2的handler,,也就是event handler,然后将用handler中的open函数替代之前的open函数,并执行新的open函数,这样就以handler本身定义的open来打开设备完成相应的初始化了。
总结: 1,分配evdev,并初始化,记录input device和handler的关系 2,创建设备节点/dev/event0 3, 注册cdev,并实现fops
4,关系: 多个input device可以对应一个event handler,一个input_device对应一个 evdev,对应于一个设备节点/dev/event0,1,2 5, 所有的设备节点调用open,read,write文件io的时候实际是调用cdev中fops中各个接口,最终都调用了 static const struct file_operations evdev_fops结构体。
四、数据是如何传递给用户层的?
open(“/dev/event1”, O_RDWR);
vfs sys_open(); struct file file->f_ops = cdev->ops; file->f_ops->open();
input handler 层:evdev.c
// 实际cdev是谁,就是evdev_connect注册的那个
struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);
// 通过儿子,找到老母input device
unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
// size就包含了很多input_event
unsigned int size = sizeof(struct evdev_client) +
bufsize * sizeof(struct input_event);
struct evdev_client *client;
// 分配一个client对像,描述一个缓冲队列,存放的就是input_event
client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
// client中有一个缓冲区
client->bufsize = bufsize;
spin_lock_init(&client->buffer_lock);
//在client中记录evdev
client->evdev = evdev;
// 将client加入到evdev中一个小链表
evdev_attach_client(evdev, client);
list_add_tail_rcu(&client->node, &evdev->client_list);
// 将client记录到file,方面其他的接口使用
file->private_data = client;
总结: 1、为输入设备分配一个缓冲区evdev_client,用户存放input device层上报的数据 2、evdev_client记录到evdev中 3、evdev_client记录到file中,方面其他的接口使用
应用程序中read,是如何获取到数据的?
read(fd, &event, sizeof(struct input_event));
vfs sys_read(); file->f_ops->read();
evdev.c
static const struct file_operations evdev_fops = {
.read = evdev_read,
evdev_read(struct file *file, char __user *buffer,size_t count, loff_t *ppos)
// 获取到open中分配的缓冲区对象
struct evdev_client *client = file->private_data;
//获取到evdev
struct evdev *evdev = client->evdev;
//表示一个数据包,要给用户
struct input_event event;
for (;;) {
// 实现非阻塞
if (client->packet_head == client->tail &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN;
while (read + input_event_size() <= count &&
// 1从缓冲区获取数据,存放在 input_event数据包
evdev_fetch_next_event(client, &event)) {
|
*event = client->buffer[client->tail++];
// 2, 将数据上报给用户
if (input_event_to_user(buffer + read, &event))
|
copy_to_user(buffer, event, sizeof(struct input_event)
// 3,统计上报多少数据
read += input_event_size();
}
// 如果当前是阻塞模式
if (!(file->f_flags & O_NONBLOCK)) {
//等待---休眠,需要被唤醒,有数据时候唤醒
error = wait_event_interruptible(evdev->wait,
client->packet_head != client->tail ||
!evdev->exist || client->revoked);
} 总结:
1,如果没有数据,就会休眠等待
2,如果有数据,就从缓冲区client->buffer[client->tail++]拿数据
通过copy_to_user上报给用户
疑问: 数据到底是如何存放在缓冲区的? 等待队列是谁唤醒的? input_report_key(inputdev, pdesc->key_code, 0); input_sync(inputdev);//上报数据结束
在编写的XXX.C驱动中利用下面两个函数进行数据上报。
input_report_key(inputdev, KEY_POWER, 1);
input_sync(inputdev);//上报数据结束
然而他们其实是input_event函数的封装,调用的都是input_event函数,这一函数在input.c中实现如下:
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);
}
}
其中input_handle_event(dev, type, code, value);中有一个event()函数与input_pass_event()函数。有些事件是发送给设备,而不是发送给 handler 处理的。event()函数用来向输入子系统报告一个将要发送给设备的事件,例如让 LED 灯点亮事件、蜂鸣器鸣叫事件等。当事件报告给输入子系统后,就要求设备处理这个事件。这里就体现了函数的一次次传递与封装!
参考前辈的文章: input子系统 input子系统整体流程全面分析