Linux input 子系统范例和基本函数解析

  输入子系统是为了将输入设备的功能呈现给应用程序。

  它支持 鼠标、键盘、蜂鸣器、触摸屏、传感器等需要不断上报数据的设备。

  简单的例子

  这个例子中的设备只有一个按键key,当key按下时,将产生中断,内核检测到中断并对其进行处理。

#include  

#include  

static struct input_dev *button_dev;    /*输入设备结构体*/  

/*中断处理函数*/  

staTIc irqreturn_t button_interrupt(int irq, void *dummy)                                                       

{  

    /*向输入子系统报告产生按键事件*/  

    input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);     

    /*通知接收者,一个报告发送完毕*/  

    input_sync(button_dev);             

    return IRQ_HANDLED;  //?

}  

/*加载函数*/  

staTIc int __init button_init(void)

{  

    int error;  

    /*申请中断处理函数*/ //返回0表示成功,返回-INVAL表示无效

    if(request_irq(BUTTON_IRQ,button_interrupt,0,"button",NULL))

    {  

        /*申请失败,则打印出错信息*/  

        printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);  

        return -EBUSY;  

    }  

    /*分配一个设备结构体*/ 

    //将在 sys/class/input/input-n 下面创建设备属性文件

    button_dev = input_allocate_device();     

    if (!button_dev)                        /*判断分配是否成功*/  

    {  

        printk(KERN_ERR "button.c: Not enough memory\n");  

        error = -ENOMEM;  

        goto err_free_irq;  

    }  

    button_dev->evbit[0] = BIT_MASK(EV_KEY);    /*设置按键信息*/  

    button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);  

    error = input_register_device(button_dev);  /*注册一个输入设备*/  

    if (error)  

    {  

        printk(KERN_ERR "button.c: Failed to register device\n");  

        goto err_free_dev;  

    }  

    return 0;  

    err_free_dev:                               /*以下是错误处理*/  

        input_free_device(button_dev);  

    err_free_irq:  

       free_irq(BUTTON_IRQ, button_interrupt);  

    return error;  

}  

staTIc void __exit button_exit(void)            /*卸载函数*/  

{  

    input_unregister_device(button_dev);        /*注销按键设备*/  

    free_irq(BUTTON_IRQ, button_interrupt);     /*释放按键占用的中断线*/  

}  

module_init(button_init);  

module_exit(button_exit); 

  从这个简单的例子中可以看到。

  在初始化函数 button_init() 中注册了一个中断处理函数,然后调用 input_allocate_device() 函数分配了一个 input_dev 结构体,并调用 input_register_device() 对其进行注册。

  在中断处理函数 button_interrupt() 中,实例将接收到的按键信息上报给 input 子系统,从而通过 input子系统,向用户态程序提供按键输入信息。

  input 子系统的关键函数

  input_allocate_device()

  input_register_device()-》input_attach_handler()-》input_match_device()

  input_allocate_device()

  这个函数在内存中为输入设备结构体分配一个空间,并对其主要成员进行初始化。

  其代码如下
 

struct input_dev *input_allocate_device(void)  

{  

    struct input_dev *dev;  

    dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);              

    /*分配一个input_dev结构体,并初始化为0*/  

    if (dev) {  

        dev->dev.type = &input_dev_type;        /*初始化设备的类型*/  

        dev->dev.class = &input_class;          /*设置为输入设备类*/  

        device_initialize(&dev->dev);           /*初始化device结构*/  

        mutex_init(&dev->mutex);                /*初始化互斥锁*/  

        spin_lock_init(&dev->event_lock);       /*初始化事件自旋锁*/  

        INIT_LIST_HEAD(&dev->h_list);           /*初始化链表*/  

        INIT_LIST_HEAD(&dev->node);             /*初始化链表*/  

        __module_get(THIS_MODULE);              /*模块引用技术加1*/  

    }  

    return dev;  

