Input子系统作为linux下很重要的一个设备管理框架,同时运用了platform平台设备总线,在驱动的入门阶段,值得深入的分析。在碰壁很多次之后,小编决定对这部分的内容进行总结,也算是对platform平台总线和input设备驱动框架的回顾。(以keyboard按键驱动为例,由于工作原因源码不做展示)
Linux中的输入子系统从上到下依次是:事件处理层(evdevt.c)、核心处理层(input.c)、设备驱动层(sunxi_keyboard.c)。这三层共同合作,每层负责自己所负责的部分,各司其职,很好的将设备和驱动进行了分离,也增强了其可移植性,由于设备树的引入,更使得一套驱动在经过简单的配置修改,可以适配多个硬件设备。下面分别介绍一下三部分的职责:
事件处理层(evevt.c):这是与用于交互直接相关的一层,为用户提供统一的接口(open、write、read、close),注册设备节点(/dev/iuput/evet0),处理驱动层提交上来的数据,将数据的格式进行统一的处理。 对于事件处理层相关的一个最重要的结构体是:input_handle。 与这个结构体相关的,也是事件处理层核心的一个注册函数是:input_register_handle( )。
核心处理层(input.c):为驱动层提供规范的接口(也就是注册函数、接口函数、处理函数),驱动层只需要关心硬件的驱动,采集底层的硬件数据,而最后则是通过这些接口完成数据的上报(上报至事件处理层)。 对于核心层最重要的结构体是input_handler。 与这个结构体相关的是核心处理层核心的一个注册函数:input_register_handler( )。
设备驱动层(keyboard.c):负责对硬件设备的读写访问,利用中断进行数据的采集上发,并利用核心处理层提供的接口函数将采集到的数据进行数据的规范处理。对于核心层最重要的结构体是input_dev。 与这个结构体相关的是核心处理层核心的一个注册函数:input_register_device( )。
关于这三层是如何进行协同工作的,请看下图:Linux输入子系统的事件处理机制:
Input子系统分成硬件驱动层(input device)、子系统核心层(input core)和事件处理层(event handler),因此它也会有相应的三个主要的数据结构:
1.struct input_dev结构体:硬件驱动层的基本数据结构体,包括设备相关的一些信息;
2.struct input_handle结构体:子系统核心层数据结构体,当input_dev与input_handler匹配成功时产生此结构体,也是连接input_dev与input_handler的桥梁;
3.struct input_handler结构体:事件处理结构体,描述事件处理的逻辑关系。
他们的具体关系如下图所示:
划重点 划重点 划重点
所有的input_dev结构体的node变量在注册时添加到input_dev_list链表上,再游历input_handler_list链表是否有与其匹配的input_handler结构体;
所有的input_handler数据结构体的node变量在注册时添加到input_handler_list链表上,同时也游历input_dev_list链表是否有与其匹配的input_dev结构体;
当游历input_handler_list(input_dev_list)链表是否有input_handler(input_dev)结构体与之匹配时,会调用input_match_device()函数,此函数作用是比较input_dev中的id变量是否与input_hander中id_table比较,如果相等,说明匹配成功,则通过handler->connect()函数下的input_register_handle()函数注册handle数据结构体;input_handle数据结构体中的dev变量指向匹配成功的input_dev数据结构体handler变量指向配成功的input_handler数据结构体,d_node变量添加到input_dev数据结构中的h_list链表中,h_node变量添加到input_handler数据结构体的h_list链表中。Input_handle数据结构体是一对匹配成功的input_dev与input_handler连接的桥梁。
下面对三层进行具体的分析
接下来从 sunxi_keyboard.c 设备驱动层往上根据代码分析一下驱动的工作流程。
subsys_initcall_sync(keyboard_init);
(注意:这里有一个细节问题,在sunxi_keyboard.c中的入口函数是 subsys_initcall_sync 说明这部分工作的在内核驱动的过程中就已经完成了,在驱动和设备匹配之后就已经完成后,就已经做好了一切采集数据的准备; 而在evdev.c中的入口函数是module_init(evdev_init) 这个入口函数进去后会将设备驱动层和事件处理层联系到一起,然后等待输入事件的发生,将设备驱动层采集到的数据通过处理成统一的格式交付给Linux中固定的设备节点)
subsys_initcall_sync(keyboard_init);
ret = platform_driver_register(&keyboard_driver); 驱动的注册
static struct platform_driver sunxi_keyboard_driver =
{
.probe = keyboard_probe,
.remove = keyboard_remove,
.driver =
{
.name = "keyboard",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = SUNXI_KEYBOARD_PM_OPS,
#endif
.of_match_table = of_match_ptr(keyboard_of_match),
},
};
首先在这里确定了driver->name,为之后的匹配打下了基础。
Probe回调函数是关键,值得注意的是static int keyboard_probe(struct platform_device *pdev),这个函数默认的参数是 struct platform_device 用描述具体的input输入子系统硬件设备(这个就是总线、驱动、设备中的设备)。这里将完成设备信息的填充、注册、以及具体的硬件读写操作。其中常见包括:硬件的初始化、中断的初始化、input_dev类设备的申请、以及中断服务程序。最终在中断服务函数中调用input_report_key 函数完成数据的上报。
input_report_key
input_event
input_handle_event
input_pass_values
input_to_handler
handler->events( )
到这里之后,在evdev.c中的input_event类型的event赋值给了evdev_client的数组,传送结束。数据完成上报。
其实到调用 input_report_key函数时,硬件设备驱动层的任务就已经完成,完成了数据的采集,然后 input_report_key 的具体实现是在 input.c 中完成,为事件处理层提供一个统一格式的 input_event 类型的数据。其中input_dev 是一个特别大的一个结构体类型,所有的输入子系统类设备最终上报时都是遵守这个结构体类变量的格式进行数据的传输。 而不同的设备可以有属于自己的数据描述结构体,但最后都会包含这个结构体。例如:
struct sunxi_key_data {
struct platform_device *pdev;
struct clk *mclk;
struct clk *pclk;
struct input_dev *input_dev;
struct adc_disc *disc;
void __iomem *reg_base;
u32 scankeycodes[KEY_MAX_CNT];
int irq_num;
u32 key_val;
unsigned char compare_later;
unsigned char compare_before;
u8 key_code;
u8 last_key_code;
char key_name[16];
u8 key_cnt;
int wakeup;
};
总结:经过设备驱动层的努力工作
①最终完成了驱动 driver 的注册,设备 device 信息的填充,设备的注册,设备的初始化;
②最终在 /sys 目录下可以找到Linux驱动模型中总线、设备以及驱动的节点;
③等待中断的发生,将采集到的数据统一成 input_event 格式进行数据的上报;
开始分析之前必须说一下这层中最重要的一个结构体 input_handler :事件处理结构体,描述事件处理的逻辑关系
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;
};
event 函数:是当事件处理器接收到了来自input设备传来的事件时调用的处理函数,负责处理事件,非常重要。
划重点 划重点 划重点:Connect 函数:当input_dev与input_handler配对成功时被调用,负责配对成功后的后续工作,注册一个input_handle数据结构体。
下面我们开始从代码分析事件处理层驱动的工作流程:
module_init(evdev_init);
注意这里是 module_init 驱动在被 insmod 安装时会被调用,而我们常见的是通过 make menuconfig 进行模块的配置,配置完内核进行编译时就会调用这个函数。
Int input_register_handler(struct input_handler *handler)
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
这里的 input_register_handler 负责将新设备加入input_handler_list链表中,遍历input_dev_list链表,将handler与合适的dev匹配,匹配成功的话调用handler的connect函数,将handler与dev关联起来。过程与input_register_devtce()步骤一样,只是这里是绑定dev。
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,
};
而在这个关键的函数 evdev_connect 中:
①开辟一个 evdev 类型变量的空间,结构体内包含着需要注册的结构体 input_handle,然后进行结构体的填充,最后利用 input_register_handle() 进行 input_handle 的注册;
②用户层接回调函数的指定:file_operations evdev_fops
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,
};
用户调用 read 进行数据的读取,实际调用的是回调函数 evdev_read,其中ecdev_fetch_next_event 取到了一个event值,然后复制到用户层,这样整个读取数据的流程完成。
总结:经过事件处理层的努力
①在Linux下创建了具体的设备节点,用户可以通过复这个节点进行统一的文件操作来实现对设备的读写;
②最终为用户提供了统一的接口函数(open、write等)的回调函数。
subsys_initcall(input_init);
和硬件驱动层一样,在内核启动时就进行了此部分的工作:
input子系统也是字符设备驱动,在初始化时调用input_init(void)函数,在input_init()函数中主要完成三件事:
①注册一个input_class类,作用是在sysfs中将所有input device都位于/dev/class/input下:
class_register(&input_class)
②在proc下面建立相关的交互文件:
input_proc_init()
③注册设备号,主设备号为13,次设备号为0~255