H01_Linux输入子系统(1):内核源码分析
文章目录
内核版本 | Linux4.4 |
---|---|
作者 | huang liang |
Linux内核input子系统分为三层:
- 设备驱动层:包含各类输入设备驱动(如触摸屏、鼠标、键盘等等),获取输入事件并上报,各种输入设备驱动;
- 核心层:核心层根据输入设备种类,分发事件至不同的事件处理器,主要是input.c;
- 事件处理层:通用事件处理器(evdev)、鼠标事件处理器(mousedev)、摇杆事件处理器(joydev),缓存事件并提供接口等待用户获取。
各个关键结构体分析
input_dev:代表底层驱动,用于表示输入设备数据结构,在输入子系统的驱动中都会用到这个结构体。
struct input_dev {
const char *name;//名字
const char *phys;//设备在系统中路径
const char *uniq;//指定唯一的ID号,就像MAC地址一样
struct input_id id; //输入id
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // 表示能产生哪类事件
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];// 表示能产生哪些按键
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];// 表示能产生哪些相对位移事件, x,y,滚轮
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];// 表示能产生哪些绝对位移事件, x,y
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,struct input_keymap_entry *ke);
...
unsigned long key[BITS_TO_LONGS(KEY_CNT)];// 按键对应的键值
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)];
struct device dev;
struct list_head h_list;//内核链表头
struct list_head node; //内核链表节点
...
}
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;
};
input_handle:相当于是把输入设备和input_handler链接起来。
struct input_handle {
void *private;
int open;
const char *name;
struct input_dev *dev;
struct input_handler *handler;
struct list_head d_node;/*用来将hanle链接到input_dev的链表上的handle*/
struct list_head h_node;/*用来将hanle链接到input_handler的链表上的handle*/
};
其实只需要弄懂input_dev和input_handler是如何关联的也就弄清楚了整个Linux子系统的框架结构了。
Linux输入子系统的流程可以归纳为:调用input_register_device注册到input系统。然后调用input_attach_handler,并最终调用handler->connect将input_dev 和connect链接起来。在connect函数中会调用input_register_handler注册,input_register_handler函数会调用input_register_handle,其函数定义如下:
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
struct input_dev *dev = handle->dev;
/*
* Filters go to the head of the list, normal handlers
* to the tail.
*/
if (handler->filter)
list_add_rcu(&handle->d_node, &dev->h_list);
else
list_add_tail_rcu(&handle->d_node, &dev->h_list);
/*
* Since we are supposed to be called from ->connect()
* which is mutually exclusive with ->disconnect()
* we can't be racing with input_unregister_handle()
* and so separate lock is not needed here.
*/
list_add_tail_rcu(&handle->h_node, &handler->h_list);
if (handler->start)
handler->start(handle);
return 0;
}
可以理解这个函数,主要就是将handle放到device’s list 和 handler list ,这样通过dev或者handler 就可以找到 handle ,结合之前的分析,通过handle 也可以找到dev和handler,这样他们就都关联起来了。
输入子系统注册函数关系
针对函数的调用关系可以参考如下:
ret = input_register_device(ts->input_dev);
-->input_attach_handler(dev, handler);
-->id = input_match_device(handler, dev);
error = handler->connect(handler, dev, id);
各个关键函数分析
input_register_device函数
int input_register_device(struct input_dev *dev)
{
__set_bit(EV_SYN, dev->evbit);
__clear_bit(KEY_RESERVED, dev->keybit);
input_cleanse_bitmasks(dev);
packet_size = input_estimate_events_per_packet(dev);
if (dev->hint_events_per_packet < packet_size)
dev->hint_events_per_packet = packet_size;
dev->max_vals = dev->hint_events_per_packet + 2;
dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
if (!dev->vals) {
error = -ENOMEM;
goto err_devres_free;
}
error = device_add(&dev->dev);
list_add_tail(&dev->node, &input_dev_list); // 将输入设备加入内核链表
list_for_each_entry(handler, &input_handler_list, node)/*遍历整个事件处理器链接进行匹配*/
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
if (dev->devres_managed) {
devres_add(dev->dev.parent, devres);
}
return 0;
}
可以看到上面有两个链表:
-
输入设备链表 —— input_dev_list
-
事件处理器链表 —— input_handler_list
而这两个链表是在input.c中初始化的。
static LIST_HEAD(input_dev_list); static LIST_HEAD(input_handler_list);
input_attach_handler函数
input_attach_handler函数的原型申明如下,当调用input_match_device输入设备与事件处理器匹配成功,则调用事件处理器的connect()方法进一步处理,
error = handler->connect(handler, dev, id);如果是一个通用的事件处理器(evdev),最终调用evdev_connect
函数。
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
id = input_match_device(handler, dev);
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
pr_err("failed to attach handler %s to device %s, error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error);
return error;
}
input_match_device函数
input_match_device定义如下:为了代码简洁,我删掉了一些代码,其实都是类似的。
static const struct input_device_id *input_match_device(struct input_handler *handler,struct input_dev *dev)
{
const struct input_device_id *id;
for (id = handler->id_table; id->flags || id->driver_info; id++) {
if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
continue;
if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
continue;
if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))
continue;
if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))
continue;
if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))
continue;
if (!handler->match || handler->match(handler, dev))
return id;
}
return NULL;
}
在上面的代码中,会去遍历整个input_device_id,而在evdev中evdev_ids定义如下:driver_info=1,并且没有match函数,因此会return id。
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
可以看出evdev的id==NULL,因此上面所有的bitmap_subset执行都会不成立,直接运行 if (!handler->match || handler->match(handler, dev))
并且没有math函数,这就是为什么evdev.c 中的 handler 可以适用于任何的 dev。
如果感兴趣的话也可以去分析keyboard.c的匹配。
关于evdev
在上面的函数中,最终会调用handler->connect函数,这个函数从何而来呢,我们来看一下evdev的相关说明。
vdev是一种仅限Linux的通用协议,内核使用该协议将有关输入设备的信息和事件转发给用户空间。包括鼠标和键盘,滑动轴,按键、按钮触摸屏、遥控装置等。每个设备都以/dev/input/event0的形式表示为设备节点, 随着更多的输入设备,尾随数字会增加。拔下设备后设备节点将会被其他设备重新使用,因此不要将设备节点硬编码到脚本中。设备节点也只能由root读取,因此您也需要以root身份运行任何调试工具。evdev是与Linux上的输入设备通信的主要方式。
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,
};
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
connect函数
对于输入子系统无论是注册设备input_register_device
还是注册事件处理器input_register_handler
最终都会调用到input_attach_handler进行匹配,当正确匹配的时候就会调用handler的connect方法,接下来我们就分析evdev.c中evdev_connect到底做了什么。整个函数定义如下:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
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;
/* Normalize device number if it falls into legacy range */
if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
dev_no -= EVDEV_MINOR_BASE;
dev_set_name(&evdev->dev, "event%d", dev_no);
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev;
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
error = input_register_handle(&evdev->handle);
cdev_init(&evdev->cdev, &evdev_fops);
evdev->cdev.kobj.parent = &evdev->dev.kobj;
error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
error = device_add(&evdev->dev);
return 0;
}
上面函数最关键的就是input_register_handle,其中struct evdev中有一个很重要的成员struct input_handle handle,它是连接dev和handler的桥梁,
其他的像cdev_add、device_add就是创建一个设备节点,最后就会生成字符设备节点(/dev/input/eventN),这就是用户空间获取内核输入事件的接口。
如何编写一个input的驱动框架
配置i2c_driver
配置i2c_driver函数,并且调用 ret = i2c_add_driver(&ts_driver)注册,当name匹配的时候,就会运行probe函数。
配置probe函数
获取dts配置信息
gtp_int_gpio = of_get_named_gpio_flags(np, "int-gpio", 0, (enum of_gpio_flags *)&irq_flags);
gtp_rst_gpio = of_get_named_gpio_flags(np, "rst-gpio", 0, &rst_flags);
devm_gpio_request_one申请rst和int管脚信息,并且对int管脚注册中断管脚。
申请中断
申请中断有两种方式,一种是devm_request_threaded_irq
调用devm_request_threaded_irq注册中断,当调用之后就会像系统注册一个中断,有中断来就会响应中断函数。
另外一种方式就是采用定时器查询方式。
hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
ts->timer.function = goodix_ts_timer_handler;
hrtimer_start(&ts->timer, ktime_set(1, 0), HRTIMER_MODE_REL);
注册input device
ret = input_register_device(ts->input_dev);
完善中断函数
一个中断函数如下:
static irqreturn_t goodix_ts_irq_handler(int irq, void *dev_id)
{
struct goodix_ts_data *ts = dev_id;
gtp_irq_disable(ts);
queue_work(goodix_wq, &ts->work);
return IRQ_HANDLED;
}
在这里有一个关于gt9xx在rk3399平台的驱动分析,具体可以参考这里。
rk3399平台gt9xx触摸屏驱动分析