--------------------- 

  其返回一个指向 input_dev 类型的指针,该结构体是一个输入设备结构体,包含了输入设备的相关信息(按键码、设备名、支持的事件)。

  input_register_device()

  这个函数是输入子系统核心(input core)提供的函数。它将input_dev 结构体注册到输入子系统核心中(input_dev 结构体必须由 input_allocate_device()函数来分配的)。

  如果函数注册失败,必须调用 input_free_device() 函数来释放分配的空间。

  如果函数注册成功,在卸载函数中应该调用 input_unregister_device() 函数来注销输入设备结构体。

  我们看一下函数原型:
 

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;

    //设置 input_dev 所支持的事件类型,由 evbit 成员来表示。具体类型在后面归纳。

    __set_bit(EV_SYN, dev->evbit);  

    //初始化 timer 定时器,用来处理重复点击按键。(去抖)

    init_timer(&dev->timer);  

    //如果 rep[REP_DELAY] 和 [REP_PERIOD] 没有设值,则赋默认值。为了去抖。

    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;  //设置指定位置键值

    //设置 input_dev 中 device 的名字为 inputN

    //将如 input0 input1 input2 出现在 sysfs 文件系统中

    dev_set_name(&dev->dev, "input%ld",(unsigned long) atomic_inc_return(&input_no) - 1);  

    //将 input->dev 包含的 device 结构注册到 Linux 设备模型中。

    //并在文件系统中表现出来

    error = device_add(&dev->dev);  

    if (error)  

        return error;  

    //打印设备的路径并输出调试信息

    path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);  

    printk(KERN_INFO "input: %s as %s\n",  

        dev->name ? dev->name : 

            "Unspecified device" , path ? : "N/A");  

    kfree(path);  

    error = mutex_lock_interruptible(&input_mutex);  

    if (error) {  

       device_del(&dev->dev);  

       return error;  

    }  

    //将 input_dev 加入 input_dev_list 链表中(这个链表中包含有所有 input 设备)

    list_add_tail(&dev->node, &input_dev_list);  

    list_for_each_entry(handler, &input_handler_list, node);

    //调用 input_attatch_handler()函数匹配 handler 和 input_dev。

    //这个函数很重要,在后面单独分析。

    input_attach_handler(dev, handler);  

    input_wakeup_procfs_readers();  

    mutex_unlock(&input_mutex);  

    return 0;  

 

给 evbit 设置的,input_dev所支持的事件类型:

#define EV_SYN          0x00    /*表示设备支持所有的事件*/  

#define EV_KEY          0x01    /*键盘或者按键,表示一个键码*/  

#define EV_REL          0x02    /*鼠标设备,表示一个相对的光标位置结果*/  

#define EV_ABS          0x03    /*手写板产生的值,其是一个绝对整数值*/  

#define EV_MSC          0x04    /*其他类型*/  

#define EV_LED          0x11    /*LED灯设备*/  

#define EV_SND          0x12    /*蜂鸣器,输入声音*/  

#define EV_REP          0x14    /*允许重复按键类型*/  

#define EV_PWR          0x16    /*电源管理事件*/ 

  input_attatch_handler()

  这个函数用来匹配 input_dev 和 handler,匹配成功才进行关联。

  函数代码如下
 

static int input_attach_handler(struct input_dev *dev, 

struct   input_handler *handler)  

{  

    // input_device_id 这个结构体表示设备的标识,存储了设备信息。

    const struct input_device_id *id;       /*输入设备的指针*/  

    int error;  

    //先判断 handler 的 blacklist 有无赋值,然后判断是否匹配

    //blacklist 是一个 input_device_id *类型,指向了一个表,表中存放的是该驱动程序应该忽略的设备

    if (handler->blacklist && input_match_device(handler->blacklist,        dev))  

        return -ENODEV;                     

    /*** 设备和处理函数之间的匹配 ***/

    //匹配 handler->id_table指向的列表中的设备 和 dev->id 数据

    id = input_match_device(handler->id_table, dev);  

    if (!id)  

        return -ENODEV;  

    //匹配成功则调用 handler->connect,连接 handler 和 input_dev

    error = handler->connect(handler, dev, id);/*连接设备和处理函数*/  

    if (error && error != -ENODEV)  

