Linux驱动开发之输入子系统

2020-02-15

关键字:



Linux 中输入设备大致可分以下几种:

1、按键/键盘(keyboard)

2、鼠标(mouse)

3、触摸屏(touchscreen)

4、游戏杆(joystick)

输入子系统的目的是为了屏蔽众多输入设备在硬件上的差异化,使得在开发输入设备的程序时能更简单统一。输入子系统屏蔽差异的方式就是为各种输入设备与上层应用提供统一的编程接口。

Linux 输入子系统是一种编程框架,它可以自上而下分为以下几种层次:

1、应用层

2、input handler层:数据处理层

完成 fops 的动作,并将输入事件上报给用户。

负责创建文件节点,即 /dev/input/xxx

3、input core层:输入核心层/管理层

负责为来自输入设备层的事件分发给相应的数据处理层,它内部会维护两个链表,分别记录输入事件与注册数据处理层对象。

负责申请设备号,输入设备的设备号一般是13。

负责创建类。

4、input device层:输入设备层

负责初始化硬件。

负责将硬件的原始数据上报给核心层。

5、硬件层

如:mouse, touchscreen, keyboard, joystcik

在输入子系统的开发中,需要我们去实现的就应用层与输入设备层。其余部分内核已经实现好了,直接用就行。

下面贴出一个最简单的利用输入子系统来新增输入设备驱动的源码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/input.h>

struct input_dev *inputdev;

static int __init simple_input_init(void)
{
    //1、分配一个input device对象
    inputdev = input_allocate_device();

    //2、初始化input device对象
    __set_bit(EV_KEY, inputdev->evbit);//表示当前正在开发的设备能够产生按键数据。
    __set_bit(KEY_POWER, inputdev->keybit);//表示当前设备能够产生POWER按键事件。

    //3、注册input_device对象到输入核心层
    int ret = input_register_device(inputdev);

    return 0;
}

static void __exit simple_input_exit(void)
{
    input_unregister_device(inputdev);
    input_free_device(inputdev);
}

module_init(simple_input_init);
module_exit(simple_input_exit);
MODULE_LICENSE("GPL");

这段代码在运行以后会自动在 /dev/input 目录下新增一个 event* 的设备节点,且在 /sys/class/input 目录下也会新增一个 event* 的目录。像传统的驱动编写过程中的申请设备号、创建文件节点等工作会由内核自动帮我们去做。由此可见,通过输入子系统,我们在开发适配输入设备时会变得简单很多。

另外提一点:要想利用输入子系统框架来编写驱动,就必须保证输入子系统正确编进了系统内核镜像中。

输入核心层与数据处理层所对应的代码文件分别为:./drivers/input/input.c  ,  ./drivers/input/evdev.c 。

可以在 make menuconfig 中将它们开启编译,具体路径为:

make menuconfig

  Device Drivers

    Input device support

      Generic input layier -- 表示input.c

      Event interface  -- 表示evdev.c

在输入子系统中,用于在输入设备层向核心层上报数据的函数接口签名如下:

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

参数1 表示哪个input device上报的数据。

参数2 表示上报的是哪种类型的数据。

参数3 表示上报的具体数据是什么,比如是哪个按键。

参数4 表示值是什么。

需要注意的是,在调用完 input_event() 函数后一定要再调用 input_sync() 函数,否则上层是不会去查收设备层上报的事件的。

除了 input_event() 函数外,还有一个更细化的上报函数:

void input_report_key(struct input_dev *dev, int code, int value);

上层应用,即用户空间所读到的输入子系统上报的数据是一个统一格式的数据包,其结构体对象原型如下:

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

以下是一个示例程序,它包含驱动程序与上层应用程序。它用于演示适配开发板上一个按键的驱动与应用。

驱动源码如下:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_irq.h>

#include <asm/io.h>

#define GPX3_CON 0x11400c20

struct input_dev *inputdev;

int irq;
void *reg_base;

