在linux中,像TP、G-sensor、按鍵等模塊都會用到input系統上報事件,下面我們看看input系統是如何實現事件上報的。
Linux對input子系統進行了高度的抽象,將input系統具體分成三層,input核心層、input事件處理層和input設備驅動層。input核心層(input-core)對input設備驅動層(input-device)和input事件處理層(input-handler)進行管理並進行消息轉發。如下圖:
- 設備驅動層input-device
input設備驅動層對應的不同的設備驅動,像TP、鼠標、鍵盤等設備驅動,驅動中會註冊input系統,利用input系統上報事件,下面是一個簡單的設備驅動註冊input系統。
static int tiny210_init_input(void)
{
int err = 0;
printk(SF_LOG_LEVEL, "%s(..) enter.\n", __FUNCTION__);
struct inout_dev *input = input_allocate_device(); //申請input設備
if (!input) {
printk(KERN_ERR, "input_allocate_device(..) failed.\n");
return (-ENOMEM);
}
//填充input結構體數據
input->name = "tiny210-keys";
__set_bit(EV_KEY , input->evbit );
__set_bit(KEY_HOME, input->keybit);
__set_bit(KEY_MENU, input->keybit);
err = input_register_device(input); //註冊一個input設備
if (err) {
printk(KERN_ERR, "input_register_device(..) = %d.\n", err);
input_free_device(input);
input = NULL;
return (-ENODEV);
}
printk(SF_LOG_LEVEL, "%s(..) leave.\n", __FUNCTION__);
return err;
}
//有中斷或者按鍵事件上報按鍵事件,一般在中斷處理函數中
key_code = KEY_HOME;
input_report_key(input, key_code, kevent->value);
input_sync(input);
設備驅動層使用主要是去註冊一個input子系統,然後通過註冊的系統,上報想要上報的事件。從上面可以看出,驅動中使用input一般分爲4步,1、input_allocate_device申請input資源;2、填充input結構體,包括name、註冊的事件類型(如按鍵事件、絕對事件等等);3、input_register_device(input)在內核註冊一個input事件;4、當有按鍵或者TP報點產生時,同步上報按鍵事件,這一步一般會在中斷處理函數中進行。
input_allocate_device和input_register_device都是input-core暴露出來的接口。
int input_register_device(struct input_dev *dev)
{
static atomic_t input_no = ATOMIC_INIT(0);
struct input_handler *handler;
const char *path;
int error;
/* 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);
if (!dev->hint_events_per_packet)
dev->hint_events_per_packet =
input_estimate_events_per_packet(dev);
/*
* 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.
*/
init_timer(&dev->timer);
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
dev->timer.data = (long) dev;
dev->timer.function = input_repeat_key;
dev->rep[REP_DELAY] = 250;
dev->rep[REP_PERIOD] = 33;
}
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
dev_set_name(&dev->dev, "input%ld",
(unsigned long) atomic_inc_return(&input_no) - 1);
//設備驅動模型視圖中增加一個input設備
error = device_add(&dev->dev);
if (error)
return error;
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) {
device_del(&dev->dev);
return error;
}
//鏈接到全局輸入設備鏈表
list_add_tail(&dev->node, &input_dev_list);
//匹配全局事件處理handler鏈表,匹配支持該輸入設備的handler
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
return 0;
}
- 事件層input-handler
從上面的圖中可以看出,不同的設備會對應有不同的handler,我們以支持TP的event-handler爲例說明,event-handler代碼在driver/input/evdev.c中。
static const struct input_device_id evdev_ids[] = { //設備ID
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
MODULE_DEVICE_TABLE(input, evdev_ids);
static struct input_handler evdev_handler = {
.event = evdev_event, //消息處理接口
.connect = evdev_connect, // input-core匹配到device後調用的接口
.disconnect = evdev_disconnect,
.fops = &evdev_fops, //管理改輸入操作的接口類
.minor = EVDEV_MINOR_BASE, //handler名稱
.name = "evdev",
.id_table = evdev_ids, //設備id,標識支持的device型號
};
static int __init evdev_init(void)
{
//註冊handler,input_register_handler是input-core提供的接口
return input_register_handler(&evdev_handler);
}
input_register_handler函數在/driver/input/input.c中,也是input-core暴露出來的接口:
static struct input_handler *input_table[8];
//初始化全局input-handler列表
static LIST_HEAD(input_handler_list);
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int retval;
retval = mutex_lock_interruptible(&input_mutex);
if (retval)
return retval;
INIT_LIST_HEAD(&handler->h_list);
if (handler->fops != NULL) {
if (input_table[handler->minor >> 5]) {
retval = -EBUSY;
goto out;
}
//註冊的handler添加到input_table中,input_table最多支持8種handler(例如按鍵、鼠標爲不同hander),每種handler最多支持32個設備
input_table[handler->minor >> 5] = handler;
}
//添加到input-handler全局鏈表
list_add_tail(&handler->node, &input_handler_list);
//遍歷input_dev_list全局設備鏈表,尋找handler支持的設備
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
out:
mutex_unlock(&input_mutex);
return retval;
}
- 核心層input-core
核心層input-core完成的工作包括:
1) 直接跟字符設備驅動框架交互,字符設備驅動框架根據主設備號來進行管理,而input-core則是依賴於次設備號來進行分類管理。Input子系統的所有輸入設備的主設備號都是13,其對應input-core定義的structfile_operations input_fops.驅動架構層通過主設備號13獲取到input_fops,之後的處理便交給input_fops進行。
2) 提供接口供事件處理層(input-handler)和輸入設備(input-device)註冊,併爲輸入設備找到匹配的事件處理者。
3) 將input-device產生的消息(如觸屏座標和壓力值)轉發給input-handler,或者將input-handler的消息傳遞給input-device(如鼠標的閃燈命令)。
input-core代碼主要在/driver/input/input.c中:
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
.llseek = noop_llseek,
};
static int __init input_init(void)
{
int err;
//sys/class/下面創建input節點
err = class_register(&input_class);
if (err) {
pr_err("unable to register input_dev class\n");
return err;
}
err = input_proc_init();
if (err)
goto fail1;
//dev下面創建設備節點
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
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;
}
static int __init input_proc_init(void)
{
struct proc_dir_entry *entry;
proc_bus_input_dir = proc_mkdir("bus/input", NULL);
if (!proc_bus_input_dir)
return -ENOMEM;
// bus/input下面建立devices節點
entry = proc_create("devices", 0, proc_bus_input_dir,
&input_devices_fileops);
if (!entry)
goto fail1;
// bus/input下面建立handlers節點
entry = proc_create("handlers", 0, proc_bus_input_dir,
&input_handlers_fileops);
if (!entry)
goto fail2;
return 0;
fail2: remove_proc_entry("devices", proc_bus_input_dir);
fail1: remove_proc_entry("bus/input", NULL);
return -ENOMEM;
}
除了在/dev下面簡歷主設備號爲13的input設備節點,還會在設備模型/sys/class目錄註冊設備類,在/proc/bus/input目錄產生設備信息,可以在對應的節點下查看支持的device和handler。
- input-core關聯匹配input-device和input-handler
在上面的input_register_device和input_register_handler接口中,最後都會調用input_attach_handler接口來匹配輸入事件與對應的handler。
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
//匹配handler和device的ID,event_handler默認處理所有的事件類型設備
id = input_match_device(handler, dev);
if (!id)
return -ENODEV;
//匹配成功後調用handler的connect接口,既evdev_connect
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;
}
//每個handler支持的最多設備數
#define EVDEV_MINORS 32
//evdev_table的每一個元素對應一個input-device
static struct evdev *evdev_table[EVDEV_MINORS];
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int error;
//從0開始找到一個未用的索引號
for (minor = 0; minor < EVDEV_MINORS; minor++)
if (!evdev_table[minor])
break;
if (minor == EVDEV_MINORS) {
pr_err("no more free evdev devices\n");
return -ENFILE;
}
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev)
return -ENOMEM;
INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait);
//次設備文件名,這裏是我們能看到的最終生成的設備文件event0、event1等
dev_set_name(&evdev->dev, "event%d", minor);
evdev->exist = true;
evdev->minor = minor;
//填充evdev結構體
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev;
//在設備驅動視圖/sys/class/input和/sys/devices目錄下產生eventX設備
//最終依賴event機制和mdev在/dev目錄生成對應的設備文件
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);
//註冊handler,關聯input-handler和input-device
error = input_register_handle(&evdev->handle);
if (error)
goto err_free_evdev;
//將evdev放入evdev_table中
error = evdev_install_chrdev(evdev);
if (error)
goto err_unregister_handle;
error = device_add(&evdev->dev);
if (error)
goto err_cleanup_evdev;
return 0;
err_cleanup_evdev:
evdev_cleanup(evdev);
err_unregister_handle:
input_unregister_handle(&evdev->handle);
err_free_evdev:
put_device(&evdev->dev);
return error;
}
evdev_table代表evdev_handler所管理的底層input-device(通過input-handle管理)和應用層已打開該設備的進程、同步的相關結構和消息隊列(evdev_client記錄)。
struct evdev {
int open;
int minor;
//管理handler對應的device
struct input_handle handle;
wait_queue_head_t wait;
//記錄應用層已經open過eventX設備的進程、同步異步相關結構和消息池
struct evdev_client __rcu *grab;
struct list_head client_list;
spinlock_t client_lock; /* protects client_list */
struct mutex mutex;
struct device dev;
bool exist;
};
input-handler與input-device是通過input-handle來關聯的,在input_register_handle函數中,將input-handle中的d_node和h_node分別關聯到input-device和input-handler中,這樣通過input-handler可以快速找到input-device,通過input-device也可以快速找到input-handler。
至此,我們可以得到以下evdev-handler管理下的示意圖:
- 應用層open、read操作
input-device註冊成功後,會生成不同的節點,如下所示:
應用層如果要得到相應的數據,例如得到按鍵操作的鍵值,會去open、read節點,例如open(dev/input/event0),下面我們看看open過程,設備節點的註冊是在input_init實現的,register_chrdev(INPUT_MAJOR, "input", &input_fops);open函數會調用input_open_file函數:
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler;
const struct file_operations *old_fops, *new_fops = NULL;
int err;
err = mutex_lock_interruptible(&input_mutex);
if (err)
return err;
/* No load-on-demand here? */
//iminor(inode)爲次設備號,根據次設備號找到註冊的input-handler
//input_table在input_register_handler時會填充
handler = input_table[iminor(inode) >> 5];
if (handler) //handler下面對應的fops
new_fops = fops_get(handler->fops);
mutex_unlock(&input_mutex);
/*
* That's _really_ odd. Usually NULL ->open means "nothing special",
* not "no device". Oh, well...
*/
if (!new_fops || !new_fops->open) {
fops_put(new_fops);
err = -ENODEV;
goto out;
}
old_fops = file->f_op;
file->f_op = new_fops;
//執行對應的handler下面的open
err = new_fops->open(inode, file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
out:
return err;
}
input_open_file會通過查找不同的handler,找到對應的open函數,我們繼續已evdev_handler爲例。
static int evdev_open(struct inode *inode, struct file *file)
{
struct evdev *evdev;
struct evdev_client *client;
int i = iminor(inode) - EVDEV_MINOR_BASE;
unsigned int bufsize;
int error;
if (i >= EVDEV_MINORS)
return -ENODEV;
error = mutex_lock_interruptible(&evdev_table_mutex);
if (error)
return error;
//input_attach_handler時填充
evdev = evdev_table[i];
if (evdev)
get_device(&evdev->dev);
mutex_unlock(&evdev_table_mutex);
if (!evdev)
return -ENODEV;
bufsize = evdev_compute_buffer_size(evdev->handle.dev);
client = kzalloc(sizeof(struct evdev_client) +
bufsize * sizeof(struct input_event),
GFP_KERNEL);
if (!client) {
error = -ENOMEM;
goto err_put_evdev;
}
client->bufsize = bufsize;
spin_lock_init(&client->buffer_lock);
//根據當前進程產生client的名稱
snprintf(client->name, sizeof(client->name), "%s-%d",
dev_name(&evdev->dev), task_tgid_vnr(current));
wake_lock_init(&client->wake_lock, WAKE_LOCK_SUSPEND, client->name);
//將代表打開該設備的進程相關的數據結構client和evdev綁定
client->evdev = evdev;
evdev_attach_client(evdev, client);
//執行input-device層的open
error = evdev_open_device(evdev);
if (error)
goto err_free_client;
file->private_data = client;
nonseekable_open(inode, file);
return 0;
err_free_client:
evdev_detach_client(evdev, client);
wake_lock_destroy(&client->wake_lock);
kfree(client);
err_put_evdev:
put_device(&evdev->dev);
return error;
}
evdev_open_device會調用到設備驅動中的open,一般像TP、G-sensor註冊input系統沒有定義open接口。
上面open獲得的fd句柄對應的file_operations是evdev_handler的evdev_fops,所以應用層read的時候最終會調用evdev_read接口。上面open過程中,我們看到evdev_client結構體,evdev_client中的buffer作爲一個消息隊列會保存input-core轉發上來的底層消息:
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 wake_lock wake_lock;
char name[28];
//異步通知上層等待進程
struct fasync_struct *fasync;
struct evdev *evdev;
struct list_head node;
unsigned int bufsize;
//消息隊列,input-core轉發的底層觸屏消息會放到這裏
//上層應用進程也會在這裏讀取
struct input_event buffer[];
};
下面看看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;
struct input_event event;
int retval;
//判斷讀取長度是否小於單個input-event的長度
if (count < input_event_size())
return -EINVAL;
//判斷消息隊列消息是否爲空
if (!(file->f_flags & O_NONBLOCK)) {
//等待消息隊列不爲空事件
//阻塞在這,等待wait釋放,消息更新
retval = wait_event_interruptible(evdev->wait,
client->packet_head != client->tail || !evdev->exist);
if (retval)
return retval;
}
if (!evdev->exist)
return -ENODEV;
//將消息隊列的消息取出並通過copy_to_user填到buffer中返回
while (retval + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {
if (input_event_to_user(buffer + retval, &event))
return -EFAULT;
retval += input_event_size();
}
if (retval == 0 && file->f_flags & O_NONBLOCK)
retval = -EAGAIN;
return retval;
}
當沒有數據時,wait_event_interruptible阻塞,上層進程將進入睡眠。直到有數據更新時,喚醒進程,那什麼時候會有數據更新了?我們在前面介紹input設備驅動的時候有說到input_report_key(input, key_code, kevent->value);和input_sync(input);數據就是在這裏更新的。以TP爲例,TP在產生中斷後,會觸發中斷處理函數,調度到中斷下部分後,會去讀取TP的報點,然後通過input_report_abs和input_sync接口將數據更新到input-core中。
input_report_abs、input_report_key、input_sync都是input.h中暴露出來的接口,最後都是調用input-core層的input-event接口:
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);
add_input_randomness(type, code, value);
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition = INPUT_IGNORE_EVENT;
switch (type) {
case EV_SYN:
switch (code) {
case SYN_CONFIG:
disposition = INPUT_PASS_TO_ALL;
break;
case EV_ABS:
//檢測input-device初始化聲明是否支持該編碼ABS_MAX
if (is_event_supported(code, dev->absbit, ABS_MAX))
disposition = input_handle_abs_event(dev, code, &value);
break;
//非同步事件
if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
dev->sync = false;
//不是傳遞給input-core處理事件
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
if (disposition & INPUT_PASS_TO_HANDLERS)
input_pass_event(dev, type, code, value);
}
static void input_pass_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
struct input_handler *handler;
struct input_handle *handle;
rcu_read_lock();
//輸入設備對應的input-handle,其handler就是evdev_handler
handle = rcu_dereference(dev->grab);
if (handle) //調用到evdev_event
handle->handler->event(handle, type, code, value);
……
}
static void evdev_event(struct input_handle *handle,
unsigned int type, unsigned int code, int value)
{
struct evdev *evdev = handle->private;
struct evdev_client *client;
struct input_event event;
struct timespec ts;
//填充輸入消息結構體
ktime_get_ts(&ts);
event.time.tv_sec = ts.tv_sec;
event.time.tv_usec = ts.tv_nsec / NSEC_PER_USEC;
event.type = type;
event.code = code;
event.value = value;
rcu_read_lock();
client = rcu_dereference(evdev->grab);
if (client) //將消息寫入client結構體中的buffer中
evdev_pass_event(client, &event);
else
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_event(client, &event);
rcu_read_unlock();
//喚醒等待讀取消息進程,input_sync函數調用下來就會喚醒進程
if (type == EV_SYN && code == SYN_REPORT)
wake_up_interruptible(&evdev->wait);
}
wake_up_interruptible(&evdev->wait)調用後會喚醒執行在evdev_read中wait_event_interruptible等待讀取消息的進程,繼續下面的執行過程,從client的buffer中取出消息,並通過copy_to_user返回給應用程序。