Linux字符设备驱动-KEY-input子系统

使用input子系统上报按键值,按键驱动使用platform driver形式。

1.input子系统

按键、鼠标、键盘及触摸屏等都属于输入(input)设备,Linux内核为此类设备设计了input子系统框架,专门用来处理输入事件。输入设备本质上是字符设备,只是在这基础之上添加了input框架,用户只需负责上报输入事件,比如按键值、坐标等信息,input核心层负责处理这些事件。
input子系统分为3层,三层的分工如下:
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向核心层报告输入内容。
核心层:承上启下,为驱动层提供输入设备注册和操作接口,通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交流。
input核心层的代码在drivers/input/input.c文件中。

1.1.注册input设备

input子系统的所有设备主设备号都为13,我们在使用input子系统处理输入设备的时候不需要注册字符设备,只需要向系统注册一个input设备即可。struct input_dev表示input设备。

    include <linux/input.h>
    struct input_dev {
        const char *name;
        const char *phys;
        const char *uniq;
        struct input_id id;
        unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
        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)];  // sound有关位图
        unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];    // 压力反馈的位图
        unsigned long swbit[BITS_TO_LONGS(SW_CNT)];    // 开关状态的位图
        int (*setkeycode)(struct input_dev *dev, const struct input_keymap_entry *ke, unsigned int*old_keycode);
        int (*getkeycode)(struct input_dev *dev, struct input_keymap_entry *ke);
        int (*open)(struct input_dev *dev);
        void (*close)(struct input_dev *dev);
        int (*flush)(struct input_dev *dev, struct file *file);
        int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
    };     

evbit表示输入事件类型,可选的事件类型如下:

    include <uapi/linux/input.h>
    #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    // sound(声音)事件
    #define EV_REP			0x14    // 重复事件
    #define EV_FF			0x15    // 压力事件
    #define EV_PWR			0x16    // 电源事件
    #define EV_FF_STATUS	0x17    // 压力状态事件

vbitkeybitrelbit等等都是存放不同事件对应的值。比如我们本章要使用按键事件,因此要用到keybit,Linux内核定义了很多按键值,按键值如下:

    include <uapi/linux/input.h>
    #define KEY_RESERVED	0
    #define KEY_ESC			1
    #define KEY_1			2
    #define KEY_2			3
    #define KEY_3			4
    #define KEY_4			5
    #define KEY_5			6
    #define KEY_6			7
    #define KEY_7			8
    #define KEY_8			9
    #define KEY_9			10
    #define KEY_0			11
    #define KEY_MINUS		12
    #define KEY_EQUAL		13
    #define KEY_BACKSPACE	14
    #define KEY_TAB			15
    #define KEY_Q			16
    #define KEY_W			17
    #define KEY_E			18
    #define KEY_R			19
    #define KEY_T			20
    #define KEY_Y			21
    #define KEY_U			22
    #define KEY_I			23
    #define KEY_O			24
    #define KEY_P			25

在编写input设备驱动的时候需要先申请一个input_dev结构体。使用input_allocate_device分配input_dev结构体。

    include <linux/input.h>
    struct input_dev __must_check *input_allocate_device(void);
    // devm_input_allocate_device分配的结构体不需要显示的释放和unregister
    struct input_dev __must_check *devm_input_allocate_device(struct device *);

使用input_free_device释放分配的input_dev结构体。

    include <linux/input.h>
    void input_free_device(struct input_dev *dev);

使用input_register_device向内核注册input设备。

    include <linux/input.h>
    int input_register_device(struct input_dev *dev)

使用input_unregister_device向内核注册input设备。

    include <linux/input.h>
    void input_unregister_device(struct input_dev *dev)
1.2.上报输入事件

Linux内核使用input_event结构体表示所有输入事件。

    include <uapi/linux/input.h>
    struct input_event {
        struct timeval time;  // 上报事件发生的事件
        __u16 type;           // 事件类型
        __u16 code;           // 事件码,对于按键表示具体的按键码
        __s32 value;          // 值,对于按键表示具体的按键值,如按下表示1,未按下或者松开表示0
    };
    struct timeval {
        __kernel_time_t		tv_sec;		    /* seconds */
        __kernel_suseconds_t	tv_usec;	/* microseconds */
    };

注册完input设备后,还需要告诉内核上报的什么内容。比如按键,需要在按键中断处理函数或者消抖定时器中断函数中将按键值上报,这样内核才能获得正确的输入值。对于不同的事件,上报的API函数不同。
input_event函数用于上报指定的事件以及对应的值。dev为注册的input_dev结构体指针,type为上报事件的配型,如EV_KEYcode表示事件码,如注册的按键值,比如KEY_0KEY_1等等,value表示事件值,比如1表示按键按下,0表示按键松开。input_event函数可以上报所有的事件类型和事件值,是一种通用的上报事件的API。

    include <linux/input.h>
    void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);

