一、linux驱动开发-11.1-INPUT子系统

一、前言

       linux内核专门做了一个input子系统的框架来处理输入实践。输入设备的本质还是字符设备,只是在其基础上套上了input框架。

二、子系统

2.1、简介

       input子系统就是管理输入的子系统,和pinctrl、gpio子系统一样,都是linux内核针对某一类设备而创建的框架,如下:

       驱动编写只需要关注中间的驱动层、核心层、事件层,分工如下:

       驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
       核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
       事件层:主要和用户空间进行交互

2.2、编写流程

       input 核心层会向 Linux 内核注册一个字符设备,在drivers/input/input.c 这个文件,input.c 就是 input 输入子系统的核心层。

        注册一个input类,系统启动后,就会在/sys/class目录下有一个input子目录。

       input子系统的所有设备主设备号都为13,因此在使用input子系统处理输入设备的时候就不需要注册字符设备了,只需要向系统注册一个input_device即可。

2.2.1、注册input_dev

2.2.1.1、申请

       input_dev表示input设备,定义在include/linux/input.h,使用input_allocate_device来申请。

/*
@return:申请到的input_dev
*/
struct input_dev *input_allocate_device(void)

       如果要注销input设备,使用input_free_device来释放前面申请到的input_dev

/*
@dev:需要释放的input_dev
*/
void input_free_device(struct input_dev *dev)

2.2.1.2、初始化

       需要初始化的内容主要为事件类型(evbit)和事件值(keybit)。

2.2.1.3、注册

       初始化完成后就需要向linux内核注册input_dev,

/*
@dev:要注册的input_dev

@return:0,成功
        <0,失败
*/
int input_register_device(struct input_dev *dev)

        成功注册input_dev设备后,就会在/dev/input目录下生成一个名为“eventX(X=0,1,,,n)”的文件,使用read函数读取这个文件。

       同样,注销驱动的时候也需要使用input_unregister_device函数来注销前面注册的input_dev,

/*
@dev:要注销的input_dev
*/
void input_unregister_device(struct input_dev *dev)

示例:

struct input_dev *inputdev;    //input结构体变量

static int __init xxx_init(void)
{
    //1、申请
    inputdev = input_allocate_device();     //申请input_dev
    inputdec->name = "test_input";          //设置input_dev名字

    //2、初始化
    /*********第一种设置事件和事件值的方法***********/
    __set_bit(EV_KEY, inputdev->evbit);     /* 设置产生按键事件 */
    __set_bit(EV_REP, inputdev->evbit);     /* 重复事件 */
    __set_bit(KEY_0, inputdev->keybit);     /*设置产生哪些按键值 */
    /************************************************/

    /*********第二种设置事件和事件值的方法***********/
    keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
                                     BIT_MASK(EV_REP);
    keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |=
                                     BIT_MASK(KEY_0);
    /************************************************/

    /*********第三种设置事件和事件值的方法***********/
    keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
                                     BIT_MASK(EV_REP);
    input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
    /************************************************/

    //3、注册
    input_register_device(inputdev);
}

static void __exit xxx_exit(void)
{
    //注销
    input_unregister_device(inputdev);

    //释放
    input_free_device(inputdev);
}

2.2.2、上报事件

       向内核注册input_dev后,还得将获取到的具体输入值或者输入事件,上报给内核,比如在按键中断,定时中断中将按键值上报给内核,这样内核才能获取输入值。

不同的事件,其上报事件的API函数不同,下面是常见的:

/*
@description:上报指定的事件以及对应的值

@dev:需要上报的input_dev
@type:上报事件的类型,比如EV_KEY
@code:事件码,也就是注册的按键值,比如key_0
@value:事件值,比如1表示按键按下,0表示松开
*/
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);
}

       还有其他事件上报函数:

void input_report_rel(struct input_dev *dev, unsigned int code, int value)
void input_report_abs(struct input_dev *dev, unsigned int code, int value)
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
void input_report_switch(struct input_dev *dev, unsigned int code, int value)
void input_mt_sync(struct input_dev *dev)

       上报事件以后还需要使用input_sync函数来告诉内核input子系统上报结束:

