linux内核源代码输入子系统分析(linux源码分析)
文章目录linux内核代码分析代码跟读方式分层分析分析:evdev.c中evdev_connect() ---属于input handler层应用程序中调用输入子系统的代码,数据是如何传递给用户层的linux内核代码分析代码跟读方式带着问题去读做好笔记和画图驱动联系应用,应用是调用的,驱动是实现的分层分析Input handler层:/driver/input/evdev.c...
文章目录
linux内核代码分析
代码跟读方式
- 带着问题去读
- 做好笔记和画图
- 驱动联系应用,应用是调用的,驱动是实现的
分层分析
- Input handler层:/driver/input/evdev.c
module_init(evdev_init);
module_exit(evdev_exit);
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 int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
//将当前的handler加入到一个input_handler_list
list_add_tail(&handler->node, &input_handler_list);
//遍历链表input_dev_list
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
//将当前的handler和input dev 进行匹配,event handler能够匹配所有的input dev
id = input_match_device(handler, dev);
//匹配成功,之后要调用handler中connect方法
//实际就是event handler
error = handler->connect(handler, dev, id);
//将当前的handler加入到/proc/bus/input/handlers文件中
input_wakeup_procfs_readers();
总结:
(1)注册了evdev_handler
(2)遍历input dev list,并行匹配,恒匹配成功,自动会调用handler中connect方法------evdev_connect
- Input 核心层:/driver/input/input.c
subsys_initcall(input_init); //类似于module_init();不过等级较高
module_exit(input_exit);
//注册类,类似于class_create();
err = class_register(&input_class);
//在/proc目录下创建bus/input/devices handlers
err = input_proc_init();
//申请设备号
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
总结:
(1)注册了主设备号
(2)注册了input class
- Simple_input.c
input_register_device(inputdev);
//将input dev加入到链表 input_dev_list
list_add_tail(&dev->node, &input_dev_list);
//遍历input_handler_list链表进行匹配
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
//匹配成功,之后要调用handler中connect方法
//实际就是event handler
error = handler->connect(handler, dev, id);
分层图
分析:evdev.c中evdev_connect() —属于input handler层
evdev_connect();
|
//找到一个没有被使用的次设备号,从64开始,65,66
minior = input_get_new_minior(EVDEV_MINOR_BASE,EVDEV_MINORS,true);
//实例化一个evdev对象
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
//初始化evdev对象
INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
//等待队列是完成阻塞
init_waitqueue_head(&evdev->wait);
evdev->exist = true;
dev_no = minor;
dev_no -= EVDEV_MINOR_BASE; //减去了64
//创建设备文件 /dev/event0 1 2
dev_set_name(&evdev->dev,"event%d",dev_no);
evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);//12 64
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
//以上代码和device_create是一样的
//利用handle记录input device和input handler
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
//你中有我,我中有你
evdev->handle.private = evdev;
//将儿子关联到父亲(input handler)和母亲(input dev)
error = 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);
//初始化了cdev,完成了fops
cdev_init(&evdev->cdev,&evdev_fops);
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev,evdev->dev.devt,1);
device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt,...)
|
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
|
dev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
device_initialize(dev);
dev->devt = devt;
dev->class = class;
dev->parent = parent;
dev->release = device_create_release;
dev_set_drvdata(dev,drvdata);
kobject_set_name_vargs(&dev->kobj,fmt,args); //设置名字
device_add(dev); //注册到系统
**总结:
- 分配edev,并初始化,记录input device和handler的关系
- 创建设备节点 /dev/event0
- 注册cdev,并实现fops**
- 关系:
多个input device可以对应一个event handler
一个input device对应一个 evdev,对应于一个设备节点/dev/event1,1,2 - 所有的设备节点调用open,read,write文件IO的时候,实际是调用cdev中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,
};
input内核层input_dev 和 input_handler 关系图:
应用程序中调用输入子系统的代码,数据是如何传递给用户层的
应用层与驱动层调用图
如下描述:
总结:
一个设备节点对应一个 cdev,一个 cdev 对应一个 evdev ,evdev中记录 input_dev 和 input_handler 的关系,同时还有一个缓冲队列。evdev作用就是把所用东西记录下来。
evdev_open方法
evdev_open 方法做的事情
//通过儿子,找到老母 input device
bufsize = evdev_compute_buffer_size(evdev->handle.dev);
//分配一个client对象,描述一个缓冲队列,存放发就是 input_event
/*sizeof(struct evdev_client)
+bufsize * sizeof(struct input_event)
包含了很多input_event大小
*/
client = kzalloc(sizeof(struct evdev_client) +
bufsize * sizeof(struct input_event),
GFP_KERNEL);
if (!client) {
error = -ENOMEM;
goto err_put_evdev;
}
//client 中有一个缓冲区
client->bufsize = bufsize;
spin_lock_init(&client->buffer_lock);
//在client中记录evdev
client->evdev = evdev;
//将client加入到evdev中的一个小链表
evdev_attach_client(evdev, client);
|
list_add_tail_rcu(&client->node,&evdev->client_list);
error = evdev_open_device(evdev);
if (error)
goto err_free_client;
//将client记录到file,方便其他的接口使用
file->private_data = client;
nonseekable_open(inode, file);
- 为输入设备分配一个缓冲区 evdev_client ,用户存放input device层上报的数据
- evdev_client 记录到 evdev 中
- evdev_client 记录到 file 中
应用程序中read,如何获取到数据
read
evdev_read方法
//获取到open中分配的缓冲区对象
struct evdev_client *client = file->private_data;
//获取到 evdev
struct evdev *evdev = client->evdev;
//表示一个数据包,要给用户
struct input_event event;
for(;;)
{
//实现非阻塞
if (client->head == client->tail &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN;
while (retval + input_event_size() <= count &&
//1.从缓冲区获取数据,存放在input_event数据包
evdev_fetch_next_event(client, &event)) {
|
*event = client->buffer[client->tail++];
// 2.将数据上报给用户
if (input_event_to_user(buffer + retval, &event))
|
copy_to_user(buffer,event,sizeof)
return -EFAULT;
// 3.统计上报多少数据
retval += input_event_size();
}
//如果当前是阻塞模式
if(!(file->f_flags & O_NONBLOCK))
{
error = wait_event_interruptible(evdev->wait,
client->packet_head != client->tail) ||
!evdev->exist || client->revoked);
}
}
总结:
- 如果没有数据,就会休眠等待
- 如果有数据,就从缓冲区client->buffer[client->tail++]拿数据,通过copy_to_user上报给用户
疑问:
数据到底是如何存放在缓冲区的
等待队列是谁唤醒的
input_report_key(inputdev,pdesc->key_code,0);
input_sync(inputdev); //上报数据结束
input_report_key(inputdev,pdesc->key_code,0);
input_sync(inputdev); //上报数据结束
|//input_event(dev,EV_KEY,code,!!value);
input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value);//上报数据
|
input_handle_event(dev, type, code, value);
|
//如果将数据较交给input handler去处理
if(disposition & INPUT_PASS_TO_HANDLERS)
{
struct input_value *v;
//将input device获取到数据暂存一下input value
v = &dev->vals[dev->num_vals++];
v->type = type;
v->code = code;
v->value = value;
input_pass_values(dev,dev->vals,dev->num_vals)
|
//从input device中获取到input handle
else
{
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (!handle->open)
count=input_to_handler(handle,vals,count);
|
//如果handler儿子找到handler父亲
struct input_handler *handler = handler->handler;
//如果有events函数指针,那就调用
if(hadnler->events)
handler->events(handle,vals,count);
else if(handler->event) //否则调用event();
else if(handler->event)
for(v = vals; v != end; v++)
handler->event(handle,v->type,v->code,v->value);
}
总结:
- 如果将数据上报,最终是调用handler中的events()或者event()
实际是 evdev.c
.event = evdev_event,
.events = evdev_events,
|
//拿到evdev ,肯定要拿到缓冲区
struct evdev *evdev = handle->private;
struct evdev_client *client;
//获取到缓冲区evdev_client
else
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_event(client,vals,count,time_mono,time_real);
|
//通过client获取到evdev
struct evdev *evdev = client->evdev;
const struct input_value *v;
struct input_event event;
//获取到当前的时间戳
event.time = ktime_to_timeval(client->clkid == CLOCK_MONOTONIC ? mono : real);
for(v = vals; v != vals + count; v++)
{
//将input device 上报的数据获取到,并且封装成input event对象
event.type = v->type;
event.code = v->code;
event.value = v->valuel;
//将input event数据存放到缓冲区中
__pass_event(client,&event);
client->buffer[client->head++] = *event;
client->head &= client->bufsize - 1;
//如果调用 input_sync()
if(v->type == EV_SYN && v->code == SYN_REPORT)
wakeup = true;
}
//唤醒等待队列
if(wakeup)
wake_up_interruptible(&evdev->wait);
所以由上可以分析出以上的疑问的答案:
数据到底是如何存放在缓冲区的?
答:数据是 input_report_key(inputdev,pdesc->key_code,0); 给的
等待队列是谁唤醒的?
答:等待队列是 input_sync(inputdev); 唤醒的
总结:
- input_report_key(inputdev,pdesc->key_code,0)
*将输入设备产生的数据交给input handler,input handler 调用event();将数据存放在缓冲区 client->buffer[client->head++] = event; - input_sync(inputdev);
上报数据结束,唤醒等待队列,表示输入设备上报数据完毕
整体逻辑图如下
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)