在此节之前,我们学的都是简单的字符驱动,涉及的内容有字符驱动的框架、自动创建设备节点、linux中断、poll机制、异步通知、同步互斥/非阻塞、定时器去抖动。
其中驱动框架如下:
1)写file_operations结构体的成员函数: .open()、.read()、.write()
2)在入口函数里通过register_chrdev()创建驱动名,生成主设备号,赋入file_operations结构体
3)在出口函数里通过unregister_chrdev() 卸载驱动
一 输入子系统简介
linux的input子系统用来管理所有的输入类设备,对所有的输入类设备进行抽象,以便更简单的编写输入类设备的驱动程序。
同样的输入子系统也需要输入驱动的框架,好来辨认应用程序要打开的是哪个输入驱动
比如: 鼠标、键盘、游戏手柄等等这些都属于输入设备;这些输入设备的驱动都是通过输入子系统来实现的(当然,这些设备也依赖于usb子系统)
这些输入设备都各有不同,那么输入子系统也就只能实现他们的共性,差异性则由设备驱动来实现。差异性又体现在哪里?
最直观的就表现在这些设备功能上的不同了。对于我们写驱动的人来说在设备驱动中就只要使用输入子系统提供的工具(也就是函数)来完成这些“差异”就行了,其他的则是输入子系统的工作。这个思想不仅存在于输入子系统,其他子系统也是一样(比如:usb子系统、video子系统等)
我们学习最重要的就是学会输入子系统的代码框架!
二 输入子系统框架分析
输入子系统由输入子系统核心层( Input Core ),驱动层和事件处理层(Event Handler)三部份组成。
一个输入事件,从具体设备的device driver—> input driver -> Input core -> Event handler -> userspace 到达用户空间传给应用程序。
1.系统核心层(对应图中的input core 层)
主要功能
a.注册主设备号
b.中转站:对于系统调用经过虚拟文件系统进入的open函数进行第一层处理,并通过次设备号选择handler进入第二层open,也就是真正的open所在的file_operation,并返回该file_opration的fd.
c.提供input_register_device跟input_register_handler函数分别用于注册device跟handler
有以下这么两段:
subsys_initcall(input_init); //修饰入口函数
module_exit(input_exit); //修饰出口函数
(1)显然输入子系统是作为一个模块存在,我们先来分析下input_int()入口函数
1 static int __init input_init(void)
2 {
3 int err;
4 err = class_register(&input_class); //(1)注册类,放在/sys/class
5 if (err) {
6 printk(KERN_ERR "input: unable to register input_dev class\n");
7 return err;
8 }
9
10 err = input_proc_init(); //在/proc下面建立相关的文件
11 if (err)
12 goto fail1;
13 input_fops为本驱动的file_operations结构体
14 err = register_chrdev(INPUT_MAJOR, "input", &input_fops); //(2)注册驱动
15 if (err) {
16 printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
17 goto fail2;
18 }
22 return 0;
31
32 }
(1)上面第4行”err = class_register(&input_class);”是在/sys/class 里创建一个
input类, input_class变量如下图:
如下图,我们启动内核,再启动一个input子系统的驱动后,也可以看到创建了个"input"类 :
为什么这里代码只创建类,没有使用class_device_create()函数在类下面创建驱动设备?在下面会详细讲到,这里简单描述:当注册input子系统的驱动后,才会有驱动设备,此时这里的代码是没有驱动的
(2)上面第14行通过register_chrdev创建驱动设备,其中变量INPUT_MAJOR
=13,所以创建了一个主设备为13的"input"设备。然后我们来看看它的操作结构体input_fops,如下图:
只有一个.open函数,比如当我们挂载一个新的input驱动,则内核便会调用该.open函数,接下来分析该.open函数
(2)然后进入input_open_file函数(drivers/input/input.c)
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler = input_table[iminor(inode) >> 5]; // (1)
const struct file_operations *old_fops, *new_fops = NULL;
int err;
if (!handler || !(new_fops = fops_get(handler->fops))) //(2)
return -ENODEV;
if (!new_fops->open) {
fops_put(new_fops);
return -ENODEV;
}
old_fops = file->f_op;
file->f_op = new_fops; //(3)
err = new_fops->open(inode, file); //(4)
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
return err;
}
(1)第3行中,其中iminor
(inode)函数调用了MINOR(inode->i_rdev);读取子设备号,然后将子设备除以32,找到新挂载的input驱动的数组号,然后放在input_handler
驱动处理函数handler中。 handler中又包含file_operations结构体。
(2)第7行中,若handler有值,说明挂载有这个驱动,就将handler结构体里的成员file_operations *
fops赋到新的file_operations *new_fops里面(3)第16行中, 再将新的file_operations *new_fops赋到file-> file_operations
*f_op里, 此时input子系统的file_operations就等于新挂载的input驱动的file_operations结构体,实现一个偷天换日的效果.(4)第18行中,然后调用新挂载的input驱动的*new_fops里面的成员.open函数
(3)上面代码的input_table[]数组在初始时是没有值的,所以我们来看看input_table数组里面的数据又是在哪个函数里被赋值
在input.c函数(drivers/input/input.c)中搜索input_table,找到它在input_register_handler()函数中被赋值,代码如下:
int input_register_handler(struct input_handler *handler)
{
//将驱动处理程序input_handler 注册到input_table[]中
input_table[handler->minor >> 5] = handler;
//将驱动处理程序input_handler 放在input_handler_list链表中,
list_add_tail(&handler->node, &input_handler_list);
}
(4)继续来搜索input_register_handler,看看这个函数被谁来调用
如下图所示,有evdev.c(事件设备),tsdev.c(触摸屏设备),joydev.c(joystick操作杆设备),keyboard.c(键盘设备),mousedev.c(鼠标设备) 这5个内核自带的设备处理函数注册到input子系统中
我们以evdev.c为例,它在evdev_ini()函数中注册:
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
(5)我们来看看这个evdev_handler变量是什么结构体,:
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect, //(4)
.disconnect = evdev_disconnect,
.fops = &evdev_fops, //(1)
.minor = EVDEV_MINOR_BASE, //(2)
.name = "evdev",
.id_table = evdev_ids, //(3)
};
(1) 第5行中.fops:文件操作结构体,其中evdev_fops函数就是自己的写的操作函数,然后赋到.fops中
(2)第6行中 .minor:用来存放次设备号,其中EVDEV_MINOR_BASE=64,
然后调用input_register_handler(&evdev_handler)后,由于EVDEV_MINOR_BASE/32=2,所以存到input_table[2]中
所以当open打开这个input设备,就会进入 input_open_file()函数,执行evdev_handler->
evdev_fops -> .open函数,如下图所示:
(3)第8行中.id_table :
表示能支持哪些输入设备,比如某个驱动设备的input_dev->的id和某个input_handler的id_table相匹配,就会调用.connect连接函数,如下图(4)第3行中.connect:连接函数,将设备input_dev和某个input_handler建立连接,如下图
2.系统event handler层(对应图中的event handler层)
1.我们先来看看上图的input_register_device()函数,如何创建驱动设备的
(1)然后进入input_register_device()函数,代码如下:
int input_register_device(struct input_dev *dev) //*dev:要注册的驱动设备
{
... ...
list_add_tail(&dev->node, &input_dev_list); //(1)放入链表中
... ...
list_for_each_entry(handler, &input_handler_list, node) //(2)
input_attach_handler(dev, handler); //(3)
... ...
}
(1)第4行中,将要注册的input_dev驱动设备放在input_dev_list链表中
(2)第6行中,其中input_handler_list在(3)中讲过,就是存放每个input_handle驱动处理结构体,
然后list_for_each_entry()函数会将每个input_handle从链表中取出,放到handler中
(3)最后会调用input_attach_handler()函数,将每个input_handle的id_table进行判断,若两者支持便进行连接。
(2)然后我们在回过头来看注册input_handler的input_register_handler()函数,如下图所示
所以,不管新添加input_dev还是input_handler,都会进入input_attach_handler()判断两者id是否有支持, 若两者支持便进行连接。
(3)我们来看看input_attach_handler()如何实现匹配两者id的:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
... ...
id = input_match_device(handler->id_table, dev); //匹配两者
if (!id) //若不匹配,return退出
return -ENODEV;
error = handler->connect(handler, dev, id); //调用input_handler ->connect函数建立连接
... ...
}
2.我们还是以evdev.c(事件驱动) 的evdev_handler->connect函数为例来分析是怎样建立连接的,如下图:
(1)evdev_handler的.connect函数是evdev_connect(),代码如下:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
{
//第一步:分配一个input_handle全局结构体(没有r)
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
//第二步:
evdev->handle.dev = dev; //指向参数input_dev驱动设备
evdev->handle.handler = handler; //指向参数 input_handler驱动处理结构体
//第三步
error = input_register_handle(&evdev->handle); //注册这个input_handle结构体
}
最终会进入input_register_handle()函数来注册,代码在下面
(2)input_register_handle()函数如下:
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler; //handler= input_handler驱动处理结构体
list_add_tail(&handle->d_node, &handle->dev->h_list); //(1)
list_add_tail(&handle->h_node, &handler->h_list); // (2)
if (handler->start)
handler->start(handle);
return 0;
}
(1)在第5行中,因为handle->dev指向input_dev驱动设备,所以就是将handle->d_node放入到input_dev驱动设备的h_list链表中,即input_dev驱动设备的h_list链表就指向handle->d_node
(2) 在第6行中, 同样, input_handler驱动处理结构体的h_list也指向了handle->h_node
最终如下图所示:
两者的.h_list都指向了同一个handle结构体,然后通过.h_list来找到handle的成员.dev和handler,便能找到对方,便建立了连接
3.建立了连接后,又如何读取evdev.c(事件驱动) 的evdev_handler->.fops->.read函数?
(1)事件驱动的.read函数是evdev_read()函数,我们来分析下:
static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
... ...
/*判断应用层要读取的数据是否正确*/
if (count < evdev_event_size())
return -EINVAL;
/*在非阻塞操作情况下,若client->head == client->tail|| evdev->exist时(没有数据)||非阻塞,则return返回*/
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
/*若client->head == client->tail|| evdev->exist时(没有数据),等待中断进入睡眠状态 */
retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);
//上传数据
... ...
}
(2)若read函数进入了休眠状态,又是谁来唤醒?
我们搜索这个evdev->wait这个等待队列变量,找到evdev_event函数里唤醒:(具体参见之前讲解中断体系)
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
wake_up_interruptible(&evdev->wait); //有事件触发,便唤醒等待中断
}
其中evdev_event()是evdev.c(事件驱动) 的evdev_handler->.event成员,如下图所示:
当有事件发生了,比如对于按键驱动,当有按键按下时,就会进入.event函数中处理事件。
(3)分析下,是谁调用evdev_event()这个.event事件驱动函数
应该就是之前分析的input_dev那层调用的
我们来看看内核 gpio_keys_isr()函数代码例子就知道了 (driver/input/keyboard/gpio_keys.c)
在设备的中断服务程序里,确定事件是什么,然后调用相应的input_handler的event处理函数
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
/*获取按键值,赋到state里*/
... ...
/*上报事件*/
input_event(input, type, button->code, !!state);
input_sync(input); //同步信号通知,表示事件发送完毕
}
显然就是通过input_event()来调用.event事件函数,我们来看看:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
... ...
/* 通过input_dev ->h_list链表找到input_handle驱动处理结构体*/
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open) //如果input_handle之前open 过,那么这个就是我们的驱动处理结构体
handle->handler->event(handle, type, code, value); //调用evdev_event()的.event事件函数
}
若之前驱动input_dev和处理input_handler已经通过input_handler 的.connect函数建立起了连接,那么就调用evdev_event()的.event事件函数,如下图所示:
4.总结
- 注册输入子系统,进入put_init():
1)创建主设备号为13的"input"字符设备
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
- open打开驱动,进入input_open_file():
1)更新设备的file_oprations
file->f_op=fops_get(handler->fops);
2)执行file_oprations->open函数
err = new_fops->open(inode, file);
- 注册input_handler,进入input_register_handler():
1)添加到input_table[]处理数组中
input_table[handler->minor >> 5] = handler;
2)添加到input_handler_list链表中
list_add_tail(&handler->node, &input_handler_list);
3)判断input_dev的id,是否有支持这个驱动的设备
list_for_each_entry(dev, &input_dev_list, node) //遍历查找input_dev_list链表里所有input_dev
input_attach_handler(dev, handler); //判断两者id,若两者支持便进行连接。
- 注册input_dev,进入input_register_device():
1)放在input_dev_list链表中
list_add_tail(&dev->node, &input_dev_list);
2)判断input_handler的id,是否有支持这个设备的驱动
list_for_each_entry(handler, &input_handler_list, node) //遍历查找input_handler_list链表里所有input_handler
input_attach_handler(dev, handler); //判断两者id,若两者支持便进行连接。
- 判断input_handler和input_dev的id,进入input_attach_handler():
1)匹配两者id,
input_match_device(handler->id_table, dev); //匹配input_handler和dev的id,不成功退出函数
2)匹配成功调用input_handler ->connect
handler->connect(handler, dev, id); //建立连接
- 建立input_handler和input_dev的连接,进入input_handler->connect():
1)创建全局结构体,通过input_handle结构体连接双方
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //创建两者连接的input_handle全局结构体
list_add_tail(&handle->d_node, &handle->dev->h_list); //连接input_dev->h_list
list_add_tail(&handle->h_node, &handler->h_list); // 连接input_handle->h_list
- 有事件发生时,比如按键中断,在中断函数中需要进入input_event()上报事件:
1)找到驱动处理结构体,然后执行input_handler->event()
list_for_each_entry(handle, &dev->h_list, d_node) // 通过input_dev ->h_list链表找到input_handle驱动处理结构体
if (handle->open) //如果input_handle之前open 过,那么这个就是我们的驱动处理结构体(有可能一个驱动设备在不同情况下有不同的驱动处理方式)
handle->handler->event(handle, type, code, value); //调用evdev_event()的.event事件函数