linux 内核 --- input 子系统

为什么要用INPUT子系统

不采用input子系统,如果自己实现按键字符驱动,需要自己申请设备号,注册字符设备,实现file_operations接口,创建设备节点,实现阻塞、非阻塞、复用IO、异步通知,INPUT 子系统还统一定义了不同输入设备上报数据的格式。

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

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

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

INPUT子系统框架

输入子系统主要由三部分构成: 核心层(input.c)、事件处理层(evdev.c, joydev.c, mousedev.c 等等)、设备驱动层(具体硬件设备相关的驱动,比如按键驱动,触摸屏驱动等等 ) 

一个输入事件从产生到应用捕获的流程是: 产生事件(鼠标移动,触摸点击,按键等) -> 设备驱动捕获事件(通过硬件中断响应) , 上报事件(input_evnet) -> 事件处理层获取到事件, 上报用户 -> 用户获取事件进行逻辑处理。

事件处理层和设备层通过中间核心层接口进行注册和匹配,一个设备驱动可以匹配到多个事件处理层,产生的事件会上报到匹配到的事件处理层。

 

一个 struct input_handler 实例称为一个事件处理,不同的输入设备有不同的事件处理,比如按键、鼠标,INPUT 子系统也提供了一个能匹配所有设备驱动的事件处理,所以一个设备驱动至少对应一个事件处理。

一个输入设备的驱动(platform总线设备和设备驱动匹配后执行probe(),创建并注册 struct input_dev 实例,实现中断代码调用INPUT子系统提供的上报函数上报事件)称为设备驱动;设备驱动匹配到对应的事件驱动后,调用此事件处理的代码创建设备节点等操作。

INPUT子系统初始化

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 类型设备使用。

核心层 - input.c

这一层是linux内核实现的的一些通用的接口,可以向事件处理层和设备驱动层提供一些公用函数

  • input_register_handler /input_unregister_handler -> 向事件处理层提供注册/释放一个hander的接口
  • input_register_device /input_unregister_device -> 向设备驱动层提供注册/释放设备的接口(设备驱动程序会用到这两个接口来注册input设备)
  • input_register_handle/input_unregister_handle -> 设备和事件匹配成功后会注册一个handle,通过这个handle来绑定上面的handler和device
  • input_event -> 上报一个输入事件,由device驱动层调用上报到应用端去处理(设备驱动程序会调用该接口来上报事件,比如按键,鼠标等等)

以上就是input 子系统常用的接口,更多接口可阅读源码(driver\input\input.c)

事件处理层 - 各种 struct input_handler 实例注册

内核一共定义了如下几个事件处理类型

#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) // 决定了事件类型的最大数量

事件处理 - evdev 

内核在事件处理层中实现了一个输入设备通用的事件驱动,即evdev,对应代码driver/input/evdev.c。无论是按键、触摸屏还是鼠标,都可以通过evdev进行输入事件的处理。

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

module_init(evdev_init);
module_exit(evdev_exit);

evdev_init()为链表 input_handler_list 填充了一个节点,设备驱动调用 input_register_device(),如果设备驱动和evdev这个事件处理匹配,执行函数 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,
};

如果用户空间读取的是evdev提供的设备节点eventtn,则上报的是一个未经处理的通用于所有输入设备的事件数据,格式如下:

事件处理 - mousedev - 对应的 event type 是 EV_MSC

static struct input_handler mousedev_handler = {
    .event        = mousedev_event,
    .connect    = mousedev_connect,
    .disconnect    = mousedev_disconnect,
    .legacy_minors    = true,
    .minor        = MOUSEDEV_MINOR_BASE,
    .name        = "mousedev",
    .id_table    = mousedev_ids,
};

mousedev会对输入事件数据进行处理从而上报的是鼠标特有的事件,格式如下:

事件处理 - event type 是 EV_SYN

同步事件用来表示什么时候数据读取完成。读取完成后应用层会收到一个同步事件数据,如下0代表同步事件,3代表EV_ABS事件

设备驱动层

这一层就是具体的驱动程序,这一层就是需要我们自己来实现的。我们先分配一个input_dev结构指针,然后可以调用input_register_device 函数来注册一个input_device,同时在注册的时候也会去扫描有没有匹配的handler, 如果有匹配的,handler.connect 就会被调用。注册一个input device的具体步骤如下:

  • 分配一个input_dev -- input_allocate_device
  • 初始化input_dev成员变量
  • 做一些硬件相关的操作,比如中断申请等
  • 注册一个input_dev -- input_register_device

注册 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); /* 设置事件类型,对应input_event.type */
    __set_bit(EV_REP, inputdev->evbit); /* 重复事件 */
    __set_bit(KEY_0, inputdev->keybit); /* 设置哪个按键产生事件,对应input_event.code */

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创建并添加到内核、设备的创建并添加到内核

向内核注册输入设备的关键是向链表 input_dev_list 添加输入设备struct input_dev

上报输入事件

当我们向 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)

驱动实例

 
#include <linux/device.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/timer.h>
 
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <asm/current.h>
#include <asm/io.h>
 
 
static struct input_dev *input_key_dev;
 
struct input_gpio_key{
    int m_gpio;   //引脚号
    struct gpio_desc * m_gpiod;
    int m_irq;
    int m_flag;
    int m_code;
    struct timer_list m_timer;
} ;
 
static struct input_gpio_key * input_keys_gpio;
 