input_report_key函数用于上报按键事件,其内部调用的是input_event函数。

    include <linux/input.h>
    void input_report_key(struct input_dev *dev, unsigned int code, int value)

同样还有其他的上报函数,如下所示。

    include <linux/input.h>
    // 上报相对坐标事件
    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_sync函数本质上上报的是一个同步事件。

    include <linux/input.h>
    void input_sync(struct input_dev *dev)

2.在设备树中指定中断

设备中中断控制器组成了一个树状结构,my_key使用gpio5_1中断,interrupt-parent属性指定其父中断控制器为gpio5gpio5interrupt-controller属性,说明gpio5是一个中断控制器,其#interrupt-cells属性值为2,说明以gpio5作为父中断控制器的节点需要使用2个32为的整数描述中断信息,my_key节点interrupts属性就使用了2个32位整数描述中断信息。gpio5interrupt-parent属性继承自soc节点,其父中断控制器为gpc,gpc的父中断控制器为intcintc为arm的GIC中断控制器,其负责将所有中断汇总,然后分发给CPU,是最顶层的中断控制器。

/ { // GIC中断控制器,最顶端的中断控制器
    intc: interrupt-controller@00a01000 {
        compatible = "arm,cortex-a7-gic";
        #interrupt-cells = <3>;
        interrupt-controller;
        reg = <0x00a01000 0x1000>,
              <0x00a02000 0x100>;
    };
    // 按键设备节点
    my_key {
        #address-cells = <1>;
        #size-cells = <1>;
        pinctrl-names = "default";
        compatible = "my-key";
        /* 设置pinctrl */
        pinctrl-0 = <&pinctrl_gpio_keys>;
        key = <&gpio5 1 GPIO_ACTIVE_HIGH>;
        /* 设置父中断控制器 */
        interrupt-parent = <&gpio5>;            // gpio5作为中断控制器
        interrupts = <1 IRQ_TYPE_EDGE_BOTH>;	// 按键使用gpio5_1中断,中断触发类型为双边沿触发
        // 可使用interrupts-extended属性代替interrupt-parent和interrupts属性
        // interrupts-extended = <&gpio5 1 IRQ_TYPE_EDGE_BOTH>;
        status = "okay";
        code = <KEY_0>;	
    };
    soc {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus";
        interrupt-parent = <&gpc>;
        ranges;
        gpio5: gpio@020ac000 {
            compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
            reg = <0x020ac000 0x4000>;
            interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
                        <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
            gpio-controller;  
            #gpio-cells = <2>;
            interrupt-controller;	 // 表示中断控制器
            #interrupt-cells = <2>;  // 子节点使用几个32位整数描述中断
        };

        aips1: aips-bus@02000000 {
            compatible = "fsl,aips-bus", "simple-bus";
            #address-cells = <1>;
            #size-cells = <1>;
            reg = <0x02000000 0x100000>;
            ranges;

            gpc: gpc@020dc000 {
                compatible = "fsl,imx6ul-gpc", "fsl,imx6q-gpc";
                reg = <0x020dc000 0x4000>;
                interrupt-controller;
                #interrupt-cells = <3>;
                interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
                interrupt-parent = <&intc>;
                fsl,mf-mix-wakeup-irq = <0xfc00000 0x7d00 0x0 0x1400640>;
            };
        };
    };
};

3.获取中断号的函数

    include <linux/gpio.h>
    // 使用gpio编号获取中断号
    int gpio_to_irq(unsigned int gpio)

    include <linux/of_irq.h>
    / 使用节点指针和索引号获取中断号
    unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
    int of_irq_get(struct device_node *dev, int index)
    // 使用节点指针和名称获取中断号
    int of_irq_get_byname(struct device_node *dev, const char *name);

    // 如设备树节点转换为platform_device,可使用平台设备相关函数
    include <linux/platform_device.h>
    // 使用platform_get_resource函数获取中断号,此时type应为IORESOURCE_IRQ
    struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
    // 使用platform_get_irq直接获取中断号,num为中断索引号
    int platform_get_irq(struct platform_device *dev, unsigned int num)

