Linux驱动:input输入子系统

1、input输入子系统

1.1 简介

input子系统是内核针对某一类输入设备的一个框架,键盘、鼠标、按键和触摸屏等设备都属于输入设备。它主要分成了三部分:

  • drivers:驱动层,我们主要编写这部分的驱动代码,驱动里只要上报键盘按下、鼠标点击等事件即可,至于这些事件到底有什么用,就是通过接下来的input core层传达给handlers层;
  • input core:核心层,作为中间层负责将drivers层上报的事件“转告”给handlers层,起到承上启下的作用;
  • handlers:事件层,这部分是纯软件的,不涉及硬件的操作,drivers层上报的事件到底有何作用就是由它来处理。

  虽然说我们编写输入子系统的驱动属于drivers层,但是我们还是可以了解一下input core层,它在内核源码drivers/input/input.c里:

struct class input_class = {
    
    
	.name		= "input",
	.devnode	= input_devnode,
};

static int __init input_init(void)
{
    
    

	err = class_register(&input_class);		// 根据input_class的name成员在/sys/class目录下会创建input目录
	...

	err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),	// 主设备号INPUT_MAJOR在include/uapi/linux/major.h里定义为13
				     INPUT_MAX_CHAR_DEVICES, "input");	// 最多支持1024个设备,在同一个文件中定义
	...

}

1.2 相关API函数

  前面register_chrdev_region注册字符设备的时候已经指定了主设备号major,所以无需像普通字符设备驱动一样分配主设备号等操作,只需要分配一个input_dev结构体并且在设置好之后注册进input输入子系统中即可。相关结构体和API函数如下:

// 结构体定义
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)];	/* 相对坐标 */
...													/* ... */
};

// 为新的input_dev分配内存,成功返回结构体指针,失败返回NULL
struct input_dev *input_allocate_device(void);

// 与input_allocate_device相对应,释放input_dev的内存
void input_free_device(struct input_dev *dev);

// 将dev注册到“input core”中
int input_register_device(struct input_dev *dev);

// 与input_register_device相对应,将dev从“input core”中注销
void input_unregister_device(struct input_dev *dev);

// 不同的事件选用不同的上报函数,code是具体的事件选项,比如KEY_1;value是事件的值,1表示按下,0表示松开
void input_report_key(struct input_dev *dev, unsigned int code, int 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_sync(struct input_dev *dev);

// 该函数就就是以上input_report_xxx等函数的内部实现
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);

其中,事件类型和事件值在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
#define EV_SND			0x12
#define EV_REP			0x14
#define EV_FF			0x15
#define EV_PWR			0x16
#define EV_FF_STATUS	0x17
#define EV_MAX			0x1f
#define EV_CNT			(EV_MAX+1)

/* 部分按键的选项(事件类型的具体事件选项) */
#define KEY_RESERVED	0
#define KEY_ESC			1
#define KEY_1			2
#define KEY_2			3
#define KEY_3			4
#define KEY_4			5
...

1.3 使用流程(驱动框架)

struct input_dev *xxx_input_dev;


static irqreturn_t xxx_irq_hander(int irq, void *dev_id)
{
    
    
	// ...	// 读取按键值
	if(xxx){
    
    
		/* 按键按下 */
		input_report_key(xxx_input_dev, KEY_0, 1);		// 上报方式1
		input_sync(xxx_input_dev);
	}else{
    
    
		/* 按键松开 */
		input_event(xxx_input_dev, EV_KEY, KEY_A, 0);	// 上报方式2
		input_sync(xxx_input_dev);
	}

	return IRQ_HANDLED;
}


static int __init xxx_init(void)
{
    
    
	xxx_input_dev = input_allocate_device();// 先分配设备
	xxx_input_dev->name = "xxx_input_dev";

	/* 设置KEY_0的相关配置 */
#if 1	/* 方法1 */
	__set_bit(EV_KEY, xxx_input_dev->evbit);// 设置支持的事件类型
	__set_bit(EV_REP, xxx_input_dev->evbit);
	__set_bit(KEY_0, xxx_input_dev->keybit);// 设置支持具体的事件
#endif

#if 0	/* 方法2 */
	xxx_input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
	xxx_input_dev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
#endif

#if 0	/* 方法3 */
	xxx_input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
	input_set_capability(xxx_input_dev, EV_KEY, KEY_0);
#endif


	input_register_device(xxx_input_dev);	// 注册到input core

	/* 申请中断等等,略 */
}

static void __exit xxx_exit(void)
{
    
    
	input_unregister_device(xxx_input_dev);	// 先注销
	input_free_device(xxx_input_dev);		// 后释放内存
}