static irqreturn_t input_key_irq(int irq, void *dev_id)
{
     struct input_gpio_key * key_desc = (struct input_gpio_key *)dev_id;
     printk(KERN_INFO "irq:key_desc->m_code=%d\n", key_desc->m_code);
     mod_timer(&key_desc->m_timer, jiffies+HZ/100);
     return IRQ_RETVAL(IRQ_HANDLED);
}
 
 
void  input_key_timer_fn(unsigned long  arg)
{
    int val;
    struct input_gpio_key * key_desc = (struct input_gpio_key *)arg;
    val = gpiod_get_value(key_desc->m_gpiod);
 
    /* 上报按键值 0: 按下 1: 抬起     */
    input_report_key(input_key_dev, key_desc->m_code, val);
    input_sync(input_key_dev);
 
}
 
static int input_key_probe(struct platform_device *pdev)
{
    int retval = 0;
    int  err, i;
    int count;
    enum of_gpio_flags flags;
    struct device_node * node = pdev->dev.of_node;
    count = of_gpio_count(node);                         //获取设备节点下 gpios属性有几个gpio
    if(!count){
        retval = -1;
        goto exit_entry;
    }
    input_keys_gpio = kmalloc(sizeof(struct input_gpio_key) * count, GFP_KERNEL);
    if(!input_keys_gpio){
        retval = -1;
        goto exit_entry;
    }
    for(i = 0; i < count; i++){
        input_keys_gpio[i].m_gpio = of_get_gpio_flags(node, i, &flags);
        input_keys_gpio[i].m_gpiod = gpio_to_desc(input_keys_gpio[i].m_gpio); //根据gpio引脚转换成gpio desc
        input_keys_gpio[i].m_irq = gpiod_to_irq(input_keys_gpio[i].m_gpiod);  //获取gpio中断号
        input_keys_gpio[i].m_flag = flags & OF_GPIO_ACTIVE_LOW;       //只获取低电平触发中断的标志
        input_keys_gpio[i].m_code = KEY_1+i;  //user key1,2
        setup_timer(&input_keys_gpio[i].m_timer, input_key_timer_fn, (unsigned long)&input_keys_gpio[i]);
        add_timer(&input_keys_gpio[i].m_timer);
    }
 
    //申请中断
    for(i = 0; i < count; i++){
        err = request_irq(input_keys_gpio[i].m_irq, input_key_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "input_gpio_key", &input_keys_gpio[i]);
    }
    
    input_key_dev = input_allocate_device(); //分配一个 input_dev 结构体
    if(input_key_dev == NULL){
        printk(KERN_ERR "input_allocate_device filed");
        retval = -1;
        goto exit_entry;
    }
    input_key_dev->name = "input_key_dev";
 
    //设置支持的输入事件类型
    set_bit(EV_KEY, input_key_dev->evbit);
    //set_bit(EV_LED, input_key_dev->evbit);
 
    //设置key支持的事件码(支持哪些按键)
    set_bit(KEY_1, input_key_dev->keybit);
    set_bit(KEY_2, input_key_dev->keybit);
    
 
    //注册一个输入设备
    retval = input_register_device(input_key_dev);
 
exit_entry:
    return retval;
}
 
static int input_key_remove(struct platform_device *pdev)
{
    int retval = 0;
    int count, i;
 
    count = of_gpio_count(pdev->dev.of_node);
    for(i = 0 ; i < count; i++){
        free_irq(input_keys_gpio[i].m_irq, &input_keys_gpio[i]);
        del_timer(&input_keys_gpio[i].m_timer);
    }
    kfree(input_keys_gpio);
    
    if(input_key_dev != NULL){
        input_unregister_device(input_key_dev);
        input_free_device(input_key_dev);
        input_key_dev = NULL;
    }
 
    return retval;
}
 
 
static const struct of_device_id    input_key_table[] = {
    {.compatible = "100ask,gpio_key"},
    { },
};
 
 
static struct platform_driver input_key_driver = {
    .probe        = input_key_probe,
    .remove        = input_key_remove,
    .driver        = {
        .name    = "InputKey",    //会根据这个名字去匹配device
        .of_match_table = input_key_table,
    },
};
 
static int __init input_key_init(void)
{
    platform_driver_register(&input_key_driver);  //注册platfrom 驱动
    return 0;
}
 
 
static void __exit input_key_exit(void)
{
    platform_driver_unregister(&input_key_driver);    
}
 
 
module_init(input_key_init);
module_exit(input_key_exit);
MODULE_LICENSE("GPL");
 
 

 

input_dev、input_handler 以及 input_handle之间的关系

 

注册input_handler的时候会去扫描input_dev_list 链表中有没有匹配的input_dev。同时在注册input_dev的时候也会去扫描input_handler_list 链表查找是否有与之匹配的input_handler 。如果input_device 和 input_handler有匹配成功的,input_handler ->connect函数就会调用。

connect 函数里面会为input_device 分配一个minor(次设备号),并且注册一个handle,将input_dev 和 input_handler 地址分别赋值给 handle.dev 和 handle.handler,通过一个handle将input_dev和input_handler 绑定起来。

input_register_handle 调用时,将handle->d_node 添加到 input_dev->h_list, 将handle->h_node 添加到input_handler->h_list中,这样在input_dev中就能通过input_dev->h_list 找到input_handle, 然后根据input_handle->handler成员找到对应的事件处理层。同理也可以通过input_handler ->h_list 找到input_handle, 然后再通过input_handle->dev 成员找到对应的input_dev。至此input_dev和input_handler就能通过一个input_handle 一一对应起来。设备和事件处理层就连同通了,事件处理层上面的就是用户层。

应用层

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

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

time:事件产生的时间

type:event type 事件类型,如果是按键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) // 决定了事件类型的最大数量

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

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

应用代码实例:

static struct input_event inputevent; 

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

查看 input 设备信息

 读取 input 设备输入信息

hexdump /dev/input/event0

 

查看 input 设备信息

cat /proc/bus/input/devices

 

posted @ 2023-03-16 23:18  流水灯  阅读(609)  评论(0编辑  收藏  举报