        printk(KERN_ERR  

            "input: failed to attach handler %s to device %s, "  

            "error: %d\n",  

            handler->name, kobject_name(&dev->dev.kobj), error);  

    return error;  

input_device_id 结构体的定义:

struct input_device_id {

    kernel_ulong_t flags;           /*标志信息*/  

    __u16 bustype;                  /*总线类型*/  

    __u16 vendor;                   /*制造商ID*/  

    __u16 product;                  /*产品ID*/  

    __u16 version;                  /*版本号*/  

    ...  

    kernel_ulong_t driver_info;     /*驱动额外的信息*/  

}; 

  input_match_device()

  这个函数用来将 input_dev 和 handler 进行匹配。

  handler-》id_table 中定义了其支持 input_dev 设备。

 

static const struct input_device_id *input_match_device(const struct  

    input_device_id *id,struct input_dev *dev)  

{  

    int i;  

    //匹配 id 和 dev->id 中的信息

    for (; id->flags || id->driver_info; id++) {  

        if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)  

            if (id->bustype != dev->id.bustype)  //总线类型

                continue;  

        if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)  

            if (id->vendor != dev->id.vendor)  //厂商信息

                continue;  

        if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)  

            if (id->product != dev->id.product)  //匹配设备号

               continue;  

        if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)  

            if (id->version != dev->id.version)  //匹配版本号

               continue;  

        MATCH_BIT(evbit,  EV_MAX);  

        MATCH_BIT(keybit, KEY_MAX);  

        MATCH_BIT(relbit, REL_MAX);  

        MATCH_BIT(absbit, ABS_MAX);  

        MATCH_BIT(mscbit, MSC_MAX);  

        MATCH_BIT(ledbit, LED_MAX);  

        MATCH_BIT(sndbit, SND_MAX);  

        MATCH_BIT(ffbit,  FF_MAX);  

        MATCH_BIT(swbit,  SW_MAX);  

        return id;  

    }  

    return NULL;  

 

在上面,只有 flags 中的信息匹配成功,或者 flags 没有定义才会调用下面。

#define MATCH_BIT(bit, max) \  

        for (i = 0; i < BITS_TO_LONGS(max); i++) \               if ((id->bit[i] & dev->bit[i]) != id->bit[i]) \  

                break; \  

        if (i != BITS_TO_LONGS(max)) \  

            continue; 

 

  从宏定义中可以看到,

  只有当 input device和input handler 的 ID 成员在 evbit、keybit、… swbit 项相同才会匹配成功。而且匹配的顺序是从evbit、keybit到swbit。只要有一项不同,就会循环到ID中的下一项进行比较。

  总结

  在 input 的分配和注册中,我们分析了四个函数。

  1. input_allocate_device 在内存中为输入设备结构体分配空间并进行初始化。

  2. input_register_device()-》input_attach_handler()-》input_match_device()

  input_register_device

  将input_dev 结构体注册到输入子系统核心中。主要操作是 初始化 input_dev 并将其 device_add 进 Linux 设备驱动模型中(在文件系统中创建 inputN 等文件);打印其路径;调用 input_attach_handler 匹配 handler 和 input_dev。

  input_attach_handler

  匹配 handler 和 input_dev。主要操作是,判断 dev 在不在 devices 的黑名单中,不在就 调用 input_match_device 进行匹配,成功就调用 handler-》connect 连接设备和处理函数。

  input_match_device

  真正的 将 input_dev 和 handler 进行匹配。主要操作是匹配 id 和 dev-》id 中的信息。包括 bustype、vendor、product、version 等;再匹配 evbit 事件类型、keybit 按键类型 等。

技术专区

  • Linux input 子系统范例和基本函数解析
  • Ubuntu系统Linux学习环境的配置方法
  • 嵌入式软件工程师职业规划必知
  • 关于嵌入式电能量采集系统的详细剖析
  • Xen Project在嵌入式和汽车应用中扩展功能
  • Linux input 子系统范例和基本函数解析已关闭评论
    A+
发布日期:2019年07月14日  所属分类:物联网