1.框架,文件路径和内核选择:
event handler层:为应用层提供编程接口,接受设备驱动层的上报的数据,属于应用空间和设备驱动之间的桥梁。
input core层:负责管理输入设备,为输入设备驱动层提供各种接口,比如注册注销,上报数据方式等等。
driver层:主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。
基本逻辑过程:
input设备产生一个input event,但是只是产生并没有处理,而是交给handler进行处理。
各个源码路径:
event handler层:
drivers/input/input.c :用于管理输入设备,提供接口代码
input-compat.c:用于与应用层进行数据交互的代码。
input-mt.c:force-feedbac力感反馈。
以上多个文件共同编译成input-core
输入设备驱动层:
drivers/input/keyboard/
drivers/input/touchscreen/
drivers/input/joystick/
内核驱动选择:
2.各个层分析:
Evdev.c (drivers\input)
module_init(evdev_init);
module_exit(evdev_exit);
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
input.c层(核心层)
input_register_handler(struct input_handler *handler)
|
// 初始化handler链表
INIT_LIST_HEAD(&handler->h_list);
//现将handler保存到input_table数组中,minor一般都是32的倍数
// 注意,这个在input.c核心代码中有用到
input_table[handler->minor >> 5] = handler;
//并将handler->node加入到input_handler_list链表中
list_add_tail(&handler->node, &input_handler_list);
// 通过handler找到对应的输入设备
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);-----------------------------------
|
//更新//proc中的数据 |
input_wakeup_procfs_readers(); |
|
|
input_attach_handler(dev, handler); <-------------------------------------------------
|
//比对设备id列表
list_for_each_entry(dev, &input_dev_list, node)
id = input_match_device(handler, dev);------------------------
// 如果比对成功,那么执行handler的connect函数 |
handler->connect(handler, dev, id); |
|
//比对方法 |
static const struct input_device_id evdev_ids[] = { |
{ .driver_info = 1 }, /* Matches all devices */ |
{ }, /* Terminating zero entry */ |
}; |
|
input_match_device(handler, dev);<-------------------------------------------|
| //id表示的是hander中的id_table,那这里面一般有什么呢
// 在evdev_ids中兼容所有的设备
for (id = handler->id_table; id->flags || id->driver_info; id++)
/*以下有几种比对方式:
1, 比较id->flags: 前提就是设置了flags,如果没有设置,就不比较
总线INPUT_DEVICE_ID_MATCH_BUS,id->bustype != dev->id.bustype
厂商:INPUT_DEVICE_ID_MATCH_VENDOR,id->vendor != dev->id.vendor
产品id:INPUT_DEVICE_ID_MATCH_PRODUCT,id->product != dev->id.product
版本:INPUT_DEVICE_ID_MATCH_VERSION,id->version != dev->id.version
2,比较设置产生的事件类型:比如有evbit,keybit,absbit
//就是将dev->id和handler->id中evbit/keybit/absbit数组进行位与预算
MATCH_BIT(evbit, EV_MAX);
3,如果hander有match函数,那么就执行match函数,以下这种写法很神奇
if (!handler->match || handler->match(handler, dev))
return id; // 代码能执行到这里就表示找到了
*/
总结:1,将构建的handler加入到input_handler_list中
2,并匹配input_dev_list中的device,device的类型为struct input_dev
3, 匹配的依据是各自对象中的id结构体,匹配成功后会执行handler中的connect函数
4,对于input设备对象的驱动代码中,需要设置evbit/keybit/absbit等数组中的位,用于进行比对
------------------------------------------------------------------------------------------------------------
hander对象中的connect函数:
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
//参数1,handler对象本身 2,匹配成功后的input设备对象 3,id列表
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)
|
struct evdev *evdev;
// 在evdev_table数组找到一个没有使用的位置
for (minor = 0; minor < EVDEV_MINORS; minor++)
if (!evdev_table[minor]);
//分配一个evdev 对象
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
// 初始化列表,用于记录????
INIT_LIST_HEAD(&evdev->client_list);
//初始化等待队列
init_waitqueue_head(&evdev->wait);
//设置evdev的名字,用于用户空间的设备节点
dev_set_name(&evdev->dev, "event%d", minor);
evdev->exist = true;
evdev->minor = minor;
// 此时出现了struct input_handle
// struct input_handle会记录input设备是哪个
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
//同时handle也会记录handler是哪个
evdev->handle.handler = handler;
evdev->handle.private = evdev;
//设置input设备的设备号,次设备号从64开始
evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
// 注册handle,实际就是将input设备对象加入到handle中的handle->d_node链表中
// 将handler加入到handle中的handle->h_node链表中
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);
// 如果handler中有start,那么就执行start
if (handler->start)
handler->start(handle);
// 将evdev存放到evdev_table数组中,下标为次设备号
evdev_install_chrdev(evdev);
//注册字符设备,创建相应的设备节点
device_add(&evdev->dev);
总结:
1, struct evdev是用于与应用程序进行交互的媒介,用于创建字符设备
2, struct evdev是在handler和input设备匹配成功后产生的
3, struct evdev中包含了struct input_handle,该handle中记录了handler和input设备
4, 每一个input设备对应一个struct evdev,一个handler可以对应多个struct evdev和input设备
5, evdev中的input_handle两个链表:handle->d_node记录对应的input设备对象的链表
handle->d_node记录对应的handler对象
-----------------------------------------------------------------------------------
input核心层代码分析:
Input.c (drivers\input)
subsys_initcall(input_init);
module_exit(input_exit);
struct class input_class = {
.name = "input",
.devnode = input_devnode,
};
input_init(void)
|
// 创建/sys/class/input
class_register(&input_class);
// 创建/proc/bus/input/: devices handlers,你可以理解成是总线
err = input_proc_init();
//注册字符设备
register_chrdev(INPUT_MAJOR, "input", &input_fops);-------------
|
|
|
|
static const struct file_operations input_fops = {<----------------------
.owner = THIS_MODULE,
.open = input_open_file,----------------------------------------
.llseek = noop_llseek, |
|
}; |
|
应用open() |
------------------------------------------------------------------------|
sys_open |
------------------------------------------------------------------------|
|
static int input_open_file(struct inode *inode, struct file *file)<-----|
|
struct input_handler *handler;
//根据设备此设备号召到对应的input_table
// 该数组在input_register_handler函数中初始化了
handler = input_table[iminor(inode) >> 5];
if (handler)//如果有当前次设备对应的handler,那么就获取hander对应的fops
new_fops = fops_get(handler->fops);
// 保存用户空间的f_op
old_fops = file->f_op;
// 将handler的f_op赋值给用户空间的f_op
file->f_op = new_fops;
// 执行handler的open函数
err = new_fops->open(inode, file); -----------------------------
| |
此时要切换到evdev.c中代码中驱动 | |
static struct input_handler evdev_handler = { | |
.event = evdev_event, | |
.connect = evdev_connect, | |
.disconnect = evdev_disconnect, | |
.fops = &evdev_fops, <------------------------------ |
.minor = EVDEV_MINOR_BASE, |
.name = "evdev", |
.id_table = evdev_ids, |
}; |
|
|
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,
};
static int evdev_open(struct inode *inode, struct file *file)
|
int i = iminor(inode) - EVDEV_MINOR_BASE;
// 在evdev_table数组中根据次设备号找到对应的struct evdev
// 该对象是在evdev_connect()中实现的
evdev = evdev_table[i];
bufsize = evdev_compute_buffer_size(evdev->handle.dev);
//产生一个struct evdev_client对象,该对象用于承载用户空间和内核空间的缓冲区
//里面包含一个struct input_event buffer[];
client = kzalloc(sizeof(struct evdev_client) +
bufsize * sizeof(struct input_event),
GFP_KERNEL);
client->bufsize = bufsize;
client->evdev = evdev;
// 将evdev_client放入到evdev->client_list链表中
evdev_attach_client(evdev, client);
error = evdev_open_device(evdev);
|
//好像没做什么事情,主要是判断input设备对象是否有open方法,有就打开
//并且对应handle的使用进行计数
input_open_device(&evdev->handle);
//将evdev_client记录到应用空间的file中的私有数据中
file->private_data = client;
总结:1,当应用空间通过open函数时, 在evdev层会产生一个evdev_client对象
用户open-->input_handler-->evdev(handle)<----input_dev
|
|
evdev_client
==============================================================================================
应用read()
---------------------------------------------------------------------
sys_read
-------------------------------------------------------------------
input.c file->f_op = new_fops;
evdev.c: evdev_handler->evdev_read
|
|
static ssize_t evdev_read(struct file *file, char __user *buffer,size_t count, loff_t *ppos)
|
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
// 等待数据
wait_event_interruptible(evdev->wait,client->packet_head != client->tail || !evdev->exist);
//如果数据到来后,将数据上传给用户
input_event_to_user(buffer + retval, &event)
|
copy_to_user(buffer, event, sizeof(struct input_event)
总结: 1,read中数据的等待需要有等待队列来完成
2,在read中的等待队列是谁唤醒的呢:
接下来我们来看看input设备驱动层中数据提交:
input_report_key(button_dev, KEY_LEFT, 0);
|
input_event(dev, EV_KEY, code, !!value);
|
input_handle_event(dev, type, code, value);
|
input_start_autorepeat(dev, code);
input_pass_event(dev, type, code, value);
|
struct input_handler *handler;
struct input_handle *handle;
// 在input设备对象中的dev->h_list找到handle对象
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
//再找到handler
handler = handle->handler;
//执行handler的event函数,并传递type, code, value
handler->event(handle, type, code, value);------------
|
|
所以此时又切换到evdev.c中的evdev_handler: |
static void evdev_event(struct input_handle *handle, <---------------------------------------|
unsigned int type, unsigned int code, int value)
|
//获取通过handle中找到evdev
struct evdev *evdev = handle->private;
//封装input_event
event.type = type;
event.code = code;
event.value = value;
//通过evdev->client_list找到一个evdev_client对象
list_for_each_entry_rcu(client, &evdev->client_list, node)
// 将input设备层传递过来的数据放入到client对象中
evdev_pass_event(client, &event);
//如果type为EV_SYN的话,那么就将唤醒等待队列
if (type == EV_SYN && code == SYN_REPORT)
wake_up_interruptible(&evdev->wait);
总结:
evdev.c
|
用户open-->input_handler-->evdev(handle)<----input_dev
|
|
file->private_data<----evdev_client
用户read-->input_handler--evdev_client input_dev
| |
|填充evdev_client<----------------------input_report_key
| |
<----input_evnt----------evdev->waitqueue<---wake_up_interruptible<----input_sync
3.应用编程:
a.前提编译一个能用的驱动
fd = open("/dev/event0",O_RDWR);
struct input_event *input;
input = malloc(sizeof(struct input_event));
read(fd,input,sizeof(struct intput_event))
switch(input ->code)
case KEY_UP:
if(input->value)
free(input);
close(fd);
b.驱动编写参考文档:Documention/input/input-programming.txt。