今天看代码的时候看到了输入子系统,就来复习了一下输入子系统的框架,顺便记录一下。
Linux输入子系统其实是Linux内核代码里针对输入设备做了一个统一的驱动管理,采用了分层分离的思想,对上层应用软件提供了统一的标准接口。
输入的设备很多,这里我用tsc2007的驱动作为例子,大概地说一下输入子系统。tsc2007是一个iic接口的触摸屏芯片,IIC相关的驱动这里不做深入。
触摸屏作为一个输入设备,tsc2007自然也少不了使用输入子系统作为框架。
首先看tsc2007的驱动入口:
static struct i2c_driver tsc2007_driver = {
.driver = {
.name = "tsc2007",
.of_match_table = of_match_ptr(tsc2007_of_match),
},
.id_table = tsc2007_idtable,
.probe = tsc2007_probe,
};
驱动采用module_i2c_driver(tsc2007_driver);这个宏直接向内核注册了IIC驱动,这里不去深入IIC驱动的框架,我们只关心输入子系统部分。
假设IIC驱动注册成功,tsc2007_probe()函数会被调用。
static int tsc2007_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
const struct tsc2007_platform_data *pdata = dev_get_platdata(&client->dev);
struct tsc2007 *ts;
struct input_dev *input_dev;
int err;
if (!i2c_check_functionality(client->adapter,I2C_FUNC_SMBUS_READ_WORD_DATA))
return -EIO;
ts = devm_kzalloc(&client->dev, sizeof(struct tsc2007), GFP_KERNEL);
if (!ts)
return -ENOMEM;
if (pdata)
err = tsc2007_probe_pdev(client, ts, pdata, id);
else
err = tsc2007_probe_dt(client, ts);
if (err)
return err;
input_dev = devm_input_allocate_device(&client->dev);
if (!input_dev)
return -ENOMEM;
i2c_set_clientdata(client, ts);
ts->client = client;
ts->irq = client->irq;
ts->input = input_dev;
init_waitqueue_head(&ts->wait);
mutex_init(&ts->mlock);
snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(&client->dev));
input_dev->name = "TSC2007 Touchscreen";
input_dev->phys = ts->phys;
input_dev->id.bustype = BUS_I2C;
input_dev->open = tsc2007_open;
input_dev->close = tsc2007_close;
input_set_drvdata(input_dev, ts);
input_set_capability(input_dev, EV_KEY, BTN_TOUCH);
input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, ts->fuzzx, 0);
input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, ts->fuzzy, 0);
input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, ts->fuzzz, 0);
if (pdata) {
if (pdata->exit_platform_hw) {
err = devm_add_action(&client->dev, tsc2007_call_exit_platform_hw, &client->dev);
if (err) {
dev_err(&client->dev,"Failed to register exit_platform_hw action, %d\n",err);
return err;
}
}
if (pdata->init_platform_hw)
pdata->init_platform_hw();
}
err = devm_request_threaded_irq(&client->dev, ts->irq, tsc2007_hard_irq, tsc2007_soft_irq, IRQF_ONESHOT, client->dev.driver->name, ts);
if (err) {
dev_err(&client->dev, "Failed to request irq %d: %d\n", ts->irq, err);
return err;
}
tsc2007_stop(ts);
/* power down the chip (TSC2007_SETUP does not ACK on I2C) */
err = tsc2007_xfer(ts, PWRDOWN);
if (err < 0) {
dev_err(&client->dev, "Failed to setup chip: %d\n", err);
return err; /* chip does not respond */
}
err = input_register_device(input_dev);
if (err) {
dev_err(&client->dev, "Failed to register input device: %d\n", err);
return err;
}
err = tsc2007_iio_configure(ts);
if (err) {
dev_err(&client->dev, "Failed to register with IIO: %d\n", err);
return err;
}
return 0;
}
很明显,在tsc2007_probe()中,驱动会采用devm_input_allocate_device(&client->dev);申请分配一个input_dev结构体。
input_dev->open = tsc2007_open;
input_dev->close = tsc2007_close;
然后设置了该结构体的open成员和close成员。
input_set_capability(input_dev, EV_KEY, BTN_TOUCH); // 设置支持触摸事件
input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, ts->fuzzx, 0); // 设置绝对位移事件,以及位移的范围
input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, ts->fuzzy, 0);
input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, ts->fuzzz, 0); //设置压力事件,以及压力范围
甚至设置了该结构体的一些属性和参数,这些参数的意思就是用了描述这个设备能产生什么样的事件,能产生什么样的参数
比如说,产生触摸事件,X坐标,Y坐标,压力值等
然后,驱动会调用err = input_register_device(input_dev);方法注册这个结构体,意思就是说,告诉它我这个驱动有这样一个能力做这样的事。
我们继续深入源码, 看看input_register_device这个函数到底做了什么高大上的事情。input_register_device的源码位于/driver/inout/input.c中。
int input_register_device(struct input_dev *dev)
{
struct input_devres *devres = NULL;
struct input_handler *handler;
unsigned int packet_size;
const char *path;
int error;
if (test_bit(EV_ABS, dev->evbit) && !dev->absinfo) {
dev_err(&dev->dev,
"Absolute device without dev->absinfo, refusing to register\n");
return -EINVAL;
}
if (dev->devres_managed) {
devres = devres_alloc(devm_input_device_unregister,
sizeof(*devres), GFP_KERNEL);
if (!devres)
return -ENOMEM;
devres->input = dev;
}
/* Every input device generates EV_SYN/SYN_REPORT events. */
__set_bit(EV_SYN, dev->evbit);
/* KEY_RESERVED is not supposed to be transmitted to userspace. */
__clear_bit(KEY_RESERVED, dev->keybit);
/* Make sure that bitmasks not mentioned in dev->evbit are clean. */
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;
}
/*
* If delay and period are pre-set by the driver, then autorepeating
* is handled by the driver itself and we don't do it in input.c.
*/
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD])
input_enable_softrepeat(dev, 250, 33);
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
error = device_add(&dev->dev);
if (error)
goto err_free_vals;
path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
pr_info("%s as %s\n",
dev->name ? dev->name : "Unspecified device",
path ? path : "N/A");
kfree(path);
error = mutex_lock_interruptible(&input_mutex);
if (error)
goto err_device_del;
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) {
dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n",
__func__, dev_name(&dev->dev));
devres_add(dev->dev.parent, devres);
}
return 0;
err_device_del:
device_del(&dev->dev);
err_free_vals:
kfree(dev->vals);
dev->vals = NULL;
err_devres_free:
devres_free(devres);
return error;
}
首先, 使用test_bit方法验证一下你想要注册的参数是否合法。
然后驱动会使用__set_bit/__clear_bit等方法帮你做一些额外的设置。
最后调用list_add_tail(&dev->node, &input_dev_list);将你要注册的input_dev结构体添加到input_dev_list这个链表中去。
然后调用list_for_each_entry(handler, &input_handler_list, node) ,input_attach_handler(dev, handler);
意思就是说, 从input_handler_list链表中取出每一个handler结构体,分别调用input_attach_handler(dev, handler);
这样,整个注册input_dev的流程就完成了。说白了,注册设备的流程就只做了两件重要的事, 一个是将这个设备对应的input_dev结构体放入内核的input_dev_list链表中,另一个就是,遍历内核中input_handler_list这个链表,对于每一个handler调用一次input_attach_handler。
那么问题是,input_handler_list这个链表是谁设置的呢?
继续看源码。
我们在内核代码中搜索input_handler_list这个链表, 发现只有一个函数会使用list_add_tail(&handler->node, &input_handler_list);这个方式添加这个链表。这个函数恰好就是input.c中的input_register_handler(),源码如下:
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int error;
error = mutex_lock_interruptible(&input_mutex);
if (error)
return error;
INIT_LIST_HEAD(&handler->h_list);
list_add_tail(&handler->node, &input_handler_list);
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
return 0;
}
从源码中可以看出,这个函数注册的原理其实也是将某个input_handler结构体放入到input_handler_list这个链表中,更有趣的是,和之前input_dev结构体注册的时候一样,也会有一个遍历链表的操作,不过这次是反过来。就是在刚刚添加input_dev的链表input_dev_list上,一个一个取出input_dev结构体,然后调用input_attach_handler函数。
为什么都去调用同一个函数呢?input_attach_handler这个函数到底有什么奥秘?继续看源码。
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_dev结构体和input_handler结构体进行了比较,其实本质上就是看看input_handler中的id_table是否能匹配上。
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 (input_match_device_id(dev, id) &&
(!handler->match || handler->match(handler, dev))) {
return id;
}
}
return NULL;
}
如果匹配上了,就返回非NULL的id值。然后直接调用input_handler提供的connect函数。
分析到这里可以看出,无论是注册input_handler还是注册input_dev, 都会遍历一次对方的链表,看看是否有能和自己匹配的"对象",如果能匹配,就会直接调用input_handler提供的connect函数进行所谓的连接。所以,如果要注册input_handler的话,必须要设置这个结构体的connect函数。
那问题来了, 内核中有谁会注册input_handler这个东西呢?
我们直接在内核的源码中搜索input_register_handler这个函数,可以很明显的看出有好几个文件都调用了这个函数注册了相应的input_handler.
比如说,keyboard.c, mac_hid.c, mousedev.c, evdev.c等等。
这些都是内核提供的纯软件上的驱动,代表着某一类的输入设备。其中evdev.c是极具代表性的event字符驱动。刚才说到,id_table可以意味着着个驱动能和什么input_dev匹配,我们看一下evdev.c的id_table就可以很明显的知道它支持什么设备了。
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
它的driver_info=1,从注释中可以看出,它支持所有的输入设备。
也就是说,tsc2007_core.c中注册的input_dev结构体也会被这个驱动所支持,当tsc2007_core.c向内核调用input_register_device注册input_dev的时候,在遍历input_handler_list的时候就会匹配上,然后调用evdev.c中input_handler提供的connect函数进行连接。
那这个连接又是做了什么神奇的操作呢?哈哈,还是那句话,看源码。
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int dev_no;
int error;
minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
if (minor < 0) {
error = minor;
pr_err("failed to reserve new minor: %d\n", error);
return error;
}
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev) {
error = -ENOMEM;
goto err_free_minor;
}
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);
if (error)
goto err_free_evdev;
cdev_init(&evdev->cdev, &evdev_fops);
error = cdev_device_add(&evdev->cdev, &evdev->dev);
if (error)
goto err_cleanup_evdev;
return 0;
err_cleanup_evdev:
evdev_cleanup(evdev);
input_unregister_handle(&evdev->handle);
err_free_evdev:
put_device(&evdev->dev);
err_free_minor:
input_free_minor(minor);
return error;
}
源码中很重要的几个地方:
evdev->handle.dev = input_get_device(dev);
evdev->handle.handler = handler;
error = input_register_handle(&evdev->handle);
cdev_init(&evdev->cdev, &evdev_fops);
error = cdev_device_add(&evdev->cdev, &evdev->dev);
首先,这个函数会分配一个叫做input_handle的东西,注意,这个和上面提到的input_handler不一样,哪里不一样?对,单词不一样,认真看清楚。
然后会设置这个input_handle结构体,分别设置它的dev成员和handler成员,是不是发现了什么?
是的,其实真正起了连接作用的就是这个结构体了,它将之前提到的id_table匹配的两个结构体真正意义上的“连接”起来了,也就是说,通过这个结构体,就能从input_dev找到对应的input_handler,也能从input_handler找到input_dev结构体。
然后呢,调用input_register_handle这个函数将这个input_handle结构体注册到内核中。
是不是想问,这个注册又干了什么神奇的操作?哈哈,这个注册就真的只是将这个结构体放入到dev->h_list这个链表而已啦。
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
struct input_dev *dev = handle->dev;
int error;
/*
* We take dev->mutex here to prevent race with
* input_release_device().
*/
error = mutex_lock_interruptible(&dev->mutex);
if (error)
return error;
/*
* 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);
mutex_unlock(&dev->mutex);
/*
* 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;
}
注册了input_handle这个结构体后呢,connect函数还做了一件重要的事,那就是用cdev_init和cdev_device_add函数创建设备,而这个设备的名字,在connect函数的代码中可以看出,就是"event%d", 也就对应于/dev/input/eventX, 这个X就是设备的次设备号了。
也就是说,tsc2007_core.c通过注册input_dev结构体,一系列反应之后,就会在/dev/input/目录下生成一个eventX的设备节点。
我画了个巨丑的图。
那驱动是怎么工作的呢?排开tsc2007_core.c中IIC的部分来讲,tsc2007驱动应该是从IIC总线上获取触摸的数据,然后将这些数据上报给系统。在tsc2007_core.c中有这样的一部分代码:
static irqreturn_t tsc2007_soft_irq(int irq, void *handle)
{
struct tsc2007 *ts = handle;
struct input_dev *input = ts->input;
struct ts_event tc;
u32 rt;
while (!ts->stopped && tsc2007_is_pen_down(ts)) {
/* pen is down, continue with the measurement */
mutex_lock(&ts->mlock);
tsc2007_read_values(ts, &tc);
mutex_unlock(&ts->mlock);
rt = tsc2007_calculate_resistance(ts, &tc);
if (!rt && !ts->get_pendown_state) {
/*
* If pressure reported is 0 and we don't have
* callback to check pendown state, we have to
* assume that pen was lifted up.
*/
break;
}
if (rt <= ts->max_rt) {
dev_dbg(&ts->client->dev,
"DOWN point(%4d,%4d), resistance (%4u)\n",
tc.x, tc.y, rt);
rt = ts->max_rt - rt;
input_report_key(input, BTN_TOUCH, 1);
input_report_abs(input, ABS_X, tc.x);
input_report_abs(input, ABS_Y, tc.y);
input_report_abs(input, ABS_PRESSURE, rt);
input_sync(input);
} else {
/*
* Sample found inconsistent by debouncing or pressure is
* beyond the maximum. Don't report it to user space,
* repeat at least once more the measurement.
*/
dev_dbg(&ts->client->dev, "ignored pressure %d\n", rt);
}
wait_event_timeout(ts->wait, ts->stopped, ts->poll_period);
}
dev_dbg(&ts->client->dev, "UP\n");
input_report_key(input, BTN_TOUCH, 0);
input_report_abs(input, ABS_PRESSURE, 0);
input_sync(input);
if (ts->clear_penirq)
ts->clear_penirq();
return IRQ_HANDLED;
}
很显然,这个是tsc2007的中断服务函数,当有触摸笔触摸屏幕的时候,tsc2007芯片触发中断信号,导致中断服务函数被调用,函数中,驱动会通过IIC总线读取芯片的触摸数据,当然包括X/Y/Z等数据,具体不做分析。然后会有一系列的计算和校验。最后,会调用input_report_key/input_report_abs/input_sync这些接口,将计算后的坐标值上报给系统。
那上报这些接口是怎么将数据传出去的呢?
这些接口都统一调用了input_event这个函数,input_event这个函数在input.c中实现,可以也是输入子系统中的核心的函数,源码如下:
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
这里采用了自旋锁的同步机制,然后调用了input_handle_event这个函数。然后又调用了input_pass_values(dev, dev->vals, dev->num_vals);这个函数。
然后再这个函数里通过handle = rcu_dereference(dev->grab);找到对应的input_handle结构体, 然后调用了count = input_to_handler(handle, vals, count);这个函数。在这个函数里通过struct input_handler *handler = handle->handler;的方式找到了input_handler这个结构体,其实就是evdev_handler这个东西。
static unsigned int input_to_handler(struct input_handle *handle,
struct input_value *vals, unsigned int count)
{
struct input_handler *handler = handle->handler;
struct input_value *end = vals;
struct input_value *v;
if (handler->filter) {
for (v = vals; v != vals + count; v++) {
if (handler->filter(handle, v->type, v->code, v->value))
continue;
if (end != v)
*end = *v;
end++;
}
count = end - vals;
}
if (!count)
return 0;
if (handler->events)
handler->events(handle, vals, count);
else if (handler->event)
for (v = vals; v != vals + count; v++)
handler->event(handle, v->type, v->code, v->value);
return count;
}
最后,函数会调用input_handler提供的events和event函数。就是evdev_handler结构体中的evdev_events和evdev_event函数。
这里就仅仅拿evdev_events函数来简单的分析一下。
static void evdev_events(struct input_handle *handle,
const struct input_value *vals, unsigned int count)
{
struct evdev *evdev = handle->private;
struct evdev_client *client;
ktime_t ev_time[EV_CLK_MAX];
ev_time[EV_CLK_MONO] = ktime_get();
ev_time[EV_CLK_REAL] = ktime_mono_to_real(ev_time[EV_CLK_MONO]);
ev_time[EV_CLK_BOOT] = ktime_mono_to_any(ev_time[EV_CLK_MONO],
TK_OFFS_BOOT);
rcu_read_lock();
client = rcu_dereference(evdev->grab);
if (client)
evdev_pass_values(client, vals, count, ev_time);
else
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_values(client, vals, count, ev_time);
rcu_read_unlock();
}
当有函数调用这个函数的时候,它会找到一个evdev_client结构体,然后调用evdev_pass_values函数。
其实这个evdev_client本质上就是一个环形缓存区,从它的结构体上就可以看出来了。
struct evdev_client {
unsigned int head;
unsigned int tail;
unsigned int packet_head; /* [future] position of the first element of next packet */
spinlock_t buffer_lock; /* protects access to buffer, head and tail */
struct fasync_struct *fasync;
struct evdev *evdev;
struct list_head node;
unsigned int clk_type;
bool revoked;
unsigned long *evmasks[EV_CNT];
unsigned int bufsize;
struct input_event buffer[];
};
再看看evdev_pass_values的源码。
static void evdev_pass_values(struct evdev_client *client,
const struct input_value *vals, unsigned int count,
ktime_t *ev_time)
{
struct evdev *evdev = client->evdev;
const struct input_value *v;
struct input_event event;
bool wakeup = false;
if (client->revoked)
return;
event.time = ktime_to_timeval(ev_time[client->clk_type]);
/* Interrupts are disabled, just acquire the lock. */
spin_lock(&client->buffer_lock);
for (v = vals; v != vals + count; v++) {
if (__evdev_is_filtered(client, v->type, v->code))
continue;
if (v->type == EV_SYN && v->code == SYN_REPORT) {
/* drop empty SYN_REPORT */
if (client->packet_head == client->head)
continue;
wakeup = true;
}
event.type = v->type;
event.code = v->code;
event.value = v->value;
__pass_event(client, &event);
}
spin_unlock(&client->buffer_lock);
if (wakeup)
wake_up_interruptible(&evdev->wait);
}
在evdev_pass_values函数里面,程序会使用ktime_to_timeval记录一个时间。然后使用__pass_event函数将数据放入环形缓冲区。
最后使用wake_up_interruptible唤醒等待队列。
总的来说,tsc2007读数据的过程就是由中断触发的,然后通过IIC总线将数据取出来,利用input_event函数将数据上报到内核的环形缓冲区中。
其他应用程序会从这个环形缓冲区中将数据拿走。
整一个输入子系统的框架大概就是这样的了,能力有限,如果有错的地方,可以指出哦。