2、驱动示例

#include <linux/ide.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/input.h>


struct key_drv_info{
    
    
	struct device_node *node;
	int 			 gpio;
	int				 irq_num;
	struct timer_list timer;
	struct input_dev *inputdev;
};


static struct key_drv_info key_drv;


static void key_timer_irq_function(unsigned long data)
{
    
    
	if(0 == gpio_get_value(key_drv.gpio)){
    
    
		/* 按键按下 */
		input_report_key(key_drv.inputdev, KEY_A, 1);	// 上报事件方式1
		input_sync(key_drv.inputdev);
	}else{
    
    
		/* 按键松开 */
		input_event(key_drv.inputdev, EV_KEY, KEY_A, 0);// 上报事件方式2
		input_sync(key_drv.inputdev);					// 上报完成需要同步一下
	}
}


static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
    
    
	struct key_drv_info *key_dev = dev_id;
	
	mod_timer(&key_dev->timer, jiffies + msecs_to_jiffies(10));

	return IRQ_RETVAL(IRQ_HANDLED);
}


static int __init key_drv_init(void)
{
    
    
	key_drv.node = of_find_node_by_path("/interrupt_key");
	
	key_drv.gpio = of_get_named_gpio(key_drv.node, "key-gpio", 0);
	
	if(gpio_request(key_drv.gpio, "key0")){
    
    
		printk("gpio_request error!\n");
		return -1;
	}
	
	gpio_direction_input(key_drv.gpio);

	key_drv.irq_num = irq_of_parse_and_map(key_drv.node, 0);
	
	if(request_irq(key_drv.irq_num, key_irq_handler, 
			IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "key0", &key_drv)){
    
    
		printk("request_irq error!\n");
		return -1;
	}

	init_timer(&key_drv.timer);
	key_drv.timer.function = key_timer_irq_function;


	key_drv.inputdev = input_allocate_device();	// 分配
	key_drv.inputdev->name = "key_input_dev";

	__set_bit(EV_KEY, key_drv.inputdev->evbit);	// 设置能产生哪类事件
	__set_bit(EV_REP, key_drv.inputdev->evbit);

	__set_bit(KEY_A, key_drv.inputdev->keybit);	// 设置这类型的具体某个事件

	if(input_register_device(key_drv.inputdev)){
    
    // 注册
		printk("input_register_device error!\n");
		return -1;
	}

	return 0;
}

static void __exit key_drv_exit(void)
{
    
    
	input_unregister_device(key_drv.inputdev);	// 先注销再释放内存
	input_free_device(key_drv.inputdev);

	del_timer_sync(&key_drv.timer);
	free_irq(key_drv.irq_num, &key_drv);
	gpio_free(key_drv.gpio);
}

module_init(key_drv_init);
module_exit(key_drv_exit);

MODULE_LICENSE("GPL");


3、测试程序

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

#define EV_KEY	0x01
#define KEY_A	30

int main(int argc, char **argv)
{
    
    
	int fd;
	int ret;
	unsigned char key_val;
	struct input_event key_event;

	if(argc != 2){
    
    
		printf("Usage: ./keyAPP /dev/input/eventx\n");
		return -1;
	}
	
	fd = open(argv[1], O_RDWR);
	if (fd < 0){
    
    
		printf("Open %s error!\n", argv[1]);
		return -1;
	}

	while (1)
	{
    
    
		ret = read(fd, &key_event, sizeof(key_event));
		if(ret < 0){
    
    
			printf("Read fd=%d error!\n", fd);
			return -1;
		}else{
    
    
			if(key_event.type == EV_KEY && key_event.code == KEY_A){
    
    
				if(key_event.value){
    
    
					printf("KEY_A press!    val=%d\n", key_event.value); // 注:value值不只是1
				}else if(key_event.value == 0){
    
    
					printf("KEY_A release!  val=%d\n", key_event.value);
				}
			}
		}
	}
	
	return 0;
}


4、测试结果

