Linux驱动第八章:input输入子系统

第一节、为什么要有input子系统?作用与意义是什么?

抛出一个问题:为什么我们的电脑可以兼容不同品牌不品型号的键盘、鼠标?

在没有输入子系统前,我们是如何驱动一个设备的?以键盘按下一个a键为例:

我们大概要三步:

1 为键盘所在的usb总线,写一个总线驱动逻辑,获取usb设备数据。

2. 为键盘写一套中断驱动逻辑:按下a键,触发中断,在中断处理函数中把a的扫描码通过总线发给SOC的gpio口。

3.为键盘写一套字符设备驱动逻辑:创建设备节点,并在xxx_read中把获取的a键的扫描转换为字符‘a’,再copy_to_user到用户程序APP中。

4.通过APPread读走这个字符。

我们何不把这些设备驱动构建的共同点抽象出来形成一个系统,把差异留给工程师们呢,这样即可兼容所有输入设备,而且快速开发!何乐而不为!所以我们来学习一下input输入子系统是如何抽象的,我们又该如何使用这个子系统!

所以说input系统就是Linux专门为输入类设备编写的一个子系统框架,从而规范驱动开发,降低了开发难度,并且开发的驱动的通用性及兼容性更强。

常见的输入设备有:键盘,鼠标、游戏杆、触摸屏等,由用户直接操作设备而产生输入电信号的设备都是输入设备。

第二节、input输入子系统框架结构:

1.分层框图:

详情请查阅此图的原文链接:linux输入子系统详解——看这一篇就够了-CSDN博客,我根据相关资料画了一下调用框图,方便大家理解:

2.内部调用关系框图:

通过以上方式建立了弱耦合的input输入子系统,它的意义与作用:

1)统一了物理形态各异的输入设备的处理方法。对不同的输入设备,大致分为按键类、相对坐标类、绝对坐标类等,每个类在核心层和事件处理层如何处理都是代码已经写好的,我们只需要学习接口的使用;

(2)供用于分发输入报告给用户应用程序的简单事件接口。对于应用程序来说,输入类设备都同一用struct input_event结构体来表示,屏蔽了输入设备的差异;

(3)提炼出输入驱动程序的通用部分,简化驱动程序的开发和移植工作。核心层提供了输入设备的注册、卸载、事件上报接口,我们只需要根据核心层提供的接口和硬件的特性去编写驱动代码;

第三节、输入子系统常用类型与相应API:

1. 描述输入设备的结构体:input_dev:

struct input_dev {
	const char *name;	//设备名称
	const char *phys;	//设备在系统中的物理路径
	const char *uniq;	//设备唯一识别符
	struct input_id id;	//设备ID,包含总线类型(PCI 、 USB)、厂商ID,设备ID与 input handler 匹配的时会用到
 //    
	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];	//设备支持的事件类型
	
	unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];	//设备支持的具体的按键、按钮事件
	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)];	//设备支持的具体的LED指示灯事件
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];	//设备支持的具体的音效事件
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];	//设备支持的具体的力反馈事件
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];	//设备支持的具体的开关事件

	unsigned int keycodemax;	//键盘码表的大小
	unsigned int keycodesize;	//键盘码表中的元素个数
	void *keycode;	//设备的键盘码表
	
	//下面两个是可选方法,用于配置和获取键盘码表
	int (*setkeycode)(struct input_dev *dev,
			  unsigned int scancode, unsigned int keycode);
	int (*getkeycode)(struct input_dev *dev,
			  unsigned int scancode, unsigned int *keycode);

	struct ff_device *ff;	//如果设备支持力反馈,则该成员将指向力反馈设备描述结构
	unsigned int repeat_key;	//保存上一个键值,用于实现软件自动重复按键(用户按住某个键不放)
	struct timer_list timer;	//用于软件自动重复按键的定时器

	int sync;		//在上次同步事件(EV_SYNC)发生后没有新事件产生,则被设置为 1 

	int abs[ABS_CNT];	//用于上报的绝对坐标当前值
	int rep[REP_MAX + 1];	//记录自动重复按键参数的当前值

	unsigned long key[BITS_TO_LONGS(KEY_CNT)];	//反映设备按键、 按钮的当前状态
	unsigned long led[BITS_TO_LONGS(LED_CNT)];	//反映设备LED指示灯的当前状态
	unsigned long snd[BITS_TO_LONGS(SND_CNT)];	//反映设备音效的当前状态
	unsigned long sw[BITS_TO_LONGS(SW_CNT)];	//反映设备开关的当前状态

	int absmax[ABS_CNT];	//绝对坐标的最大值
	int absmin[ABS_CNT];	//绝对坐标的最小值
	int absfuzz[ABS_CNT];	//绝对坐标的噪音值,变化小于该值的一半可忽略该事件
	int absflat[ABS_CNT];	//摇杆中心位置大小
	int absres[ABS_CNT];

	//提供以下4个设备驱动层的操作接口,根据具体的设备需求实现它们
	int (*open)(struct input_dev *dev);
	void (*close)(struct input_dev *dev);
	int (*flush)(struct input_dev *dev, struct file *file);
	//用于处理送到设备驱动层来的事件,很多事件在事件处理层被处理,但有的事件仍需送到设备驱动中.
	//如LED指示灯事件和音效事件,因为这些操作通常需要设备驱动执行(如点亮某个键盘指示灯) 
	int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
	
	//指向独占该设备的输入句柄( input handle ),通常设备驱动上报的事件会被分发到与设备
	//关联的所有事件处理程序( input handler )中处理,但如果通过ioctl 的EVIOCGRAB命令
	//设置了独占句柄,则上报事件只能被所设置的输入句柄对应的事件处理程序处理
	struct input_handle *grab;

	
 spinlock_t event_lock;	//调用 event () 时需要使用该自旋锁来实现互斥
	struct mutex mutex;	//用于串行化的访问 open()、 close()和flush()等设备方法

	//记录输入事件处理程序(input handlers)调用设备open()方法的次数.保证设备open()方法是在
	//第一次调用 input_open_device()中被调用,设备close()方法在最后一次调用 input_close_device()中被调用
	unsigned int users;
	bool going_away;

	struct device dev;  //内嵌device结构

	struct list_head	h_list;	//与该设备相关的输入句柄列表(struct input handle)
	struct list_head	node;	//挂接到input_dev_list链表上
};

2.注册input_dev的函数接口:input_register_device()

(1)input_register_devic()函数是核心层提供给设备驱动层用于注册输入设备的接口,传参就是设备驱动层构建好的struct input_dev结构体;
(2)注册函数需要将传入的struct input_dev结构体挂接到input_dev_list链表上;
(3)将传递进来的input_dev结构体和所有注册的事件驱动进行匹配,匹配上就会产生handle,并产生设备节点/dev/input/event0....eventX;
void input_register_device(struct input_dev *dev);
//作用:在输入子系统的核心层注册input输入设备:
//成功返回0,失败返回错误码。

 对应的就是:void input_unregister_device(struct input* dev);//卸载输入设备对象。

3.上报事件数据给输入子系统:input_event():

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
//作用:上报事件数据给输入子系统:
//参数:
dev: 指向 input_dev 结构体的指针,表示哪个输入设备生成了这个事件。
type: 事件类型,表示事件的类别。常见的事件类型有:
EV_KEY:按键事件(如键盘按键、鼠标按钮等)。
EV_REL:相对轴事件(如鼠标移动)。
EV_ABS:绝对轴事件(如触摸屏位置)。
EV_SYN:同步事件,用于标志一组事件的结束。
code: 事件代码,具体描述事件的子类别。对于按键事件(EV_KEY),code 表示具体的按键;对于轴事件(EV_REL 和 EV_ABS),code 表示具体的轴(如 REL_X 表示 X 轴移动)。
value: 事件值,表示事件的具体值。对于按键事件,这里通常是 1(按下)或 0(释放);对于轴事件,这里表示移动的距离或位置的具体值。
//上报数据的结构体:
struct input_event{
    struct timeval time//事件发生的时间戳;
	__u16 type;//事件类型
	__u16 code;//事件编码 
	__s32 value;//事件值
};
//在应用层获取事件数据的格式,也应该按照这个类型来获取。

