Linux学习笔记(17.12)—— 基于输入子系统的按键驱动程序

​ Linux输入设备种类繁杂,常见的包括触摸屏、键盘、鼠标、摇杆等;这些输入设备属于字符设备,而linux将这些设备的共同特性抽象出来,Linux input 输入子系统就产生了。

1. 软件框架

input输入子系统是一组驱动程序的集合,旨在支持Linux下的所有输入设备。大多数驱动程序位于drivers/input中,但也有相当一部分位于drivers/hiddrivers/platform中。

​ 输入子系统的核心是输入模块,它必须在任何其他输入模块之前加载——它作为两组模块之间的通信方式:

1.1 设备驱动 device drivers

​ 这些模块与硬件通信(例如通过USB),并向输入模块提供事件(击键、鼠标移动)。

1.2 事件处理程序 Event handlers

​ 这些模块从输入核心获取事件,并通过各种接口将它们传递到需要的地方——敲击按键到内核,鼠标通过模拟PS/2接口移动到GPM和X,等等。

对于最常见的配置,一个USB鼠标和一个USB键盘,你必须加载以下模块(或将它们内置到内核中):

input
mousedev
usbcore
uhci_hcd或ohci_hcd或ehci_hcd 
usbhid
hid_generic

​ 在此之后,USB键盘将直接工作,USB鼠标将作为一个字符设备,其主设备号为13,次设备号为63:

crw-rw---- 1 root input 13, 63 4月   9 15:22 /dev/input/mice

​ 该设备通常由系统自动创建。

1.3 具体说明

1.3.1 事件处理程序 Event handlers

根据需要,事件处理程序将事件从设备分发到用户空间和内核消费者。

  • evdev

    evdev是通用的输入事件接口。它将内核中生成的事件直接传递给程序,并带有时间戳。事件代码在所有架构上都是相同的,并且是硬件独立的。

    这是用户空间使用用户输入的首选接口,并且鼓励所有客户端使用它。

    有关API的注释,请参阅事件接口。

    设备在/dev/input:

    crw-rw----  1 root input 13, 65 4月   9 15:22 event1
    crw-rw----  1 root input 13, 66 4月   9 15:22 event2
    crw-rw----  1 root input 13, 67 4月   9 15:22 event3
    crw-rw----  1 root input 13, 68 4月   9 15:22 event4
    crw-rw----  1 root input 13, 63 4月   9 15:22 mice
    crw-rw----  1 root input 13, 32 4月   9 15:22 mouse0
    crw-rw----  1 root input 13, 33 4月   9 15:22 mouse1
    

    次设备号范围有两个:从64到95是静态传统。如果一个系统中有32个以上的输入设备,则会创建额外的evdev节点,子节点从256开始。

  • keyboard

    键盘是内核输入处理程序,是VT代码的一部分。它使用键盘敲击事件作为VT控制台处理用户的输入。

  • mousedev

    mousedev是一种让使用鼠标输入的传统程序工作的方法。它可以从鼠标或数字化设备/平板电脑中获取事件,并为用户提供PS/2风格的鼠标设备。
    /dev/input中的mousedev设备(如上所示)包括:

    crw-r--r--   1 root     root      13,  32 Mar 28 22:45 mouse0
    crw-r--r--   1 root     root      13,  33 Mar 29 00:41 mouse1
    crw-r--r--   1 root     root      13,  34 Mar 29 00:41 mouse2
    crw-r--r--   1 root     root      13,  35 Apr  1 10:50 mouse3
    ...
    ...
    crw-r--r--   1 root     root      13,  62 Apr  1 10:50 mouse30
    crw-r--r--   1 root     root      13,  63 Apr  1 10:50 mice
    

    ​ 每个鼠标设备分配给一个鼠标或数字转换器,除了最后一个-mice。这个单字符设备由所有的鼠标和数字化设备共享,即使没有连接,设备也会出现。这对于热插拔USB鼠标是很有用的,所以旧的程序不处理热插拔可以打开设备,即使没有鼠标在场。

    ​ 内核配置中的CONFIG_INPUT_MOUSEDEV_SCREEN_[XY]是XFree86中屏幕的大小(以像素为单位)。如果你想在X中使用数字化仪,这是必要的,因为它的移动是通过虚拟PS/2鼠标发送到X的,因此需要相应地缩放。如果您只使用鼠标,则不会使用这些值。

    ​ Mousedev将生成PS/2, ImPS/2 (Microsoft IntelliMouse)或ExplorerPS/2 (IntelliMouse Explorer)协议,这取决于程序读取数据的愿望。您可以将GPM和X设置为其中任何一个。如果你想利用USB鼠标上的轮子和ExplorerPS/2,如果你想使用额外的(最多5个)按钮,你需要ImPS/2。

  • joydev

    joydev实现v0.x和v1.x Linux操纵杆API接口。详细信息请参见编程接口。

    只要任何操纵杆被连接,它就可以在/dev/input中访问:

    crw-r--r--   1 root     root      13,   0 Apr  1 10:50 js0
    crw-r--r--   1 root     root      13,   1 Apr  1 10:50 js1
    crw-r--r--   1 root     root      13,   2 Apr  1 10:50 js2
    crw-r--r--   1 root     root      13,   3 Apr  1 10:50 js3
    ...
    

    等等,直到js31在传统范围内,如果有更多的操纵杆设备,则额外的节点的次设备号大于256。

