【驱动】input子系统全面分析
【驱动】input子系统全面分析
初识linux输入子系统
linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler)、输入子系统核心层(InputCore)和输入子系统设备驱动层。
input输入子系统框架分析
输入子系统由输入子系统核心层( Input Core ),驱动层和事件处理层(Event Handler)三部份组成。
一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过 input driver -> Input core -> Event handler -> userspace 到达用户空间传给应用程序。
1.系统核心层
2.handler层(事件处理层)
handler层是纯软件层,包含不同的解决方案,如键盘,鼠标,游戏手柄等,但是没有设计到硬件方面的操作
对于不同的解决方案,都包含一个名为input_handler的结构体,该结构体内含的主要成员如下
.id_table 一个存放该handler所支持的设备id的表(其实内部存放的是EV_xxx事件,用于判断device是否支持该事件)
.fops 该handler的file_operation
.connect 连接该handler跟所支持device的函数
.disconnect 断开该连接
.event 事件处理函数,让device调用
h_list 也是一个链表,该链表保存着该handler到所支持的所有device的中间站:handle结构体的指针
3.device层(驱动层)
device是纯硬件操作层,包含不同的硬件接口处理,如gpio等
对于每种不同的具体硬件操作,都对应着不同的input_dev结构体
该结构体内部也包含着一个h_list
4:input_handler_list和input_device_list
对于handler和device,分别用链表input_handler_list和input_device_list进行维护,
当handler或者device增加或减少的时候,分别往这两链表增加或删除节点。
5.input子系统框架图
input子系统调用过程分析
1.当外部应用程序需要调用输入子系统的函数时,会先通过主设备号进入到核心层,然后
2.当外部应用程序需要调用输入子系统的函数时,
3.当外部中断到达的时候,会先确定中断事件,然后,然后在read中返回(也就是当device有多个对应的handler的时候,input_event会向所有的handler上报事件)
4.
input_register_handler的内部实现:往input_handler_list加入新增的handler节点,然后对input_device_list的所有结点(也就是所有的device)进行遍历,通过.id_table查看该device是否支持该handler,
5.当需要加入新的device时,需要先构建input_dev结构体,然后调用input_register_device对该input_dev进行注册
:往input_device_list加入新增的device节点,然后对input_handler_list的所有结点(也就是所有的handler)进行遍历,通过handler 的.id_table查看该handler是否支持该device,对支持的device调用该handler的.connect,一一地构建input_handle结构体,连接handler跟device
在输入子系统框架下,我们一般的编写驱动也就是对device部分进行编写(分配input_dev并配置,驱动入口,出口,中断时进行中断判断,然后上报事件等),然后对该device的input_dev进行注册
Input输入子系统数据结构分析
input_dev
input_dev 这是input设备基本的设备结构,每个input驱动程序中都必须分配初始化这样一个结构,成员比较多
(1)有以下几个数组:
unsigned evbit[BITS_TO_LONGS(EV_CNT)];
unsigned keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned absbit[BITS_TO_LONGS(ABS_CNT)];
unsigned swbit[BITS_TO_LONGS(SW_CNT)];
evbit[BITS_TO_LONGS(EV_CNT)]; 这个数组以位掩码的形式,代表了这个。
设置方式:
dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS)
absbit[BITS_TO_LONGS(ABS_CNT)]; 这个数组也是以位掩码的形式,代表这个类型的
触摸屏驱动支持EV_ABS,所以要设置这个数组, 有一个专门设置这个数组的函数
inline input_set_abs_params( input_dev *dev, axis, min, max, fuzz, ->absmin[axis] =->absmax[axis] =->absfuzz[axis] =->absflat[axis] =->absbit[BIT_WORD(axis)] |= BIT_MASK(axis);
}
触摸屏驱动中是这样调用的
input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0); //这个是设置ad转换的x坐标
input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0); //这个是设置ad转换的y坐标
input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0); //这个是设置触摸屏是否按下的标志
设置ABS_X编码值范围为0-0x3ff,因为mini2440的AD转换出的数据最大为10位,所以不会超过0x3ff。
(2) struct input_id id 成员
这个是的
__u16 vendor;
__u16 product;
__u16 version;
};
如果需要特定的事件处理器来处理这个设备的话,这几个就非常重要,因为子系统核心是通过他们,将设备驱动与事件处理层联系起来的。但是因为触摸屏驱动所用的事件处理器为evdev,匹配所有,所有这个初始化也无关紧要。
input_handler
input_handler 这是
(1)几个操作函数
(*)( input_handle *handle, unsigned type, unsigned code, (*connect)( input_handler *handler, input_dev *dev, input_device_id * (*disconnect)( input_handle * (*start)( input_handle *handle);
event 函数是当事件处理器接收到了来自input设备传来的事件时调用的处理函数,负责处理事件,。
connect 函数是当一个input设备模块注册到内核的时候调用的,将事件处理器与输入设备联系起来的函数,也就是。
disconnect 函数实现connect相反的功能。
(2) 两个id
input_device_id *id_table;
input_device_id *blacklist;
这两个数组都会用在connect函数中,input_device_id结构与input_id结构类似,但是input_device_id有一个flag,用来让程序选择比较哪项,如:busytype,vendor还是其他。
(3) 两个链表
list_headh_list;
list_headnode;
input_handle
input_handle 结构体代表一个成功配对的input_dev和input_handler
*;
open;
* input_dev *dev;
input_handler *handler;
list_head d_node;
list_head h_node;
};
三个数据结构之间的关系
是硬件驱动层,代表一个input设备
是事件处理层,代表一个事件处理器
属于核心层,代表一个配对的input设备与input事件处理器
input_dev 通过全局的链接在一起。设备注册的时候实现这个操作。
input_handler 通过全局的链接在一起。事件处理器注册的时候实现这个操作(事件处理器一般内核自带,一般不需要我们来写)
没有一个全局的链表,它注册的时候将自己分别挂在了input_dev 和 input_handler 的h_list上了。
通过input_dev 和input_handler就可以找到input_handle 在设备注册和事件处理器, 注册的时候都要进行配对工作,配对后就会实现链接。
通过input_handle也可以找到input_dev和input_handler。
补充两个结构体
(1) evdev设备结构
open;
minor;
input_handle handle;
wait_queue_head_t wait;
evdev_client *grab;
list_head client_list;
spinlock_t client_lock;
device dev;
};
evdev结构体在配对成功的时候生成,由handler->connect生成,对应设备文件为/class/input/event(n)。
如触摸屏驱动的event0,这个设备是用户空间要访问的设备,可以理解它是一个虚拟设备,因为没有对应的硬件,但是通过handle->dev 就可以找到input_dev结构,而它对应着触摸屏,设备文件为/class/input/input0。这个设备结构生成之后保存在evdev_table中,索引值是minor
(2) evdev用户端结构
head;
tail;
spinlock_t buffer_lock;
fasync_struct *fasync;
evdev *evdev;
list_head node;
};
这个结构在进程打开event0设备的时候调用evdev的open方法,在open中创建这个结构,并初始化。在关闭设备文件的时候释放这个结构。
Input输入子系统数据结构关系图
input输入子系统主要函数分析
各种注册函数
每种数据结构都代表一类对象,所以每种数据结构都会对应一个注册函数,他们都定义在子系统核心的input.c文件中。
主要有三个注册函数
input_register_device
input_register_handle
input_register_handler
1.input_register_device
注册一个input输入设备,这个注册函数在三个注册函数中是驱动程序唯一调用的。
input_register_device( input_dev * atomic_t input_no = ATOMIC_INIT( input_handler * * __set_bit(EV_SYN, dev->evbit); init_timer(&dev-> (!dev->rep[REP_DELAY] && !dev-> dev->timer.data = ( dev->timer.function = dev->rep[REP_DELAY] = dev->rep[REP_PERIOD] = (!dev-> dev->getkeycode = (!dev-> dev->setkeycode = dev_set_name(&dev->dev, (unsigned ) atomic_inc_return(&input_no) - error = device_add(&dev-> path = kobject_get_path(&dev-> printk(KERN_INFO dev->name ? dev->name : , path ? path : error = mutex_lock_interruptible(& device_del(&dev-> list_add_tail(&dev->node, & list_for_each_entry(handler, & mutex_unlock(& } View Codeinput_register_device完成的就是:初始化一些默认的值,将自己的device结构添加到linux设备模型当中,将input_dev添加到input_dev_list链表中,然后寻找合适的handler与input_handler配对,配对的核心函数是input_attach_handler。
下面看看函数
input_attach_handler( input_dev *dev, input_handler * input_device_id * (handler->blacklist && input_match_device(handler-> - id = input_match_device(handler-> (! - error = handler-> (error && error != - handler->name, kobject_name(&dev-> } View Codeinput_attach_handler的主要功能就是调用了两个函数,一个input_match_device进行配对,一个connect处理配对成功后续工作。
下面看看函数
input_device_id *input_match_device( input_device_id * input_dev * (; id->flags || id->driver_info; id++ (id->flags & (id->bustype != dev->