/*
@dev:需要上报同步事件的input_dev
*/
void input_sync(struct input_dev *dev)

示例如下:

/* 用于按键消抖的定时器服务函数 */
void timer_function(unsigned long arg)
{
    unsigned char value;

    value = gpio_get_value(keydesc->gpio); /*  读取 IO  值 */
    if(value == 0){ /* 按下按键 */
        /*  上报按键值 */
        input_report_key(inputdev, KEY_0, 1); /*  最后一个参数 1 , 按下 */
        input_sync(inputdev); /* 同步事件 */
    } else { /* 按键松开 */
        input_report_key(inputdev, KEY_0, 0); /*  最后一个参数 0 , 松开 */
        input_sync(inputdev); /* 同步事件 */
    }
}

2.3、input_event结构体

        Linux 内核使用 input_event 这个结构体来表示所有的输入事件,input_envent 结构体定义在include/uapi/linux/input.h 文件中,结构体内容如下

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

三、驱动编写

#include <linux/types.h>

#include <linux/module.h>

#include <linux/ide.h>

#include <linux/kernel.h>

#include <linux/init.h>

#include <linux/errno.h>

#include <linux/fs.h>

#include <linux/slab.h>

#include <linux/uaccess.h>

#include <linux/io.h>

#include <linux/cdev.h>

#include <linux/device.h>

#include <linux/of.h>

#include <linux/of_address.h>

#include <linux/of_gpio.h>

#include <linux/gpio.h>

#include <linux/of_irq.h>

#include <linux/irq.h>

#include <linux/semaphore.h>

#include <linux/timer.h>

#include <linux/io.h>

#include <linux/input.h>



#define IMX6UIRQ_CNT     	1     

#define IMX6UIRQ_NAME    	"keyinput"

#define KEY0VALUE			0X01

#define INVAKEY				0xFF 

#define KEY_NUM				1



struct irq_keydesc{

	int gpio;				//gpio编号

	unsigned int irqnum;	//中断号

	irqreturn_t (*irq_handler)(int, void*);		//中断处理函数

};



//设备结构体

struct imx6uirq_dev{

	dev_t devid;

	struct cdev cdev;

	struct class *class;

	struct device *device;

	int major;

	int minor;

	struct device_node *nd;		//设备节点

	atomic_t keyvalue;			//有效的按键值

	atomic_t releasekey;		//标记是否完成一次完整的按键

	struct timer_list timer;	//定时器

	struct irq_keydesc irq_keydesc[KEY_NUM];	//按键描述数组



	struct input_dev *inputdev;	//input

};



struct imx6uirq_dev imx6uirq;  



//定时器回调函数

static void timer_function(unsigned long arg)

{

	int value;

	struct irq_keydesc *keydesc;

	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

	keydesc = &dev->irq_keydesc[0];



	value = gpio_get_value(keydesc->gpio);



	if (value == 0)

	{

		input_report_key(dev->inputdev, KEY_0, 1);

		input_sync(dev->inputdev);

	}

	else {

		input_report_key(dev->inputdev, KEY_0, 0);

		input_sync(dev->inputdev);

	}

}



static irqreturn_t key0_irq_handler(int irq, void *dev_id)

{

	int value;

	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;



	dev->timer.data = (volatile long)dev_id;

	//启动定时器

	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));



	return IRQ_HANDLED;

}



static int key_io_init(void)