1.3.2 设备驱动device drivers

​ 设备驱动程序是生成事件的模块。

  • hid-generic

    hide-generic是整个套件中最大、最复杂的驱动程序之一。它处理所有的HID设备,因为有非常广泛的种类,而且因为USB HID规范不简单,它需要这么大。

    ​ 目前,它可以操作USB鼠标、操纵杆、手柄、方向盘、键盘、轨迹球和数码设备。

    ​ 然而,USB使用HID也用于监视器控制,扬声器控制,UPS, LCD和许多其他用途。显示器和扬声器控制应该很容易添加到hid/input接口,但对于UPS和LCD,它没有多大意义。为此,我们设计了hiddev接口。有关它的更多信息,请参阅人机界面设备的维护和保养。

    ​ usbhid模块的使用非常简单,它不需要参数,自动检测一切,当一个HID设备插入时,它会适时地检测它。

    ​ 然而,由于设备的差异很大,您可能碰巧有一个设备不能很好地工作。在这种情况下,#define DEBUG在hid-core.c的开头,并向我发送syslog跟踪信息。

  • usbmouse

    ​ 对于嵌入式系统,对于HID描述符损坏的鼠标,以及其他任何使用usbhid不是一个好的选择时,有usbmouse驱动程序。它只处理USB鼠标。它使用更简单的HIDBP协议。这也意味着鼠标必须支持这个更简单的协议。并不是所有的。如果您没有任何强烈的理由使用这个模块,那么请使用usbhid。

1.4 Event接口

​ 您可以使用阻塞和非阻塞读取,也可以在/dev/input/eventX设备上select(),这样在读取时总是会得到整个数量的输入事件。他们的结构是:

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

time是时间戳,它返回事件发生的时间。例如,Type是EV_REL表示相对移动,EV_KEY表示按键或释放。在include/uapi/linux/input-event-codes.h中定义了更多类型。

code是事件代码,例如REL_X或KEY_BACKSPACE,完整的列表在include/uapi/linux/input-event-codes.h中。

value是事件所携带的值。EV_REL的相对变化,EV_ABS的绝对新值(操纵杆…),或者EV_KEY释放为0,按键为1,自动重复为2。

​ 输入子系统是由设备驱动层(input driver)、输入核心层(input core)、输入事件处理层(input event handle)组成。如图所示:

在这里插入图片描述

  • input设备驱动层:负责具体的硬件设备,将底层的硬件输入转化为统一的事件形式,向input核心层和汇报;
  • input核心层:连接input设备驱动层与input事件处理层,向下提供驱动层的接口,向上提供事件处理层的接口;
  • input事件处理层:为不同硬件类型提供了用户访问以及处理接口,将硬件驱动层传来的事件报告给用户程序。

​ 在input子系统中,每个事件的发生都使用事件(type)->子事件(code)->值(value)所有的输入设备的主设备号都是13,input-core通过次设备来将输入设备进行分类,如0-31是游戏杆,32-63是鼠标(对应Mouse Handler)、64-95是事件设备(如触摸屏,对应Event Handler)。

2. Linux输入子系统支持的数据类型

三个数据结构input_devinput_handleinput_handler

input_dev:是硬件驱动层,代表一个input设备。

input_handle:属于核心层,代表一个配对的input设备与input事件处理器。连接input deviceinput handler.

