002.13.01输入子系统概念介绍
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了002.13.01输入子系统概念介绍相关的知识,希望对你有一定的参考价值。
可以将输入子系统看做由三大部分组成,体现了一种分离分层思想。分别为:- 核心层
- 事件处理层
- 设备驱动层
核心层:这部分主要由input.c来实现,它为事件处理层和设备驱动层提供统一接口,这里我们先列出几个重要的函数。
static int __init input_init(void)
int input_register_device(struct input_dev *dev)
int input_register_handler(struct input_handler *handler)
int input_register_handle(struct input_handle *handle)
这几个函数几乎就实现了整个输入子系统的运作过程。
input.c
subsys_initcall(input_init);/*是整个输入子系统的入口函数,它是被编译进内核的,也就是说一开机就会被执行的*/
input.c
static int __init input_init(void)
{
err = class_register(&input_class); /*注册类,放在/sys/class*/
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);/*注册一个设备名为input且主设备号为INPUT_MAJOR(13)的字符设备,
可以通过命令 cat /proc/devices 查看*/
}
接下来把重点放在input_fops
input.c
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
不可能只有.open函数的,肯定在input_open_file函数里实现了其它应用函数。
input.c
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler = input_table[iminor(inode) >> 5];/*根据次设备号从input_table取出handler*/
new_fops = fops_get(handler->fops),file->f_op = new_fops;/*从handler结构中获取一个fops结构并将替换原来的fops。*/
err = new_fops->open(inode, file); /*然后调用新的file_operations结构体里面的open函数*/
... ...
}
也就是说应用程序以后执行读写等操作(比如读按键)是调用新的fops里面相应的函数,而新的fops是在数组input_table[]里面,根据所打开的文件(或者说设备)的次设备号找出来的其中一个handler,在这个handler里面就有个新的fops,而input.c只起到一个中转作用。
那到底新的fops是什么时候注册进input_table[]数组里面的?
input_open_file函数一开始就从input_table获得一项handler,接下来我们会一步步追踪它的由来。
搜索下,找到tatic struct input_handler *input_table[8]发现它是一个静态变量
input.c
static struct input_handler *input_table[8];
一步步找下来最终发现它在int input_register_handler函数中定义,但input_register_handler又是由谁调用呢?
input.c
int input_register_handler(struct input_handler *handler)
{
input_table[handler->minor >> 5] = handler;/*input_table[]被赋值*/
}
同样搜索下,找到了几个c文件调用了它,分别有evdev.c、joydev.c、mousedev.c等等。
分析到这里,就已经体现了一开始说的分离分层思想的分离思想,evdev.c、游戏手柄joydev.c、鼠标mousedev.c这些就是事件处理层,这部分的代码稳定,一般我们不用修改。
接着上面我们说的input_table在input_register_handler中定义的,我们现在要分析的就是input_register_handler函数做了哪些具体的事情。
以evdev.c为例,看看evdev_handler里面都有哪些内容:
evdev.c
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler); /*调用input.c--->input_register_handler,注册evdev_handler*/
}
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops, /*.fops:文件操作结构体,其中evdev_fops函数就是自己的写的操作函数,然后赋到.fops中*/
.minor = EVDEV_MINOR_BASE, /*.minor:用来存放次设备号,EVDEV_MINOR_BASE=64,然后调用
input_register_handler(&evdev_handler)后,
由于EVDEV_MINOR_BASE/32=2,所以存到input_table[2]中*/
.name = "evdev",
.id_table = evdev_ids, /*一个存放该handler所支持的设备id的表(其实内部存放的是EV_xxx事件,用于判断device是否支持该事件)*/
};
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
}
我们看input.c--->input_register_handler,是如何注册handler的
这个函数主要做了三件事情:
- 1、将handler放进input_table表中,而不同的处理层该input_handler的结构不同,比如evdev.c与joydev.c的input_handler的结构就可能不一样
- 2、将handler放进input_handler_list链表中
- 3、从input_dev_list链表中找出每个input_dev并执行input_attach_handler(dev, handler);
input.c
int input_register_handler(struct input_handler *handler)
{
input_table[handler->minor >> 5]; /*① 这里就是将handler注册进input_table[]数组,而不同的处理层该input_handler的结构不同*/
list_add_tail(&handler->node, &input_handler_list); /*②将handler放进input_handler_list链表中*/
list_for_each_entry(dev, &input_dev_list, node) /*③从input_dev_list链表中找出每个input_dev并执行input_attach_handler*/
input_attach_handler(dev, handler);
}
继续跟踪 input_attach_handler
input.c
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
id = input_match_device(handler->id_table, dev); /*①对两者的id进行匹配,同样若有不同则匹配不成功。*/
error = handler->connect(handler, dev, id); /*②当两者匹配成功后执行handler->connect(handler, dev, id)*/
}
内核中input_device_id的数据结构如下:
路径:includelinuxMod_devicetable.h
struct input_device_id {
kernel_ulong_t flags; /* 这个flag 表示我们的这个 input_device_id 是用来匹配下面的4个情况的哪一项*/
/* flag == 1表示匹配总线 2表示匹配供应商 4表示匹配产品 8表示匹配版本*/
__u16 bustype; /*对应input_id的四个数据域*/
__u16 vendor;
__u16 product;
__u16 version;
/*存储支持事件的位图,与input_dev中的同名数据成员功能一致*/
/*(其实内部存放的是EV_xxx事件,用于判断device是否支持该事件)*/
kernel_ulong_t evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1];
kernel_ulong_t keybit[INPUT_DEVICE_ID_KEY_MAX / BITS_PER_LONG + 1];
kernel_ulong_t relbit[INPUT_DEVICE_ID_REL_MAX / BITS_PER_LONG + 1];
kernel_ulong_t absbit[INPUT_DEVICE_ID_ABS_MAX / BITS_PER_LONG + 1];
kernel_ulong_t mscbit[INPUT_DEVICE_ID_MSC_MAX / BITS_PER_LONG + 1];
kernel_ulong_t ledbit[INPUT_DEVICE_ID_LED_MAX / BITS_PER_LONG + 1];
kernel_ulong_t sndbit[INPUT_DEVICE_ID_SND_MAX / BITS_PER_LONG + 1];
kernel_ulong_t ffbit[INPUT_DEVICE_ID_FF_MAX / BITS_PER_LONG + 1];
kernel_ulong_t swbit[INPUT_DEVICE_ID_SW_MAX / BITS_PER_LONG + 1];
kernel_ulong_t driver_info;
};
而evdev_handler的.id_table = evdev_ids, 如下:
evdev.c
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */ /*所有设备都支持,所以就不会存在id不匹配的情况*/
{ }, /* Terminating zero entry */ /*终止0头目*/
};
匹配过程如下:
input.c
static const struct input_device_id *input_match_device(const struct input_device_id *id,
struct input_dev *dev)
{
int i;
for (; id->flags || id->driver_info; id++) {
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(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);
return id;
}
return NULL;
}
两者匹配成功后执行 evdev_connect:
evdev.c
static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
{
evdev->handle.dev = dev;
evdev->handle.handler = handler; /*这两句,handle连接了dev、handle*/
class_device_create(&input_class, &dev->cdev, devt, dev->cdev.dev, evdev->name); /*创建了一个设备节点*/
input_register_handle(&evdev->handle); /*注册一个handle,注意不是handler*/
}
nput_handle结构体的原型如下,只有dev、handler、d_node、h_node几个成员。
input.h
struct input_handle {
void *private;
int open;
const char *name;
struct input_dev *dev;
struct input_handler *handler;
struct list_head d_node;/*input_handle通过d_node连接到了input_dev上的h_list链表上*/
struct list_head h_node;/*input_handle通过h_node连接到了input_handler的h_list链表上*/
};
input_register_handle
input.c
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;/*将handle里保存的handler拿出来*/
/*
下面两句,这样就可以将dev和handler建立起连接了
*/
list_add_tail(&handle->d_node, &handle->dev->h_list);/*把input_handle的d_node加入input_dev的h_list*/
list_add_tail(&handle->h_node, &handler->h_list); /*把input_handle的h_node加入input_handler的h_list*/
}
由以上可以得出handler、handle、input_dev的关系如下:
建立起连接以后:
①就可以通过input_handler里面的h_list找到input_handle,然后通过input_handle里面的dev找到input_dev。
②也可以通过inpu_dev的h_list找到input_handle,然后通过input_handle的handler找到input_handler
至此我们对事件处理层的分析基本完成了,我们接下来再分析设备驱动层
那么input_dev在哪里注册,input_dev结构体又有哪些内容?
先看input_dev结构体,有哪些内容:
input.h
struct input_dev
{
void *private;
const char *name; /*这4个不用管,不重要。*/
const char *phys;
const char *uniq;
struct input_id id;
unsigned long evbit[NBITS(EV_MAX)]; /*evbit: 表示能产生哪类事件看EV_MAX这个宏*/
unsigned long keybit[NBITS(KEY_MAX)]; /*表示能产生哪些按键*/
unsigned long relbit[NBITS(REL_MAX)]; /*表示能产生哪些相对位移事件,x,y,滚轮(比如鼠标)*/
unsigned long absbit[NBITS(ABS_MAX)]; /*表示能产生哪些绝对位移事件,x,y(比如触摸屏)*/
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)];
... ...
struct list_head h_list;
struct list_head node;
}
再看看input_dev注册函数input_register_device():
input.c
int input_register_device(struct input_dev *dev)
{
list_add_tail(&dev->node, &input_dev_list);
list_for_each_entry(handler, &input_handler_list, node) /*主要的事情还是这两件,这和上面讲的事件处理层很对称。*/
input_attach_handler(dev, handler);
}
Linux内核已经自带有一些事件处理器,可以支持大部分输入设备,比如evdev.c、mousedev.c、joydev.c等,这些.c文件所在的层为handler层。linux内核自带的input_handler中,evdev_handler是最常见的,因为它可以匹配任何的input_dev设备。一个事件处理器用struct input_handler结构体来表示,在evdev.c文件中的定义如下。
static struct input_handler evdev_handler。我们在evdev.c里面搜索不到input_register_device函数,是因为evdev.c是用作事件处理,并不支持输入设备。
搜索:input_register_device,发现存在于下面这些文件中,里面是各种鼠标、按键、触摸屏等等驱动,在这些驱动里面构造:
一般设备驱动层都是由用户根据设备的硬件情况自己编写代码的,最终要调用input_register_device将设备注册进内核。
我们以driversinputkeyboardGpio_keys.c 作为示例,看看是如何注册input_dev的:
Gpio_keys.c /*注意linux内核的Gpio_keys.c只是一个参考范例,没实际用途。*/
static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
struct input_dev *input; /*定义一个input_dev结构体指针input*/
/*①分配一个input_dev结构体input*/
input = input_allocate_device();
/*②设置(其实这里设置的就是dev的id,跟handler里面的.id_table里各项内容对比要对应才能建立起连接)*/
/*1.能产生哪类事件?*/
input->evbit[0] = BIT(EV_KEY); /*设置nput->evbit[0]这个数组中的0位为EV_KEY,这个驱动就能产生按键类事件*/
//set_bit(EV_KEY, input->evbit); /*这样写会更好,功能是一样的。*/
//set_bit(EV_REP, input->evbit); /*使之能产生重复类事件*/
/*2 能产生这类操作里的哪些事件?*/
//set_bit(KEY_L, input->keybit);/*设置这个数组里面的某一位,能够产生L这个键*/
... ...
/* ③注册input_dev结构体buttons_dev */
error = input_register_device(input);
/*
· 注册之后,它就会把buttons_dev这个设备(结构体)放到input_dev_list链表里面去
· 把input_handler_list链表里面的每一个input_handler(结构体),一个个拿出来跟buttons_dev这个input_dev结构体的id作比较
如果input_handler的id_table表示说能匹配这个buttons_dev的话,就会调用input_handler的connect函数,connect函数就会建立
一个input_handle。
· input_handle结构体有: handler、dev、d_node、h_node成员
· input_dev的h_list链表指向handle->d_node;同样地,input_handler的h_list也指向了handle->h_node,这样三者之间就建立
起了连接,handle在input_dev和input_handler之前起到了桥梁般的连接作用。
· 简单点可以理解为:input_handler里面的h_list指向input_handle结构体,inpu_dev里面的h_list同样指向input_handle结构体
建立起连接以后:
就可以通过input_handler里面的h_list找到input_handle,然后通过input_handle里面的dev找到input_dev。
也可以通过inpu_dev的h_list找到input_handle,然后通过input_handle的handler找到input_handler
*/
if (error) {/*判断返回值*/}
}
在linux内核中evdev.c中的evdev_handler的.id_table内容为:支持所有输入设备,所以connect函数就会建立一个input_handle,然后使inputv和evdev_handler建立连接。
至此设备处理层也将完了。
接下来讲讲从应用程序的角度如何使用输入子系统。
其中一个重要的函数为input_event
那如果没有按键按下的话,你读按键,最终会调用evdev.c的evdev_handler->.fops->.read函数,没有数据产生就进入休眠状态。在哪里唤醒呢?看下一部分。
我们分析一下evdev.c事件驱动的.read函数是evdev_read()函数:
evdev.c
static ssize_t evdev_read(struct file *file, char __user * buffer, size_t count, loff_t *ppos)
{
... ...
/*判断应用层要读取的数据是否正确*/
if (count < evdev_event_size()) return -EINVAL;
/*无数据并且是非阻塞方式打开,立刻返回。*/
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
/*否则休眠*/
retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);
... ... //上传数据
}
例如有按键中断了,我们在中断服务函数里通过input_event告知内核有哪个事件发生了,比如(更详细搜索源码:buttons.c):
buttons.c
xxx 中断处理函数
{
/*参考以前的poll代码,一般进入中断会先读出键值,然后再唤醒。唤醒之后应用程序就马上可以通过read函数读取键值*/
/* buttons_dev输入设备,EV_KEY按钮类事件,pindesc->key_val哪个引脚 ?,0是松开(看原理图判断)*/
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0); /*告知内核有哪个事件发生了,同时会唤醒,往下看就知道*/
input_sync(buttons_dev); /*上报完事件之后,还需要上报一个同步事件,表示这个事件已经上报完了*/
}
input_event 这个通知函数最终会调用handler的event处理函数并唤醒等待事件。
input.c
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
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); /*调用.event的evdev_event()事件函数*/
}
evdev_event函数如下:
evdev.c
/*
evdev_event为evdev_handler->.event函数,当有事件发生了,有按键按下时,就会进入.event函数中处理事件
*/
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
... ...
wake_up_interruptible(&evdev->wait);/*有事件触发,便唤醒等待中断,和之前写的驱动程序一样*/
}
以上就是整个输入子系统大概的框架。
最后,如何写一个符合input子系统框架的驱动程序?
- 1、分配一个input_dev结构体
- 2、设置
- 3、注册
- 4、硬件相关的代码,比如在中断服务程序里上报事件。
具体代码,看下一节。
以上是关于002.13.01输入子系统概念介绍的主要内容,如果未能解决你的问题,请参考以下文章