4.同步数据:void input_sync():

void input_sync();
通知输入子系统已经完成一组事件数据的上报。事件数据传递结束。

5.其它相关的辅助函数:

1.1创建并定义输入设备对象:input_allocate_device()

struct input_dev* input_allocate_device()
//功能:动态定义一个输入设备对象。
//返回值:成功返回输入设备对象的地址,失败返回NULL;
与之对应的就是回收输入设备对象:
input_free_device(struct input_dev *dev);

相应的事件类型及相关的宏定义在:

#include <uapi/linux/input.h>头文件中包含的头文件:#include "input-event-codes.h"中

1.2使用set_bit设置输入设备的事件类型 比如键盘事件:

set_bit(EV_KEY, xxx_input_dev->evbit);//设置什么事件类型
同理,也可以设备是那个按键产生输入事件:
set_bit(KEY_L,xxx_input_dev->keybit);//设置那个按键的事件?

第四节:输入子系统应用示例:

1.驱动代码:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/io.h>

#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <asm-generic/gpio.h>
#include <linux/of_platform.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/input.h>

struct MyKey
{
    u32 key1_interrupts;
    u8 key1_gpios;
    u32 key2_interrupts;
    u8 key2_gpios;
    u32 key3_interrupts;
    u8 key3_gpios;
    //添加一下irq属性:
    volatile u32 irq;
    //添加input_dev属性:
    struct input_dev* i_dev;
};
struct MyKey mykey = {0};

irqreturn_t threaded_irq_functions(int irq, void *dev)
{
    //mdelay(100);
    //阻塞式延时函数:
    msleep(50);
    if(mykey.irq == mykey.key1_interrupts)
    {
        if(gpio_get_value(mykey.key1_gpios) == 0)
        {
            printk("-------------------\n");
            //key1按下了:
            input_event(mykey.i_dev,EV_KEY,KEY_A,1);
            input_sync(mykey.i_dev);
        }
        else{
            //key1抬起了:
            input_event(mykey.i_dev,EV_KEY,KEY_A,0);
            input_sync(mykey.i_dev);
        }
    }
    if(mykey.irq == mykey.key2_interrupts)
    {
        if(gpio_get_value(mykey.key2_gpios) == 0)
        {
            //key2按下了:
            input_event(mykey.i_dev,EV_KEY,KEY_B,1);
            input_sync(mykey.i_dev);
        }
        else{
            //key2抬起了
            input_event(mykey.i_dev,EV_KEY,KEY_B,0);
            input_sync(mykey.i_dev);
        }
    }

    if(mykey.irq == mykey.key3_interrupts)
    {
        if(gpio_get_value(mykey.key3_gpios) == 0)
        {
            //key3按下了:
            input_event(mykey.i_dev,EV_KEY,KEY_C,1);
            input_sync(mykey.i_dev);
        }
        else{
            //key2抬起了
            input_event(mykey.i_dev,EV_KEY,KEY_C,0);
            input_sync(mykey.i_dev);
            
        }
    }
    return IRQ_HANDLED;
}

irqreturn_t key_ISR(int irq, void * dev)
{
    mykey.irq = irq;
    return IRQ_WAKE_THREAD;
}