4.字符设备驱动源码

	/*===========================my_key_input.h================================*/
	#include <linux/cdev.h>
	#include <linux/device.h>
	#include <linux/mutex.h>
	#include <linux/timer.h>
	#include <linux/wait.h>
	#include <linux/fs.h>
	#include <linux/input.h>
	
	#define KEY_DT_PATH "/my_key" 
	#define NAME "my_key"
	
	struct irq_key_desc
	{
		unsigned int irq;           // 虚拟中断号
		unsigned int gpio;          // gpio编号
		unsigned int code;       	 // 按键码
		volatile unsigned int key_push_cnt;  // 按键按下的次数
		volatile unsigned int key_release_cnt;  // 按键松开的次数
	};
	
	// 设备结构体
	struct my_key_dev {
		struct class* my_key_class;
		struct device* my_key_device;
		struct timer_list timer;    // 定时器
		struct input_dev* key_input_dev;
		struct irq_key_desc key_desc;
	};

	/*===========================my_key_input.c================================*/
	#include <linux/init.h>
	#include <linux/module.h>
	#include <linux/kernel.h>
	#include <asm/uaccess.h>
	#include <linux/slab.h>
	#include <linux/stat.h>
	#include <linux/sysfs.h>
	#include <linux/interrupt.h>
	#include <linux/jiffies.h>
	#include <linux/sched.h>  
	#include <asm/io.h>
	#include <linux/gpio.h> 
	#include <linux/poll.h>
	#include <linux/gpio.h>
	#include <linux/of_gpio.h>
	#include <linux/of_irq.h>
	#include <linux/platform_device.h>
	#include "my_key_input.h"
	
	static struct my_key_dev* my_key = NULL;
	
	/*********************按键中断服务函数,在中断的上半段执行***********************/
	irqreturn_t my_key_irq_handler(int irq, void* dev)
	{
		struct my_key_dev* key = (struct my_key_dev*)dev;
		mod_timer(&key->timer, jiffies + msecs_to_jiffies(10)); // 定时器延时10毫秒
		return IRQ_RETVAL(IRQ_HANDLED);
	}
	/******************************定时器到期执行函数****************************/
	void timer_fun(unsigned long data)
	{
		int val = 0;
		struct my_key_dev* dev = (struct my_key_dev*)data;
		val = gpio_get_value(dev->key_desc.gpio);
		if (val == 1) {  // 按键按下
			input_report_key(dev->key_input_dev, dev->key_desc.code, val);
			input_sync(dev->key_input_dev);
			dev->key_desc.key_push_cnt++;
		}
		else {	// 按键松开
			input_report_key(dev->key_input_dev, dev->key_desc.code, val);
			input_sync(dev->key_input_dev);
			dev->key_desc.key_release_cnt++;
		}
	}
	
	static int key_gpio_init(struct my_key_dev* dev, struct platform_device* pdev)
	{
		int ret = 0;
		struct device_node* n;
		n = of_find_node_by_path(KEY_DT_PATH);  // 获取按键设备树节点
		if (NULL == n) {
			printk(KERN_ERR "find key node error by %s\n", KEY_DT_PATH);
			return -EINVAL;		
		}
		dev->key_desc.gpio = of_get_named_gpio(n, "key",0);  // 获取gpio编号
		if (dev->key_desc.gpio < 0) {
			printk(KERN_ERR "can't get key number\n");
			return -EINVAL;
		}
		ret = gpio_request(dev->key_desc.gpio, NAME);  // 申请gpio
		if (ret < 0) {
			printk(KERN_ERR "request key gpio error\n");
			return -EINVAL;		
		}
		gpio_direction_input(dev->key_desc.gpio);  // gpio设置为输入
		dev->key_desc.irq = irq_of_parse_and_map(n, 0); // 获取虚拟中断号
		if (0 == dev->key_desc.irq) {
			printk(KERN_ERR "can't parse and map irq\n");
			ret = -1;
			goto free_gpio;
		}
		// 读取code的属性值
		ret = of_property_read_u32_index(n, "code", 0, &dev->key_desc.code);
		if (ret < 0) {
			printk(KERN_ERR "can't parse and map irq\n");
			goto free_gpio;
		}
		ret = request_irq(dev->key_desc.irq, my_key_irq_handler, 
								IRQ_TYPE_EDGE_BOTH, NAME, dev);
		if (ret < 0) {
			printk(KERN_ERR "request %s irq failed\n", NAME);
			goto free_gpio;		
		}
		return 0;
	free_gpio:
		gpio_free(dev->key_desc.gpio);
		return ret;
	} 
	
	/***************************模块初始化**************************************/
	static int my_key_probe(struct platform_device* dev)
	{
		int ret = 0;
	
		// 分配设备结构体内存并将分配的内存清0
		my_key = kzalloc(sizeof(struct my_key_dev), GFP_KERNEL);
		if (NULL == my_key) {
			printk(KERN_ERR "kzalloc failed\n");
			return -ENOMEM;
		}
	
		init_timer(&my_key->timer);  // 初始化定时器
		my_key->timer.function = timer_fun;
		my_key->timer.data = (unsigned long)my_key;
		add_timer(&my_key->timer);   // 注册定时器
	
		ret = key_gpio_init(my_key, dev);
		if (ret < 0) goto timer_del;
	
		my_key->key_input_dev = input_allocate_device(); // 分配input_dev结构体
		if (NULL == my_key->key_input_dev) {
			printk(KERN_ERR "allocate input device failed\n");
			ret = -ENOMEM;
			goto timer_del;
		}
		my_key->key_input_dev->name = NAME;
		set_bit(EV_KEY, my_key->key_input_dev->evbit);  // 产生按键事件
		set_bit(EV_REP, my_key->key_input_dev->evbit);  // 产生重复事件
		// 产生KEY_0按键
		set_bit(my_key->key_desc.code, my_key->key_input_dev->keybit);  
		ret = input_register_device(my_key->key_input_dev);
		if (ret < 0) {
			printk(KERN_ERR "input device register failed\n");
			goto free_input_device;
		}
		printk(KERN_INFO "my_key module init OK\n");
		return 0;
	
	free_input_device:
		input_free_device(my_key->key_input_dev);
	timer_del:
		del_timer(&my_key->timer);   // 删除定时器
		kfree(my_key);
		my_key = NULL;
	    return ret;
	}
	/********************模块注销************************/
	static int my_key_remove(struct platform_device* dev)
	{
		input_unregister_device(my_key->key_input_dev);
		input_free_device(my_key->key_input_dev);
		gpio_free(my_key->key_desc.gpio);
		free_irq(my_key->key_desc.irq, my_key);
		del_timer(&my_key->timer);   // 删除定时器
		kfree(my_key);
		my_key = NULL;
		printk(KERN_INFO "my_key module exit\n");
		return 0;
	}
	
	// 定义设备和驱动匹配需要的of_device_id匹配表变量,
	static const struct of_device_id my_key_of_match[] = {
		{ .compatible = "my-key" },   //  兼容属性,需要与设备树中的兼容属性一致
		{ /* Sentinel */ }            // 最后一个元素一定为空
	};
	MODULE_DEVICE_TABLE(of, my_key_of_match);
	// 定义注册驱动时需要的platform_driver结构体变量
	static struct platform_driver my_key_platform_driver = {
		.driver = {
			.name = "imx6ull-my-led",   // 设置驱动的名称
			 // 驱动的设备的匹配数组信息,一个驱动可以和多个设备匹配
			.of_match_table = my_key_of_match,
		},
		.probe = my_key_probe,    // 设置初始化probe函数,驱动和设备匹配成功后此函数会执行
		.remove = my_key_remove,  // 设置退出remove函数
	};
	
	/**********************模块初始化****************************/
	static int __init my_key_init(void)
	{
		int ret = 0;
		ret = platform_driver_register(&my_key_platform_driver);
		if (ret < 0) printk(KERN_ERR "my_key register platform driver error\n");
		return ret;
	}
	/***********************模块注销***************************/
	static void __exit my_key_exit(void)
	{
		platform_driver_unregister(&my_key_platform_driver);
	}
	
	module_init(my_key_init);
	module_exit(my_key_exit);
	
	MODULE_LICENSE("GPL");
	MODULE_AUTHOR("liyang.plus@foxmail.com");
	MODULE_VERSION("v1.00");