/drivers # ls -l /dev/input/
total 0
crw-rw----    1 0        0          13,  64 Jan  1 00:00 event0
crw-rw----    1 0        0          13,  63 Jan  1 00:00 mice
/drivers # 
/drivers # insmod key_input.ko 
input: key_input_dev as /devices/virtual/input/input1
/drivers # 
/drivers # ls -l /dev/input/
total 0
crw-rw----    1 0        0          13,  64 Jan  1 00:00 event0
crw-rw----    1 0        0          13,  65 Jan  1 06:26 event1
crw-rw----    1 0        0          13,  63 Jan  1 00:00 mice
/drivers # 
/drivers # ./keyAPP_input /dev/input/event1
KEY_A press!    val=1
KEY_A release!  val=0
KEY_A press!    val=1
KEY_A release!  val=0
KEY_A press!    val=1
KEY_A press!    val=2
KEY_A press!    val=2
KEY_A press!    val=2
KEY_A press!    val=2
KEY_A press!    val=2
KEY_A release!  val=0
^C
/drivers # hexdump /dev/input/event1
0000000 5af3 0000 e0ef 0008 0001 001e 0001 0000
0000010 5af3 0000 e0ef 0008 0000 0000 0000 0000
0000020 5af3 0000 b5a8 000a 0001 001e 0000 0000
0000030 5af3 0000 b5a8 000a 0000 0000 0000 0000
0000040 5af4 0000 be09 0006 0001 001e 0001 0000
0000050 5af4 0000 be09 0006 0000 0000 0000 0000
0000060 5af4 0000 e0e7 0008 0001 001e 0000 0000
0000070 5af4 0000 e0e7 0008 0000 0000 0000 0000
0000080 5af5 0000 c66c 0002 0001 001e 0001 0000
0000090 5af5 0000 c66c 0002 0000 0000 0000 0000
00000a0 5af5 0000 96f8 0006 0001 001e 0002 0000
00000b0 5af5 0000 96f8 0006 0000 0000 0001 0000
00000c0 5af5 0000 333a 0007 0001 001e 0002 0000
00000d0 5af5 0000 333a 0007 0000 0000 0001 0000
00000e0 5af5 0000 cf74 0007 0001 001e 0002 0000
00000f0 5af5 0000 cf74 0007 0000 0000 0001 0000
0000100 5af5 0000 6bb8 0008 0001 001e 0002 0000
0000110 5af5 0000 6bb8 0008 0000 0000 0001 0000
0000120 5af5 0000 07f8 0009 0001 001e 0002 0000
0000130 5af5 0000 07f8 0009 0000 0000 0001 0000
0000140 5af5 0000 a43a 0009 0001 001e 0002 0000
0000150 5af5 0000 a43a 0009 0000 0000 0001 0000
0000160 5af5 0000 407b 000a 0001 001e 0002 0000
0000170 5af5 0000 407b 000a 0000 0000 0001 0000
0000180 5af5 0000 dcbd 000a 0001 001e 0002 0000
0000190 5af5 0000 dcbd 000a 0000 0000 0001 0000
00001a0 5af5 0000 dcd0 000a 0001 001e 0000 0000
00001b0 5af5 0000 dcd0 000a 0000 0000 0000 0000

  从上面测试程序可以看到,按下按键时驱动上报的值为1,松开按键时驱动上报的值为0,当按住按键不放的时候上报的值为1,但后面还会上报2表示重复同样的事件。这个也可以从hexdump命令验证,其中这些值的含义如下:

编号           秒         毫秒      type   code     value
0000000    5af3 0000   e0ef 0008   0001   001e   0001 0000
0000010    5af3 0000   e0ef 0008   0000   0000   0000 0000
0000020    5af3 0000   b5a8 000a   0001   001e   0000 0000
0000030    5af3 0000   b5a8 000a   0000   0000   0000 0000

5、内核自带的input按键驱动

内核自带的input按键驱动在内核源码drivers/input/keyboard/gpio_keys.c里,相应的配置路径为:

Device Drivers  --->
	Input device support  --->
		-*- Generic input layer (needed for keyboard, mouse, ...)
			[*] Keyboards  --->
				<*> GPIO Buttons

可以参考内核文档Documentation/devicetree/bindings/input/gpio-keys.txt修改设备树中的节点:

gpio_keys: gpio_keys@0 {
    
    
    compatible = "gpio-keys";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_gpio_keys>;
    #address-cells = <1>;
    #size-cells = <0>;
    autorepeat;
    key1@1 {
    
    
        label = "USER-KEY1";
        linux,code = <114>;
        gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
        gpio-key,wakeup;
    };
};

附:对应的设备树节点

arch/arm/boot/dts/imx6ull-14x14-evk.dts:

/ {
    
    
	model = "Freescale i.MX6 ULL 14x14 EVK Board";
	compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
	...
	interrupt_key {
    
    
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "test-key";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_gpio_keys>;
		key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
		interrupt-parent = <&gpio1>;
		interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
		status = "okay";
	};
   ...
};

猜你喜欢

转载自blog.csdn.net/weixin_44498318/article/details/110336769