int my_dev_driver_probe(struct platform_device *pdev)
{
    struct device_node* key_node;
    printk("my_dev_driver_probe probe函数执行\n");
        //获取key设备树象节点的对象指针:
    key_node = pdev->dev.of_node;
    //4.获取Key对象的linux中断号:
    mykey.key1_interrupts = of_irq_get(key_node,0);
    mykey.key1_gpios = of_get_named_gpio(key_node,"mykey_gpios",0);
    gpio_direction_input(mykey.key1_gpios);
    printk("irq = %d\n",mykey.key1_interrupts);
    printk("gpio = %d\n",mykey.key1_gpios);
    mykey.key2_interrupts = of_irq_get(key_node,1);
    mykey.key2_gpios = of_get_named_gpio(key_node,"mykey_gpios",1);
    gpio_direction_input(mykey.key2_gpios);
    mykey.key3_interrupts = of_irq_get(key_node,2);
    mykey.key3_gpios = of_get_named_gpio(key_node,"mykey_gpios",2);
    gpio_direction_input(mykey.key3_gpios);

    //5.请求中断并注册中断处理程序的函数;
    request_threaded_irq(mykey.key1_interrupts,key_ISR,threaded_irq_functions,IRQF_TRIGGER_RISING | IRQF_TRIGGER_RISING,"key1-interrupts",NULL);
    request_threaded_irq(mykey.key2_interrupts,key_ISR,threaded_irq_functions,IRQF_TRIGGER_RISING | IRQF_TRIGGER_RISING,"key2-interrupts",NULL);
    request_threaded_irq(mykey.key3_interrupts,key_ISR,threaded_irq_functions,IRQF_TRIGGER_RISING | IRQF_TRIGGER_RISING,"key3-interrupts",NULL);
    //....
    
    //6.初始化mykey中的input_dev对象:
    mykey.i_dev = input_allocate_device();

    //对输入设备匹配方式及事件相关进行设置:
    mykey.i_dev->id.bustype =  ID_BUS;

    set_bit(EV_KEY,mykey.i_dev->evbit);
    set_bit(KEY_A, mykey.i_dev->keybit);
    set_bit(KEY_B, mykey.i_dev->keybit);
    set_bit(KEY_C, mykey.i_dev->keybit);
    mykey.i_dev->name = "KEY";
    mykey.i_dev->phys = "soc/pf9/key";
    mykey.i_dev->uniq = "KEY";



    return 0;
}

int my_dev_driver_remove(struct platform_device *pdev)
{
    printk("my_dev_driver_remover函数执行了\n");

    input_unregister_device(mykey.i_dev);
    free_irq(mykey.key1_interrupts,NULL);
    free_irq(mykey.key2_interrupts,NULL);
    free_irq(mykey.key3_interrupts,NULL);
    return 0;
}

//设备树匹配方式:
struct of_device_id of_node_match_table[] = {
    [0] = {.compatible = "WX,my_device_key"},
    [2] = {/*最后一个一定要给一个空元素,代表结束*/}
};

// 1.定义一个平台驱动对象:
struct platform_driver my_platform_driver = {
    .probe = my_dev_driver_probe,
    .remove = my_dev_driver_remove,
    .driver = {
        .name = "WX,my_device_driver",
        //设备树的匹配方式:
        .of_match_table = of_node_match_table,
    },

};

// 入口函数:
int __init my_test_module_init(void)
{
    int ret = 0;
    ret = platform_driver_register(&my_platform_driver);
    if (ret < 0)
    {
        return -1;
    }
    return 0;
}

// 出口函数:
void __exit my_test_module_exit(void)
{
    platform_driver_unregister(&my_platform_driver);
}

// 指定许可:
MODULE_LICENSE("GPL");
MODULE_AUTHOR("gaowanxi, email:gaonetcom@163.com");
// 指定入口及出口函数:
module_init(my_test_module_init);
module_exit(my_test_module_exit);

2.应用层测试代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <linux/input.h>
#include <sys/signal.h>
#include <arpa/inet.h>
int main(int argc, char const *argv[])
{
    int fd1 = open("/dev/input/event0", O_RDWR);
    if (fd1 == -1)
    {
        perror("open err");
        return -1;
    }
    struct input_event event_data = {0};
    int nbytes = 0;

    while (true)
    {
        memset(&event_data,0 ,sizeof(event_data));
        nbytes = read(fd1,&event_data,sizeof(event_data));
        if(nbytes == -1)
        {
            perror("read err:");
            return -1;
        }
        if(event_data.code == KEY_A) //获取温度
        {
            
            printf("A\n");
        }

        if(event_data.code == KEY_B)
        {
            printf("B\n");
        }
        if(event_data.code == KEY_C)
        {
            printf("C\n");
        }


    }
    

   
    close(fd1);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值