int get_irq_from_node()
{
    //获取到设备树中的按键节点。
    struct device_node *np = of_find_node_by_path("/key_int_node");

    return irq_of_parse_and_map(np, 0);//通过节点去获取到中断号码。
}

irqreturn_t input_key_irq_handler(int irq, void *devid)
{
    int val = readl(reg_base + 4) & (1<<2);//根据地址读出相应寄存器中的值。

    //因为应用了输入子系统,所以不再需要驱动自行去处理阻塞、队列等操作。只需将事件
    //上报给上层,框架会自动将数据继续上报上去。
    if(val)
    {
        //按键抬起。
        input_event(inputdev, EV_KEY, KEY_POWER, 0);
        input_sync(inputdev);//这句必须调,表示事件已经准备好,上层可以查收了。
    }
    else
    {
        //按键按下。
        input_event(inputdev, EV_KEY, KEY_POWER, 1);
        input_sync(inputdev);
    }

    return IRQ_HANDLED;
}

static int __init simple_input_init(void)
{
    //1、分配一个input device对象
    inputdev = input_allocate_device();

//添加设备信息,即 /sys/class/input/event*/device 下的name,phys,uniq,id。
      inputdev->name = "my simple input key button.";
      inputdev->phys = "key/input/input0";
      inputdev->uniq = "simple key0 for 4412";
      inputdev->id.bustype = BUS_HOST;
      inputdev->id.vendor = 0x1234;
      inputdev->id.product = 0x8888;
      inputdev->id.version = 0x00001;

//2、初始化input device对象
    __set_bit(EV_KEY, inputdev->evbit);//表示当前正在开发的设备能够产生按键数据。
    __set_bit(KEY_POWER, inputdev->keybit);//表示当前设备能够产生POWER按键事件。

/*
          解释一下上面两句代码。
          因为输入子系统中每个类型都是用一个长位来表示的,例如keybit中就有768个位,由24个long组成的数组来表示这768位。__set_bit()函数的原理就是将指定位位置成值1。它内部就是封装了一个long数组到长位的转换而已。
      */

//3、注册input_device对象到输入核心层
    int ret = input_register_device(inputdev);

    //拿到定义在dts中的按键中断号。
    irq = get_irq_from_node();
    //申请中断。
    ret = request_irq(irq, input_key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "key3_eint10", NULL);
    //地址映射。
    reg_base = ioremap(GPX3_CON, 8);

    return 0;
}

static void __exit simple_input_exit(void)
{
    iounmap(reg_base);
    free_irq(irq, NULL);
    input_unregister_device(inputdev);
    input_free_device(inputdev);
}

module_init(simple_input_init);
module_exit(simple_input_exit);
MODULE_LICENSE("GPL");

上层应用源码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

int main()
{
    int fd = open("/dev/event1", O_RDWR);
    struct input_event event;

    int ret;
    while(1)
    {
        ret = read(fd, event, sizeof(struct input_event));//驱动层会自动阻塞。

        if(event.type == EV_KEY)
        {
            if(event.code == KEY_POWER)
            {
                if(event.value)
                {
                    printf("key down.\n");
                }
                else
                {
                    printf("key up.\n");
                }
            }
        }
    }

    return 0;
}

通过这个示例可以发现,在有了输入子系统的辅助以后,开发驱动已经简单了很多很多。

接下来我们来了解一下输入子系统的实现原理。

首先来看看 input device 初始化的过程。

input device 被抽象成 input_dev 结构体,这个结构体本身非常庞大,但同样我们仅需要了解其中一小部分即可,input_dev 结构体的部分成员定义如下:

struct input_dev {
    //以下四个成员就是 /sys/class/input/event*/device 目录下的 name, phys, uniq, id。用户可以通过查看这几个成员记载的信息来了解当前设备。
    const char *name; // sysfs中显示出来给用户看的信息
    const char *phys;
    const char *uniq;
    struct input_id id;

    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];//一个位表,该位用于描述一个输入设备能够产生什么类型的数据,如按键、坐标值等,常见的数据类型在下方有说明。
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//表示能够产生哪种按键,如KEY_POWER,KEY_ENTER等。默认能够表示768位数据,直接用24个long来表示。
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)];//表示能够产生哪种相对坐标数据。
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];//表示能够产生哪种绝对坐标数据。
    unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
    unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
    unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
    unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

    struct device dev;//继承device结构体对象,面向对象的编程思想。
    struct list_head h_list;//
    struct list_head node;//表示节点。
};

常用的事件类型定义如下:

#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  //LED指示数据
#define EV_SND    0x12  //声音数据

如何去驱动多个按键呢?

首先我们要将驱动的按键信息先描述在设备树文件中:

key_int_node{
    compatible = "test_key";
    #address-cells = <1>; //表示reg的地址长度占1个
    #size-cells = <1>;//表示reg的长度的长度占1个。

    [email protected]0{
        key_name = "key2_power_eint";
        key_code = <116>;
        gpio = <&gpx1 1 0>; //表示GPX1_1
        reg = <0x11000c20 0x18>; //第1个是地址,第2个是长度。
        interrupt-parent = <&gpx1>;
        interrupts = <1 0>;
    };

    [email protected]1{
        key_name = "key3_vup_eint";
        key_code = <115>;
        gpio = <&gpx1 2 0>;
        reg = <0x11000c20 0x18>;
        interrupt-parent = <&gpx1>;
        interrupts = <2 0>;
    };

    [email protected]2{
        key_name = "key4_vdown_eint";
        key_code = <114>;
        gpio = <&gpx3 2 0>;
        reg = <0x11000c20 0x18>;
        interrupt-parent = <&gpx3>;
        interrupts = <2 0>;
    };
};

然后要在代码当中获取这个设备树节点的所有信息,因为节点内部又有子节点,因此要用到以下函数来获取:

struct device_node *of_get_next_child(const struct device_node *parent_node, struct device_node *prev_node);

以下贴出利用输入子系统驱动多个按键的源码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>

#include <asm/io.h>

#define KEY_NUMS 3

//设计一个对象用来保存按键节点信息。
struct key_desc{
    int irqno;
    int key_code;
    int gpionum;
    void *reg_base;
    struct device_node *node;//可以随时获取到节点的各个信息。
};

struct key_desc all_key[KEY_NUMS];
struct input_dev *inputdev;

void get_all_child_from_node()
{
    //获取到设备树中的按键节点。
    struct device_node *np = of_find_node_by_path("/key_int_node");

    struct device_node *child;
    struct device_node *prevnode = NULL;

    int idx = 0;

    do{
        //获取到其中一个子节点。
        child = of_get_next_chile(np, prevnode);
        if(child != NULL)
        {
            /*
            char *key_name;
            u32 code;
            int gpionum;
            int irq;

            irq = irq_of_parse_and_map(child, 0);//通过节点去获取到中断号码。
            of_property_read_string(child, "key_name", &key_name);
            of_property_read_u32(child, "key_code", &code);
            gpionum = of_get_named_gpio(child, "gpio", 0);//读取自定义名称的GPIO值。官方定义的名称应该是gpios,但我们的是gpio。第3个参数是读第几个元素,这里我们只有一个,所以是0。
            printk("name:%s,code:%d,gpionum:%d,irq:%d\n", key_name, code, gpionum, irq);
            */
            all_key[idx++].node = child;
        }
        else
        {
            break;
        }

        prevnode = child;
    }while(1);
}

irqreturn_t input_key_irq_handler(int irq, void *devid)
{
    struct key_desc *pdesc = (struct key_desc *)devid;

    int gpionum = of_get_named_gpio(pdesc->node, "gpio", 0);
    int value = gpio_get_value(gpionum);//有了这个函数就可以不用读寄存器了。

    if(value)
    {
        //按键抬起。
        input_event(inputdev, EV_KEY, pdesc->key_code, 0);
        input_sync(inputdev);//这句必须调,表示事件已经准备好,上层可以查收了。
    }
    else
    {
        //按键按下。
        input_event(inputdev, EV_KEY, pdesc->key_code, 1);
        input_sync(inputdev);
    }

    return IRQ_HANDLED;
}