{

	int ret;

	//通过节点名字查找节点

	imx6uirq.nd = of_find_node_by_name(NULL, "gpiokey");

	if (imx6uirq.nd == NULL)

	{

		printk("find %s fail\r\n", "gpiokey");

		return -EINVAL;

	}



	//通过节点获取GPIO编号

	imx6uirq.irq_keydesc[0].gpio = of_get_named_gpio(imx6uirq.nd, "key-gpio", 0);

	if (imx6uirq.irq_keydesc[0].gpio < 0)

	{

		printk("get %s fail\r\n", "key-gpio");

		return -EINVAL;

	}

	//初始化key-gpio

	ret = gpio_request(imx6uirq.irq_keydesc[0].gpio, "gpio_irq");

	if (ret != 0)

	{

		printk("request %s fail\r\n", "gpio_irq");

		return -EINVAL;

	}

	ret = gpio_direction_input(imx6uirq.irq_keydesc[0].gpio);

	if (ret < 0)

	{

		printk("set key status fail\r\n");

		return -EINVAL;

	}



	//通过节点获取中断号

	imx6uirq.irq_keydesc[0].irqnum = irq_of_parse_and_map(imx6uirq.nd, 0);

#if 0

	//通过GPIO编号获取中断号

	imx6uirq.irq_keydesc[0].irqnum = gpio_to_irq(imx6uirq.irq_keydesc[0].gpio);

#endif

	//设置中断处理函数

	imx6uirq.irq_keydesc[0].irq_handler = key0_irq_handler;

		

	//通过中断号申请中断

	ret = request_irq(imx6uirq.irq_keydesc[0].irqnum,	

				imx6uirq.irq_keydesc[0].irq_handler,

				IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,

				IMX6UIRQ_NAME,

				&imx6uirq);	

	if (ret < 0)

	{

		printk("request irq fail\r\n");

		return -EINVAL;

	}



	//初始化定时器

	init_timer(&imx6uirq.timer);

	imx6uirq.timer.function = timer_function;

}



static int __init imx6uirq_init(void)

{

	int ret = 0;

	int val = 0;



	//按键gpio初始化

	key_io_init();	



	imx6uirq.inputdev = input_allocate_device();

	if (imx6uirq.inputdev == NULL)

	{

		printk("allocate input device fail\r\n");

		return -EINVAL;

	}

	imx6uirq.inputdev->name = IMX6UIRQ_NAME;



	__set_bit(EV_KEY, imx6uirq.inputdev->evbit);

	__set_bit(EV_REP, imx6uirq.inputdev->evbit);

	__set_bit(KEY_0,  imx6uirq.inputdev->keybit);



	ret = input_register_device(imx6uirq.inputdev);

	if (ret < 0)

	{

		printk("register input device fail\r\n");

		return -EINVAL;

	}



	return 0;

}



static void __exit imx6uirq_exit(void)

{

	//删除定时器

	del_timer_sync(&imx6uirq.timer);

	//释放中断

	free_irq(imx6uirq.irq_keydesc[0].irqnum, &imx6uirq);



	gpio_free(imx6uirq.irq_keydesc[0].gpio);

	

	input_unregister_device(imx6uirq.inputdev);

	input_free_device(imx6uirq.inputdev);



	printk(" dev exit\n");

}



module_init( imx6uirq_init);

module_exit( imx6uirq_exit);



MODULE_LICENSE("GPL");

MODULE_AUTHOR("ZK");



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

struct input_event keyinput_evt;

int main(int argc, char *argv[])
{
    int fd, ret;
    char *filename;
    unsigned char keyvalue;

    if (argc != 2)
    {
        printf("Usage:\n");
        printf("\n");
        return -1;
    }
    filename = argv[1];
    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        printf("open file %s fail\n", filename);
    }

    while (1)
    {
        ret = read(fd, &keyinput_evt, sizeof(keyinput_evt));

        if (ret <= 0)
        {

        }
        else 
        {
            switch (keyinput_evt.type)
            {
                case EV_KEY:
                    switch (keyinput_evt.code)
                    {
                        case KEY_0:
                            printf("key0 press %s\r\n", keyinput_evt.value? "press":"release");
                            break;
                        default:
                            break;

                    }
                    break;   
                default:
                    break;
            }
        }
    }

    close(fd);
    return ret;
}

四、运行测试

       在加载模块之前,我们先看看/dev/input目录下的内容:

        加载模块之后,再看看:

        多了一个event1,所以/dev/input/event1就是刚注册的驱动对应的文件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值