zl程序教程

您现在的位置是:首页 >  系统

当前栏目

linux input子系统

Linux input 子系统
2023-09-11 14:19:53 时间

为什么要用INPUT子系统

在不采用input子系统,而是自己实现的按键字符驱动中,会自己注册驱动,提供file_operations接口,并在读接口中,读取按键的电平值上传给应用。在linux系统中(linux4.9.88),构建了input子系统,所有采用input子系统的设备,在有输入事件后都会主动上报输入事件。

在输入设备中会有以下几个问题:

  a. 何时上报?是在输入设备输入事件中断产生时上报。

  b. 如何上报?输入设备在中断函数中调用input提供的input_report_key函数。

 

INPUT子系统初始化

input.c

subsys_initcall(input_init);
module_exit(input_exit);

 

由上可知,input子系统作为一个模块,在kernel启动的时候就运行了 input_init()

static int __init input_init(void)
{
    int err;

    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;

    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;
    }
        ......
}

先注册一个 input 类型的 class

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;

    entry = proc_create("devices", 0, proc_bus_input_dir,
                &input_devices_proc_ops);
    if (!entry)
        goto fail1;

    entry = proc_create("handlers", 0, proc_bus_input_dir,
                &input_handlers_proc_ops);
        ......
}

然后在 /proc/input 下创建两个文件,用于向应用层提供 input 类型的设备信息

 

最后申请了主设备号 INPUT_MAJOR,给 INPUT 类型设备使用。

evdev.c

evdev作为一个模块,在kernel启动的时候执行

module_init(evdev_init);
module_exit(evdev_exit);

 

evdev_init()为链表 input_handler_list 填充了一个条目evdev_handler,给 input_register_device() 使用,input_register_device() --->...--->evdev_connect() 自动创建字符设备

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,
};

 

用户层

应用层代码会通过如下结构体读取事件信息

    struct input_event {
        struct timeval time;
        unsigned short type;
        unsigned short code;
        unsigned int value;
    };

 tv_sec / tv_usec:事件产生的时间

type:事件类型,如果是按键就选 EV_KEY,如下图:

#define EV_SYN            0x00
#define EV_KEY            0x01
#define EV_REL            0x02
#define EV_ABS            0x03
#define EV_MSC            0x04
#define EV_SW             0x05
#define EV_LED            0x11
#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)

EV_CNT 决定了事件类型的最大数量

code:哪个设备产生的事件,对于键盘按键,code 的取值小于 BTN_MISC,每一个code值对应键盘的一个按键,如下图:

 value:对于按键,可以设置产生事件value等于1、没产生事件value等于0

 

应用代码实例:

static struct input_event inputevent; 

err = read(fd, &inputevent, sizeof(inputevent));

驱动层

注册 input_dev 实例到内核

input 设备类型结构体

struct input_dev {
    const char *name;
    const char *phys;
    const char *uniq;
    struct input_id id;

    unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
    unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; // 1比特对应一个事件类型,EV_CNT为32,即最大32个事件类型,只需要一个 unsigned long 类型变量,所以 BITS_TO_LONGS(EV_CNT) == 1
  unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; 
  ......
  ktime_t timestamp[INPUT_CLK_MAX];
  bool inhibited;
};

 1、动态申请了一个 struct input_dev 类型变量dev,并初始化dev->dev.kobj.name = "input0",这个就是设备节点的名字;dev->dev的class name为 "input"

struct input_dev *input_allocate_device(void);

2、初始化 input_dev 的事件类型和事件值

    __set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
    __set_bit(EV_REP, inputdev->evbit); /* 重复事件 */
    __set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */

 

3、向内核注册输入设备

int __must_check input_register_device(struct input_dev *);

从 input_register_device()--->device_add() 可知,如果 input_dev.dev没有设置parent,/sys/devices/virtual/input 作为 input_dev.dev的parent。如果 input_dev.dev 没有设置 init_name,inpu_dev.dev的名字默认为 inputX(X表示数字,每添加一个,数字加1)

从input_register_device()--->input_attach_handler()--->evdev_connect()--->cdev_device_add()可知,INPUT子系统为我们做了设备号的申请、cdev创建并添加到内核、设备的创建并添加到内核

上报输入事件

 当我们向 Linux 内核注册好 input_dev 以后还不能使用 input 设备, input 设备都是具有输入功能的,但是具体是什么样的输入值 Linux 内核是不知道的,我们需要获取到具体的输入值,或者说是输入事件,然后将输入事件上报给 Linux 内核。比如按键,我们需要在按键中断处理函数,或者消抖定时器中断函数中将按键值上报给 Linux 内核,这样 Linux 内核才能获取到正确的输入值。不同的事件,其上报事件的 API 函数不同,我们依次来看一下一些常用的事件上报 API 函数。

首先是 input_event 函数,此函数用于上报指定的事件以及对应的值,函数原型如下

void input_event(struct input_dev *dev, 
          unsigned int type,
          unsigned int code,
          int value);

 

input_event 函数可以上报所有的事件类型和事件值, Linux 内核也提供了其他的针对具体事件的上报函数,这些函数其实都用到了 input_event 函数。比如上报按键所使用的input_report_key 函数,此函数内容如下:

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
    input_event(dev, EV_KEY, code, !!value);
}

 

 当我们上报事件以后还需要使用 input_sync 函数来告诉 Linux 内核 input 子系统上报结束,input_sync 函数本质是上报一个同步事件,此函数原型如下所示:

void input_sync(struct input_dev *dev)