Linux Input子系统分析之eventX设备创建和事件传递

Linux Input子系统整体架构


Linux Input子系统的架构图


The input subsystem

 

注册eventX设备


注册过程大致如下:
input_register_device -> input_attach_handler -> input_match_device -> connect

input_register_device对input_handler_list中的每一个handler尝试input_attach_handler

list_for_each_entry(handler, &input_handler_list, node)
    input_attach_handler(dev, handler);
input_attach_handler调用input_match_device,如果match,则调用该handler的connect evdev默认match所有的input_dev设备,evdev_handler的connect为evdev_handler 它会分配一个evdev,初始化handle的dev和handler,将input_dev和input_handler绑定在一起,并将该handle分别挂到input_dev的h_list和input_handler的h_list上
static int evdev_connect(struct input_handler *handler, 
            struct input_dev *dev,
            const struct input_device_id *id)
{
    struct evdev *evdev;
    ...
    // 寻找可用minor值
    for (minor = 0; minor < EVDEV_MINORS; minor++)
        if (!evdev_table[minor])
            break;

    // 分配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);

    dev_set_name(&evdev->dev, "event%d", minor);
    evdev->exist = true;
    evdev->minor = minor;
    evdev->hw_ts_sec = -1;
    evdev->hw_ts_nsec = -1;

    // 初始化evdev->handle
    evdev->handle.dev = input_get_device(dev);
    evdev->handle.name = dev_name(&evdev->dev);
    evdev->handle.handler = handler;
    evdev->handle.private = evdev;

    // 初始化evdev->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);

    // 将handle分别挂到input_dev的h_list和input_handler的h_list上
    error = input_register_handle(&evdev->handle);

    // 保存evdev到evdev_table数组
    error = evdev_install_chrdev(evdev);

    // 创建/dev/input/eventX文件
    error = device_add(&evdev->dev);
   ...
}

int input_register_handle(struct input_handle *handle)
{
    ...
    list_add_tail_rcu(&handle->d_node, &dev->h_list);
    list_add_tail_rcu(&handle->h_node, &handler->h_list);
    ...
}

// 保存evdev到evdev_table数组
static int evdev_install_chrdev(struct evdev *evdev)
{
    evdev_table[evdev->minor] = evdev;
    return 0;
}
最后在/dev/input/目录下创建出eventX设备 需要注意的是evdev和input_dev各自拥有自己的struct device dev成员,调用device_add创建eventX设备时使用的是evdev->dev evdev->dev.parent = dev->dev 两个dev的class都是input_class 每个eventX都对应一对input_dev和input_handler(由input_handle绑定在一起),一个input_dev可以对应多个input_handler,一个input_handler也可以对应多个input_dev 每个eventX都对应一对input_dev和input_handler(由input_handle绑定在一起),一个input_dev可以对应多个input_handler,一个input_handler也可以对应多个input_dev 注:与eventX对应的input_dev包含多个handler:sysrq、rfkill、kbd、evdev,evdev只是其中之一
<3>handler: c0817300, sysrq    drivers/tty/sysrq.c
<3>handler: c084a7e0, rfkill   net/rfkill/input.c
<3>handler: c081752c, kbd      drivers/tty/vt/keyboard.c
<3>handler: c08215d4, evdev    drivers/input/evdev.c
其他handler,如kbd在kbd_connect时并没有创建设备文件  

通过/dev/input/eventX注入和接收输入事件

直接写/dev/input/eventX,或者通过uinput创建完eventX设备后直接写/dev/uinput, 都能让eventX产生输入事件,进而Android通过EventHub从eventX中收到新的key或者motion事件 调用流程如下:
input_inject_event -> input_handle_event -> input_pass_event

drivers/input/input.c
void input_inject_event(struct input_handle *handle,
            unsigned int type, unsigned int code, int value);
static void input_handle_event(struct input_dev *dev,
                   unsigned int type, unsigned int code, int value);
static void input_pass_event(struct input_dev *dev,
                 unsigned int type, unsigned int code, int value);
从dev的h_list中取出每一个handle,进而取出handle中的handler,调用handler->event
list_for_each_entry_rcu(handle, &dev->h_list, d_node) {
    if (!handle->open)
        continue;

    handler = handle->handler;
    if (!handler->filter) {
        if (filtered)
            break;

        handler->event(handle, type, code, value);

    } else if (handler->filter(handle, type, code, value))
        filtered = true;
}
对于eventX,handler为evdev_handler,event为evdev_event,
drivers/input/evdev.c
list_for_each_entry_rcu(client, &evdev->client_list, node)
    evdev_pass_event(client, &event, time_mono, time_real);
对evdev上每一个evdev_client调用evdev_pass_event,注意,每次open eventX都会分配一个evdev_client挂在evdev的client_list上 evdev_pass_event将新event事件加入到evdev_client的buffer中,并唤醒buffer上所有读等待的任务 这样,每一个open eventX的进程都能从buffer中读出事件 从以上流程可以看到,对evdev创建的eventX设备write时,会导致所有的handler的event被调用,而不仅仅是evdev的evdev_event
每个eventX都对应一个evdev(input_device_registe调用evdev_connect产生),每次open eventX都会分配一个evdev_client挂在evdev的client_list上 每个eventX都对应一个evdev(input_device_registe调用evdev_connect产生),每次open eventX都会分配一个evdev_client挂在evdev的client_list上

Input Kernel Driver Example

event-injector-kmod是一个内核驱动demo,insmod后会注册一个/dev/input/eventX设备 假设我们创建出的设备为/dev/input/event2,在Android中可以通过以下脚本模拟调节音量键
$ cat event.sh
sendevent /dev/input/event2 1 $1 1
sendevent /dev/input/event2 0 0 0
sendevent /dev/input/event2 1 $1 0
sendevent /dev/input/event2 0 0 0
执行adb shell后执行sh event.sh 114,就会让Android系统收到音量调节按键事件 也可以通过Android jni代码向eventX写入事件(需要root,先chmod 666 /dev/input/event2)
#include <linux/input.h>

/* struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
}; */

void send_event(int fd, uint16_t type, uint16_t code, int32_t value) {
    debug("SendEvent call (%d,%d,%d,%d)", fd, type, code, value);
    if (fd <= fileno(stderr)) return;

    struct input_event event;
    int len;

    memset(&event, 0, sizeof(event));
    gettimeofday(&event.time, NULL);

    // event (type, code, value)
    event.type = type;
    event.code = code;
    event.value = value;
    if (write(fd, &event, sizeof(event)) < 0) {
        debug("send_event error");
    }

    // sync (0,0,0)
    event.type = EV_SYN;
    event.code = SYN_REPORT;
    event.value = 0;
    if (write(fd, &event, sizeof(event)) < 0) {
        debug("send_event error");
    }
}

void inject()
{
    int fd = open("/dev/input/event2", O_RDWR | O_NDELAY);
    send_event(fd, 1, 114, 1);  // send volume-down key down event
    send_event(fd, 1, 114, 0);  // send volume-down key up event
    close(fd);
}




参考


input子系统整体流程全面分析
Linux设备驱动剖析之Input(一)(二)(三)(四)
Linux输入子系统:输入设备编程指南 – input-programming.txt
Input Event Drivers (Virtual Mouse)

PDF格式:
linux input子系统整体流程分析
设备驱动程序实例_Button
设备驱动程序实例_VirtualMouse