linux input子系统是linux内核用于管理各种输入设备的部分,内核将给用户导出一套固定的硬件无关的input API,供用户空间程序使用。
input系统分为三块:input core、input drivers和event handles。数据传输从底层硬件到input driver,再经过input core到event handles,最后到达用户空间。
以下代码,以 linux4.20.3 为例进行学习。
input子系统的core代码主要是input.c,该文件自成模块,模块的注册函数实现如下:
static int __init input_init(void)
{
int err;
/* 注册input类 */
err = class_register(&input_class);
if (err) {
pr_err("unable to register input_dev class\n");
return err;
}
/* 在/proc创建 bus/input/devices handlers */
err = input_proc_init();
if (err)
goto fail1;
/* 注册input字符设备 */
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES, "input");
if (err) {
pr_err("unable to register char major %d", INPUT_MAJOR);
goto fail2;
}
return 0;
fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
在input.c是input core的核心文件,input core主要是承上启下,为input drivers提供输入设备注册和操作接口,如input_register_device()函数;通知event handles对事件进行处理;在/proc下产生相应的设备信息。input core将会负责将input drivers和event handles联通,具体是如何完成这个操作的呢,继续看input drivers和event handles。
[linux/input.h]
/*
* The event structure itself
*/
struct input_event {
struct timeval time; /* 输入事件时间 */
__u16 type; /* 类型 */
__u16 code; /* 事件代码 [linux/input-event-codes.h] */
__s32 value; /* 事件值(当type为EV_KEY时,value:0表示按键抬起,1表示按键按下) */
};
[linux/input-event-codes.h]
/*
* Event types
*/
#define EV_SYN 0x00 /* 所有input设备都具备的同步事件,主要是与client同步事件队列 */
#define EV_KEY 0x01 /* 按键 */
#define EV_REL 0x02 /* 鼠标事件 相对坐标 */
#define EV_ABS 0x03 /* 手写板事件 绝对坐标 */
#define EV_MSC 0x04 /* 其他类型 */
#define EV_SW 0x05 /* 开关状态事件 */
#define EV_LED 0x11 /* LED事件 */
#define EV_SND 0x12 /* 音频事件 */
#define EV_REP 0x14 /* 用于指定自动重复事件 */
#define EV_FF 0x15 /* 用于初始化具有力反馈功能的设备并使该设备反馈 */
#define EV_PWR 0x16 /* 电源管理 */
#define EV_FF_STATUS 0x17 /* 用于接收力反馈设备状态 */
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
通过按键代码,了解input子系统的框架,按键驱动代码位于/drivers/input/keyboard目录下的gpio_keys.c。
以下是gpio_keys.c的分析:
首先可以看到,将会通过gpio_keys_init()函数注册一个platform驱动,驱动name为“gpio-keys”,当系统中存在platform设备与gpio_keys_of_match数组中的compatible字段的信息一致,则驱动设备与驱动匹配成功,将执行probe()函数gpio_keys_probe(),所以,需要匹配成功,需要在dts中配置gpio-keys设备的相应信息。
[dts]
gpio_keys {
compatible = "gpio-keys";
button@0 {
gpios = <&r_pio PL 2 1 2 2 1>;
linux,code = <158>;
label = "Back";
};
};
static int gpio_keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
if (!pdata) {
/* 一般的,初始化时pdata是NULL,所以将会通过gpio_keys_get_devtree_pdata()函数,
* 从dts中获取button的相应信息 */
pdata = gpio_keys_get_devtree_pdata(dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
}
}
gpio_keys_get_devtree_pdata()将会进行以下操作:
继续回到gpio_keys_probe()函数,从dts中获取到button信息之后,将会通过调用devm_input_allocate_device()([input.c])函数创建input_dev设备。接着是通过gpio_keys_setup_key()函数,为各个button申请相应的GPIO、中断资源等。最后,通过input_register_device()函数,注册input设备。至此,可以看到input drivers注册完成。
input handle的相关操作在evdev.c实现,该文件恰好自己构成一个模块,先分析模块的注册函数,注册函数只是调用了input_register_handler()([input.c]),该函数主要是为系统中的输入设备注册一个新的输入处理程序,并将其附加到与该处理程序兼容的所有输入设备上。该模块将在input dev注册时,将会通过input core完成与input dev完成相应的connect,当event发生时,又将会处理相应的event事件并上报。
input core、input drivers和event handles三块的简单介绍如上,下面,我们将带着问题去阅读代码,进一步了解Linux input子系统。
在介绍上面的input drivers的时候就有提到,在driver的probe函数中,将会通过input_register_device()函数注册input device,下面来分析一下该函数的实现。
int input_register_device(struct input_dev *dev)
{
/* 检查input dev支持的事件类型,注册device等 */
...
error = mutex_lock_interruptible(&input_mutex);
if (error)
goto err_device_del;
/* 将该input device添加到input_dev_list链表 */
list_add_tail(&dev->node, &input_dev_list);
/* 在linux input子系统中,一个input device的输入事件
* 将会发送到系统中所有的event handles,所以这里从保存
* event handles的全局链表input_handler_list中,逐个获
* 取event handles添加input device */
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
/* 当注册input device成功,将会通过该函数唤醒profs的poll线程 */
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
...
}
然后我们再回到这个问题,input core是如何知道有多少的input devices,显然,是在通过input_register_device()注册input device的时候,同时会把input device添加到全局链表input_dev_list[input.c]中,这样,input core通过枚举input_dev_list链表,就可以得到相应的input device。
还剩一个问题,这些input device支持的事件类型和事件代码,又是从哪里填充的呢?
上面在介绍input device的时候,我们是以gpio-keys为例,同样的,在这里我们继续以它为例,也即gpio_keys.c。
在input device的probe()函数中,如上面介绍,将会通过gpio_keys_get_devtree_pdata()函数从dts中获取button的相应信息,接着通过gpio_keys_setup_key()将上面获取得到的信息填充到input dev,比如通过input_set_capability()函数设置input dev支持的事件类型等。
这样,linux内核就知道,当前的input dev支持什么事件type以及code。
继续以gpio-keys作为input dev例子进行这个问题的解答。
回到gpio_keys_setup_key()函数,在该函数中,获取gpio之后,将会申请相应的中断,同时设置中断函数,中断函数有两个,分别是gpio_keys_gpio_isr()和gpio_keys_irq_isr(),将会根据gpio的信息相应的选择其中一个,以gpio_keys_gpio_isr()中断函数,当相应的gpio中断信号到来时,系统将会调用该函数,而在该函数中,又将存在以下代码:
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
...
mod_delayed_work(system_wq,
&bdata->work,
msecs_to_jiffies(bdata->software_debounce));
...
同时的,在gpio_keys_setup_key()函数中,是这样初始化bdata->work,所以,在中断函数中,设置延迟一段时间执行gpio_keys_gpio_work_func()函数。
INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);
而gpio_keys_gpio_work_func()函数也很简单,主要是通过gpio_keys_gpio_report_event()函数报告相应的事件信息。
static void gpio_keys_gpio_work_func(struct work_struct *work)
{
struct gpio_button_data *bdata =
container_of(work, struct gpio_button_data, work.work);
gpio_keys_gpio_report_event(bdata);
if (bdata->button->wakeup)
pm_relax(bdata->input->dev.parent);
}
static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata)
{
const struct gpio_keys_button *button = bdata->button;
struct input_dev *input = bdata->input;
unsigned int type = button->type ?: EV_KEY;
int state;
/* 获取gpio的状态,是高电平还是低电平 */
state = gpiod_get_value_cansleep(bdata->gpiod);
if (state < 0) {
dev_err(input->dev.parent,
"failed to get gpio state: %d\n", state);
return;
}
/* 通过input_event()函数报告input事件 */
if (type == EV_ABS) {
if (state)
input_event(input, type, button->code, button->value);
} else {
input_event(input, type, *bdata->code, state);
}
/* 同步事件 */
input_sync(input);
}
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;
/* 先检查input dev是否支持该type,这个dev->evbit是在调用input_set_capability()时设置的 */
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_get_disposition()函数回去事件的相应信息。
#define INPUT_IGNORE_EVENT 0 /* 忽略该事件 */
#define INPUT_PASS_TO_HANDLERS 1 /* input handles处理该事件 */
#define INPUT_PASS_TO_DEVICE 2 /* input device处理该事件 */
#define INPUT_SLOT 4
#define INPUT_FLUSH 8
#define INPUT_PASS_TO_ALL (INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE)
gpio-keys中,事件是由handles处理,所以在input_handle_event()函数中,将会将事件的信息填充到input dev的vals数组。至此,回到gpio_keys_gpio_report_event()函数,将事件信息填充到input dev的相应结构体之后,最后将会调用input_sync(input)
函数同步事件。
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
通过input_sync()函数的代码我们可以知道,最终是发送EV_SYN事件,code为SYN_REPORT,这样的一个事件代码,将会在input_handle_event()函数中调用input_pass_values()函数,激发input handles处理事件。
而在input_pass_values()函数中,重要的也是以下部分:
static void input_pass_values(struct input_dev *dev,
struct input_value *vals, unsigned int count)
{
...
/* 一般的,handle会为空,所以执行else部分代码 */
handle = rcu_dereference(dev->grab);
if (handle) {
count = input_to_handler(handle, vals, count);
} else {
/* 从input dev的dev->h_list链表获取event handles,
* 上面就有提到,一个input dev的事件,将会发送到所有的handles */
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (handle->open) {
count = input_to_handler(handle, vals, count);
if (!count)
break;
}
}
...
}
代码跟踪到这里,又多了一个疑问:dev->h_list这个链表是什么时候填充的?
在input_register_device()函数中,通过调用input_attach_handler()函数,将event handles添加到dev->h_list,函数调用流程如下:
input_register_device()
input_attach_handler()
handler->connect(handler, dev, id)
evdev_connect()
input_register_handle()
list_add_rcu(&handle->d_node, &dev->h_list)/list_add_tail_rcu(&handle->d_node, &dev->h_list)
了解到dev->h_list链表的填充过程之后,继续回到input_pass_values()函数,在该函数中,将会针对enable的event handle调用input_to_handler()函数,而在input_to_handler()函数重要的是调用handler的events函数—evdev_events()。
/*
* Pass incoming events to all connected clients.
*/
static void evdev_events(struct input_handle *handle,
const struct input_value *vals, unsigned int count)
{
...
client = rcu_dereference(evdev->grab);
if (client)
evdev_pass_values(client, vals, count, ev_time);
else
/* 主要是通过client_list链表获取handle client进行事件处理,
* 可以将client理解为一个用户层的接收者,在open event时创建 */
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_values(client, vals, count, ev_time);
...
}
在evdev_pass_values()函数中,将会通过__pass_event()将event信息填充到client的buffer缓冲区,如果code是SYN_REPORT,将会调用kill_fasync(&client->fasync, SIGIO, POLL_IN)
异步通知应用层,这样,input event传递到应用层,接着,用户程序就可以通过read函数读取event的详细信息。
至此,linux input子系统简单介绍完毕。