input_handler:是事件处理层,代表一个事件处理器。input handler附加到input device并创建input_handle 可能同时有多个处理程序附加到任何给定的输入设备上。 它们都将获得设备生成的输入事件的副本。

同样的结构也用于实现输入过滤器。 输入核心input core允许过滤器首先运行,如果任何过滤器表明该事件应该被过滤(通过从其filter()方法返回%true),则不会将事件传递给常规处理程序。

input_dev 通过全局的input_dev_list链接在一起,设备注册的时候完成这个操作(事件处理器一般内核自带,不需要我们来写)。
在这里插入图片描述input_hande 没有一个全局的链表,它注册的时候将自己分别挂在了input_dev input_handlerh_list上了。通过input_devinput_handler就可以找到input_handle在设备注册和事件处理器,注册的时候都要进行配对工作,配对后就会实现链接。通过input_handle也可以找到input_devinput_handler

​ 可以看到,input_devinput_handler中都有一个h_list,而input_handle拥有指向input_devinput_handler的指针,也就是说input_handle是用来关联input_devinput_handler的。

那么为什么一个input_dev和input_handler中拥有的是h_list而不是一个handle呢? 因为一个input_dev可能对应多个handler,而一个handler也不能只处理一个device,比如说一个鼠标,它可以对应even handler,也可以对应mouse handler,因此当其注册时与系统中的handler进行匹配,就有可能产生两个实例,一个是evdev,另一个是mousedev,而任何一个实例中都只有一个handle。 至于以何种方式来传递事件,就由用户程序打开哪个实例来决定。后面一个情况很容易理解,一个事件驱动不能只为一个甚至一种设备服务,系统中可能有多种设备都能使用这类handler,比如event handler就可以匹配所有的设备。在input子系统中,有8种事件驱动,每种事件驱动最多可以对应32个设备,因此dev实例总数最多可以达到256个

3. 编写输入驱动程序

3.1 创建一个input设备驱动

button_drv.c文件中,

probe函数采用了先获取按键节点数量,然后分别读取gpio描述符并通过其获取为gpio和irq号,并申请注册中断服务程序。

/**
 * 文件    : button_drv.c
 * 作者    : glen  
 * 描述    : button driver文件
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/gpio_keys.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>
#include <linux/timer.h>

struct btn_dev {
    int  gpio;          /* button attach to gpiox */
    int  irq;           /* interrupt source of triggle by press button    */
    int  code;          /* button code: BTN_0, BTN_1, ... */
    int  idx;           /* index of button */
    int  nbr;           /* number of button */
    struct timer_list timer;
    struct gpio_desc *gpiod;
};

struct btn_dev   *pbtn_dev;
struct input_dev *pbtn_iptdev;

static irqreturn_t btn_input_dev_isr(int irq, void *dev_id)
{
    struct btn_dev *btndev = dev_id;
    mod_timer(&btndev->timer, jiffies + msecs_to_jiffies(10));
    return IRQ_HANDLED;
}

static void key_timer_expire(unsigned long data)
{
    int val;
    struct btn_dev *btndev = (struct btn_dev *)data;

    /* 读取按键的值 */
    val = gpiod_get_value(btndev->gpiod);
    /* printk("button%d value is %d\n", btndev->idx, val); */

    /* report data */
    input_event(pbtn_iptdev, EV_KEY, btndev->code, val);
    input_sync(pbtn_iptdev);
    
}