static int __init simple_input_init(void)
{
    //1、分配一个input device对象
    inputdev = input_allocate_device();

    //添加设备信息,即 /sys/class/input/event*/device 下的name,phys,uniq,id。
    inputdev->name = "my simple input key button.";
    inputdev->phys = "key/input/input0";
    inputdev->uniq = "simple key0 for 4412";
    inputdev->id.bustype = BUS_HOST;
    inputdev->id.vendor = 0x1234;
    inputdev->id.product = 0x8888;
    inputdev->id.version = 0x00001;

    get_all_child_from_node();

    //2、初始化input device对象
    __set_bit(EV_KEY, inputdev->evbit);//表示当前正在开发的设备能够产生按键数据。

    int i;
    for(i = 0; i < KEY_NUMS; i++)
    {
        //设置keybit,表示我们支持哪些按键。
        //按键值从设备树中来。
        int code = of_property_read_u32(all_key[i].node, "key_code", &code);
        __set_bit(code, inputdev->keybit);
        all_key[i].key_code = code;

        //申请中断。
        int irqno = irq_of_parse_and_map(all_key[i].node, 0);
        char *name;
        of_property_read_string(all_key[i], "key_name", &name);
        all_key[i].name = name;
        all_key[i].irqno = irqno;

        ret = request_irq(irqno, input_key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, name, &all_key[i]);//最后一个参数就是中断处理函数中的 void *devid,用于区分不同按键。
    }

    //3、注册input_device对象到输入核心层
    int ret = input_register_device(inputdev);

    return 0;
}

static void __exit simple_input_exit(void)
{
    int i;
    for(i = 0; i < KEY_NUMS; i++)
        free_irq(all_key[i}.irqno, &all_key[i]);

    input_unregister_device(inputdev);
    input_free_device(inputdev);
}

module_init(simple_input_init);
module_exit(simple_input_exit);
MODULE_LICENSE("GPL");

关于Linux的输入子系统,还有待于去多上机编写练习以及在对使用有了一定的掌握以后去阅读其源码实现才能真正掌握其精髓。



Linux驱动开发之输入子系统

原文地址:https://www.cnblogs.com/chorm590/p/12311107.html

时间: 2024-07-29 18:08:38

Linux驱动开发之输入子系统的相关文章

Linux 驱动——Button8(输入子系统)

输入子系统由驱动层.输入子系统核心.事件处理层三部分组成.一个输入事件,如鼠标移动.键盘按下等通过Driver->Inputcore->Event handler->userspace的顺序到达用户控件的应用程序. 其中核心层提供一些设备层与事件层公用的函数,比如说注册函数.反注册函数.事件到来的处理函数等等:事件层其实在Linux内核里面已经帮我们写好了很多有关的事件:而设备层就跟我们新添加到输入系统的具体设备相关了.这里以JZ2440开发板上的4个按键作为输入子系统的按键:它定义的功

Linux驱动开发、22-USB子系统

USB子系统 USB(universal serial bus)总线:通用串行总线,是一种外部总线标准,用于规范电脑与外部设备的连接和通讯. USB1.0:1.5MB/S USB1.1(full speed):12MB/S USB2.0(high speed):480MB/S USB3.0(supper sped):4800MB/S USB硬件结构(4线):电源(5V,500mA),地线,D+,D- 工作原理: USB端口的D+.D-数据线上有15K左右的"高值"下拉电阻,从而使USB

linux驱动开发重点关注内容--摘自《嵌入式Linux驱动模板精讲与项目实践》