5.测试程序源码

	#include <stdio.h>
	#include <unistd.h>
	#include <sys/types.h>
	#include <sys/stat.h>
	#include <string.h>
	#include <fcntl.h>
	#include <poll.h>
	#include <sys/select.h>
	#include <signal.h>
	#include <linux/input.h>
	#define PATH "/dev/my_key"
	 
	int main(int argc, char* argv[])
	{
	    int len, ret;
	    struct input_event inputevent = {0};
	    int fd, push_cnt = 0, release_cnt = 0;
	    char* path = argv[1];
	    char push[] = "push";
	    char release[] = "release";
	    if (argc != 2) {
	        printf("path error\n");
	        return -1;
	    }
	    fd = open(path, O_RDONLY); // 阻塞打开
	    if (fd < 0){
	        printf("my_key open error\n");
	        return -1;
	    }
	    while (1) {
	        ret = read(fd, &inputevent, sizeof(inputevent));
	        if (ret > 0) {
	            switch (inputevent.type)
	            {
	            case EV_KEY:
	                printf("key %d %s %d\n",inputevent.code, 
	                inputevent.value ? push : release, 
	                inputevent.value ? push_cnt : release_cnt);
	                inputevent.value ? push_cnt++ : release_cnt++;
	                break;
	            default:
	                break;
	            }
	        } else printf("read error\n");
	    }
	    close(fd);
	    return 0;
	}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值