/* alloc/set/register platform_driver */
int btn_input_dev_probe (struct platform_device *pdev)
{
    struct device      *dev     = &pdev->dev;
    struct device_node *of_node = dev->of_node;
    // struct resource    *irq;    
    int error;
    int i, count;
    
    /* get hardware info from device tree 
     */
    /* get number of gpio button in device tree  */
    count = of_gpio_count(of_node);
    if (count <= 0) {
        printk("%s %s line %d, there isn't any gpio button available!\n", __FILE__, __FUNCTION__, __LINE__);
        return -EIO;
    }

    /* allocate memory space for pbtn_dev and inputdev_btn */
    pbtn_dev = kzalloc(sizeof(struct btn_dev) * count, GFP_KERNEL);
    if (pbtn_dev == NULL) {
        return -ENOMEM;
    }

    for (i = 0; i < count; i++) {
        pbtn_dev[i].gpiod = gpiod_get_index_optional(dev, NULL, i, GPIOD_ASIS);
        if (pbtn_dev[i].gpiod == NULL) {
            printk("%s %s line %d, get gpiod error!\n", __FILE__, __FUNCTION__, __LINE__);
            error = -EIO;
            goto err_alloc_mem;
        }

        /* create timer */
        setup_timer(&pbtn_dev[i].timer, key_timer_expire, (unsigned long)&pbtn_dev[i]);
        pbtn_dev[i].timer.expires = ~0;
        add_timer(&pbtn_dev[i].timer);

        pbtn_dev[i].gpio = desc_to_gpio(pbtn_dev[i].gpiod);
        pbtn_dev[i].irq =  gpio_to_irq(pbtn_dev[i].gpio);
        pbtn_dev[i].idx = i;
        pbtn_dev[i].nbr = count;
        pbtn_dev[i].code = BTN_0 + i;

        error = request_irq(pbtn_dev[i].irq, btn_input_dev_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "gbtn", &pbtn_dev[i]);
        if (error < 0) {
            printk("%s %s line%d, request irq failed!\n", __FILE__, __FUNCTION__, __LINE__);
            error = -EINVAL;
            goto err_request_irq;
        }
    }
    of_node->data = pbtn_dev;

    /* alloc/set/register input_dev */
    pbtn_iptdev = devm_input_allocate_device(dev);

    pbtn_iptdev->name = "glen,gbtn";
    pbtn_iptdev->phys = "glen,gbtn/input2";
    pbtn_iptdev->dev.parent = dev;

    pbtn_iptdev->id.bustype = BUS_HOST;
    pbtn_iptdev->id.vendor  = 0x0001;
    pbtn_iptdev->id.product = 0x0001;
    pbtn_iptdev->id.version = 0x0100;

    /* set 1: which type event? */
    __set_bit(EV_KEY, pbtn_iptdev->evbit);
    __set_bit(EV_ABS, pbtn_iptdev->evbit);

    /* set 2: which event? */
    for (i = 0; i < count; i++) 
        __set_bit(pbtn_dev[i].code, pbtn_iptdev->keybit);
    __set_bit(BTN_TOUCH,         pbtn_iptdev->keybit);
    __set_bit(ABS_MT_SLOT,       pbtn_iptdev->absbit);
    __set_bit(ABS_MT_POSITION_X, pbtn_iptdev->absbit);
    __set_bit(ABS_MT_POSITION_Y, pbtn_iptdev->absbit);

    /* set 3: event params? */
    input_set_abs_params(pbtn_iptdev, ABS_MT_POSITION_X, 0, 0xffff, 0, 0);
    input_set_abs_params(pbtn_iptdev, ABS_MT_POSITION_Y, 0, 0xffff, 0, 0);

    error = input_register_device(pbtn_iptdev);

    return 0;

err_request_irq:
err_alloc_mem:
    for (i = 0; i < count; i++) 
        kfree(&pbtn_dev[i]);
    return error;
}

/* platform_driver结构体的 remove成员函数实现 */
int btn_input_dev_remove(struct platform_device *pdev)
{
    int i;
    int count = pbtn_dev[0].nbr;

    for (i = 0; i < count; i++) {
        del_timer_sync(&pbtn_dev[i].timer);
        free_irq(pbtn_dev[i].irq, &pbtn_dev[i]);
        kfree(&pbtn_dev[i]);
    }
    // free_irq(, NULL);
    input_unregister_device(pbtn_iptdev);
    return 0;
}

/* 构造用于配置的设备属性 */
static const struct of_device_id btn_input_dev_of_match[] = {
    {.compatible = "glen,gbtn"},
    { },
};

/* 构造(初始化)file_operations结构体 */
static struct platform_driver btn_input_dev = {
    .driver = {
        .name = "gbtn",
        .of_match_table = btn_input_dev_of_match,
    },
    .probe = btn_input_dev_probe,
    .remove = btn_input_dev_remove,
};

/* 初始化 */
static int __init button_drv_init(void)
{
    return platform_driver_register(&btn_input_dev);
}
module_init(button_drv_init);

static void __exit button_drv_exit(void)
{
    platform_driver_unregister(&btn_input_dev);
}
module_exit(button_drv_exit);

/* insert author information for module */
MODULE_AUTHOR("glen");
/* insert license for module */
MODULE_LICENSE("GPL");
 