本文摘自本人拙著 <嵌入式Linux驱动模板精讲与项目实践> 初步看起来Linux设备驱动开发涉及内容非常多,而须要实现驱动的设备千差万别.事实上做一段时间驱动之后回首看来主要就是下面几点: (1)对驱动进行分类.先归纳为哪个类型的驱动.归类正确再利用内核提供的子系统进行开发,往往会发现事实上非常多通用的事情内核已经帮我们做了,一个优秀的驱动project师应该最大程度上利用内核的资源.内核已经实现的毕竟稳定性强.可移植性高. (2)找到内核的提供的子系统.接下来就是要制作该子系统对该类设备提

驱动编程思想之初体验 --------------- 嵌入式linux驱动开发之点亮LED

这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的住,不然真像一些人说的,学了一年嵌入式感觉还没找到门. 不能再扯了,涉及到linux的驱动开发知识面灰常广,再扯文章就会变得灰常长.首先还是回到led驱动的本身上,自从linux被移植到arm上后,做驱动开发的硬件知识要求有所降低,很多都回归到了软件上,这是系统编程的一大特点,当然 ,也不排除有很多

嵌入式linux驱动开发之点亮led未遂(驱动编程思想之初体验)

有了上两篇文章的基础,我们就可以开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的住,不然真像一些人说的,学了一年嵌入式感觉还没找到门. 另外实践很重要,一年多以前就知道了arm,那时整天用单片机的思维去yy着arm,直到前段时间弄来一块arm板,烧上linux系统后才知道,坑呀!根本不是那回事,所以实践是学习计算机类最重要的基本素质,如果整天看书,那基本上

linux 驱动开发-模块的构建

1.模块的含义 linux 是采用模块化的方式构建的,允许内核在运行时动态地向其中插入或从中删除代码,这些代码(包扩函数,数据,模块入口函数,模块出口函数)被一并组合 在一个单独的二进制镜像,就是所谓的可装载内核模块. 模块可以是基本的内核镜像尽可能小,同时可以方便地对新功能进行调试,还可以实现热插拔(后续会学习如何实现设备的热插拔功能,暂时无需深究),和内核的核心子系统不一样,模块文件需要有入口点和出口点. 模块与应用程序的区别: a.模块和库函数类似,一个模块通常包含若干函数和数据,每个函数

触摸屏驱动三部曲之输入子系统

一.触摸屏系统框架 1.框架代码(具体细节处理,见下节代码) #include <linux/errno.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/input.h> #include <linux/init.h> #include <linux/serio.h> #includ

Linux驱动开发之 三 (那些必须要了解的硬件知识 之 存储器篇)

Linux驱动开发之 三 (那些必须要了解的硬件知识 之 存储器篇) 本文重点学习存储器相关的基本知识,网络上对RAM,ROM,FLASH等有非常详细的介绍,老谢将这些知识点摘抄整理并加以注释如下.这个整理的过程也是加深记忆的过程. 1.什么是内存 在计算机的组成结构中,有一个很重要的部分,就是存储器.存储器是用来存储程序和数据的部件,对于计算机来说,有了存储器,才有记忆功能,才能保证正常工作.存储器的种类很多,按其用途可分为主存储器和辅助存储器,主存储器又称内存储器(简称内存),辅助存储器又称

Linux驱动开发之 六 (那些必须要了解的硬件知识 之 仪器篇)

Linux驱动开发之 六 (那些必须要了解的硬件知识 之 仪器篇) 一.前言 在之前的文章中,老谢已经分享了不少关于嵌入式系统开发过程中必须要了解的硬件知识.作为这一小节的结束(哎呀,终于要结束了),老谢还想和大家聊聊"仪器".本文中老谢不聊仪器的具体使用方法.原理等.只想聊点轻松的,老谢结合自己的实际工作,以图文结合的方式,简单聊聊工作中使用到的仪器. 实话实说,前几篇文章基本无技术含量,包括本文.老谢坚持写这几篇的用意是: Linux驱动开发,必须以此为基础: 通过写博客的方式,让