3.2 设备树文件(不作更改)

 		pinctrl_btn0:btn0 {
 			fsl,pins = <
 				MX6UL_PAD_UART1_CTS_B__GPIO1_IO18	0xF080	/* KEY0 */ 
 			>;
 		};
 
 		pinctrl_btn1:btn1 {
 			fsl,pins = <
                 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0xF080	/* KEY1  此按键不存在 */
 			>;
 		};
     /* 在根节点下添加基于pinctrl的gbtns设备节点 */
     gbtns {
         compatible = "glen,gbtn";
         #address-cells = <1>;
 
         pinctrl-names = "default";
         pinctrl-0 = <&pinctrl_btn0 
 		             &pinctrl_btn1>;
 
         gpio-controller;
         #gpio-cells = <2>;
         gpios = <&gpio1 18 GPIO_ACTIVE_LOW /* button0 */
                  &gpio1 3 GPIO_ACTIVE_LOW>;   /* button1 */
 
     };
 
  • 取消了gpios前缀“xxx-",相应地,在驱动程序用gpiod_get_index_optional函数获取gpio描述符时,第2个形参 ”const char *con_id“ 传递NULL即可;

  • 将pinctrl-0、gpios属性值由 “<>,<>;” 改为 “<>;",效果是一样的

3.3 应用程序

应用程序文件button_drv_test.c

/*
 * 文件名   :  button_drv_test.c
 * 作者     :  glen
 * 描述     :  button_drv应用程序
 */

#include "stdio.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include <signal.h>
#include <sys/select.h>
#include <sys/time.h>
#include <linux/input.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

static struct input_event iptevent;

/**
 * @brief   : main函数
 * @par     : argc  argv数组元素的个数
 *            argv  参数数组
 * @retval  : 0 成功    其它 失败
 */
int main(int argc, char *argv[])
{
    int fd;
    int ret = 0;
    char *filename;

    if (argc != 2) {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    /* 打开驱动文件 */
    fd = open(filename, O_RDWR);
    if (fd < 0) {
        printf("Can't open file %s\r\n", filename);
        return -1;
    }

    while (1) {
        ret = read(fd, &iptevent, sizeof(iptevent));
        if (ret > 0) {  /* read data successful */
            switch (iptevent.type) {
            case EV_KEY:
                if (iptevent.code < BTN_MISC) /* the value of keyboard */
                    printf("key%d %s \r\n", iptevent.code, iptevent.value ? "press" : "release");
                else 
                    printf("button%d %s \r\n", iptevent.code - BTN_0, iptevent.value ? "press" : "release");
                break;

            case EV_ABS:
                break;

            case EV_REL:
                break;

            default:
                break;
            }

        } else 
            printf("Read data failed!\n");
    }

    /* 关闭文件 */
    ret = close(fd);
    if (ret < 0) {
        printf("file %s close failed!\r\n", argv[1]);
        return -1;
    }
    return 0;
}

3.4 在alientek_linux_alpha开发板实测验证如下

/drv_module # hexdump /dev/input/event2
0000000 0551 0000 b77a 0004 0001 0100 0001 0000
0000010 0551 0000 b77a 0004 0000 0000 0000 0000
0000020 0551 0000 2856 0007 0001 0100 0000 0000
0000030 0551 0000 2856 0007 0000 0000 0000 0000
0000040 0552 0000 ec9c 000e 0001 0100 0001 0000
0000050 0552 0000 ec9c 000e 0000 0000 0000 0000
0000060 0553 0000 0a15 0001 0001 0100 0000 0000
0000070 0553 0000 0a15 0001 0000 0000 0000 0000
0000080 0553 0000 1ad1 0006 0001 0100 0001 0000
0000090 0553 0000 1ad1 0006 0000 0000 0000 0000
00000a0 0553 0000 7e46 0007 0001 0100 0000 0000
00000b0 0553 0000 7e46 0007 0000 0000 0000 0000
00000c0 0553 0000 a6d5 000a 0001 0100 0001 0000
00000d0 0553 0000 a6d5 000a 0000 0000 0000 0000
00000e0 0553 0000 7bae 000c 0001 0100 0000 0000


/drv_module # ./btn_drv_test /dev/input/event2
button0 press
button0 release
button0 press
button0 release
button0 press
button0 release
button0 press
button0 release
button0 press
button0 release
button0 press
button0 release
button0 press
button0 release
button0 press
button0 release
button0 press
button0 release
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值