Linux---input输入子系统设备驱动写法 input_allocate_device()、input_event()、input_sync()

一、输入子系统input_dev结构体注册过程及事件上报流程

1、使用 input_allocate_device 函数申请一个 input_dev

struct input_dev *input_allocate_device(void)

input_dev结构体如下(有省略):

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)];
	... ...
};

2、设置input_dev 的事件类型以及事件值

使用__set_bit设置input结构体里面的事件类型evbit,及事件值keybit(假设是按键事件)

3、使用 input_register_device 函数向 Linux 系统注册前面申请并已初始化好的 input_dev

int input_register_device(struct input_dev *dev)

4、事件上报

使用input_event() 进行事件上报,对于确定类型的事件也可以使用对应类型的上报函数,比如key类型,可以用input_report_key(),然后进行事件同步input_sync()

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

static inline void input_report_key(struct input_dev *dev, 
				unsigned int code, int value)
{
	input_event(dev, EV_KEY, code, !!value);
}

static inline void input_sync(struct input_dev *dev)
{
	input_event(dev, EV_SYN, SYN_REPORT, 0);
}

dev:需要上报的 input_dev。
type: 上报的事件类型,比如 EV_KEY。
code: 事件码,也就是我们注册的按键值,比如 KEY_L、 KEY_S等等。
value:事件值,比如 1 表示按键按下, 0 表示按键松开。

二、驱动实例

#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/input.h>
/* https://blog.csdn.net/lihuan680680/article/details/121325980 by 面朝大海0902*/
struct gpio_key{
	int gpio;
	struct gpio_desc *gpiod;
	int flag;
	int irq;
};

struct input_dev *key_input;
static struct gpio_key *ptr_gpio_key;
struct timer_list key_timer;

void timer_function(unsigned long arg)
{
	int value;
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
	struct gpio_key *ptr_gpio_key_temp = arg;
	
	if(ptr_gpio_key_temp == NULL)
		return;
	
	value = gpiod_get_value(ptr_gpio_key_temp->gpiod);

	if(value == 0)
	{
		if(ptr_gpio_key_temp->gpio == ptr_gpio_key[0].gpio)
		{
			input_report_key(key_input, KEY_L, 1);
		}
		else
		{
			input_report_key(key_input, KEY_S, 1);
		}
	}
	else
	{
		if(ptr_gpio_key_temp->gpio == ptr_gpio_key[0].gpio)
		{
			input_report_key(key_input, KEY_L, 0);
		}
		else
		{
			input_report_key(key_input, KEY_S, 0);
		}
	}
	input_sync(key_input);
}

static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	key_timer.data =(unsigned long) dev_id;
	mod_timer(&key_timer, jiffies + HZ/4);
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
	return IRQ_HANDLED;
}

static int key_probe(struct platform_device *pdev)
{
	int count = 0;
	int i=0;
	enum of_gpio_flags flag;
	struct device_node *node = pdev->dev.of_node;
	
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
	count = of_gpio_count(node);
	ptr_gpio_key = kzalloc(sizeof(struct gpio_key)*count, GFP_KERNEL);
	
	for(i=0;i<count;i++)
	{
		ptr_gpio_key[i].gpio = of_get_gpio_flags(node, i, &flag);
		if(ptr_gpio_key[i].gpio < 0)
		{
			printk(KERN_ERR "of_get_gpio_flags is err\r\n");
		}
		else
		{
			printk(KERN_INFO "gpio num is %d\r\n",ptr_gpio_key[i].gpio);
		}
		ptr_gpio_key[i].gpiod = gpio_to_desc(ptr_gpio_key[i].gpio);
		ptr_gpio_key[i].flag = flag & OF_GPIO_ACTIVE_LOW;
		ptr_gpio_key[i].irq = gpio_to_irq(ptr_gpio_key[i].gpio);
		request_irq(ptr_gpio_key[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "gpio_key", &ptr_gpio_key[i]);
	}

	/* 1、申请 input_dev */
	key_input = input_allocate_device();
	key_input->name = "key_input";
	
	/* 2、初始化 input_dev,设置产生哪些事件 */
	__set_bit(EV_KEY, key_input->evbit);
	__set_bit(EV_REP, key_input->evbit);

	/* 3、初始化 input_dev,设置产生哪些按键 */
	__set_bit(KEY_L, key_input->keybit);
	__set_bit(KEY_S, key_input->keybit);

	/* 4、注册输入设备 */
	input_register_device(key_input);
	
	init_timer(&key_timer);
	key_timer.function = timer_function;
	key_timer.expires = ~0;
	//add_timer(&key_timer);

	return 0;	
}



static int key_remove(struct platform_device *pdev)
{
	int count = 0;
	int i = 0;
	struct device_node *node = pdev->dev.of_node;

	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
	count = of_gpio_count(node);

	for(i=0;i<count;i++)
	{
		free_irq(ptr_gpio_key[i].irq, &ptr_gpio_key[i]);
	}

	kfree(ptr_gpio_key);
	del_timer(&key_timer);

	/* 释放 input_dev */
	input_unregister_device(key_input);
	input_free_device(key_input);

	return 0;	
}


static const struct of_device_id my_key[] =
{
	{.compatible = "my,key_driver"},
	{},
};


static struct platform_driver key_driver =
{
	.probe  = key_probe,
	.remove = key_remove,
	.driver = 
	{
		.name = "key_gpio",
		.of_match_table = my_key,
	},
};


static int __init my_key_init(void)
{
	int result;
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
	result = platform_driver_register(&key_driver);
	return result;
}


static void __exit my_key_exit(void)
{
	printk(KERN_INFO "%s %s line is %d \r\n", __FILE__, __FUNCTION__, __LINE__);
	platform_driver_unregister(&key_driver);
}

module_init(my_key_init);
module_exit(my_key_exit);
MODULE_LICENSE("GPL");

驱动程序初始化了2个按键,按键按下时会上报KEY_L或KEY_S类型按键值,模拟键盘“ls”,重点关注key_probe()初始化过程及timer_function()事件上报处理。
加载设备驱动input_key.ko后我们看到多了一个/dev/input/event1节点,hexdump节点并按键可以看到有事件上报的打印。

[root:~/Test]# insmod input_key.ko
[  330.832824] /home/book/code/test/input_key.c my_key_init line is 183
[  330.847402] /home/book/code/test/input_key.c key_probe line is 90
[  330.860931] gpio num is 129
[  330.864034] gpio num is 110
[  330.874126] input: key_input as /devices/virtual/input/input1
[root:~/Test]# ls -al /dev/input/event*
crw-rw---- 1 root input 13, 64 Jan  1 00:00 /dev/input/event0
crw-rw---- 1 root input 13, 65 Jan  1 00:05 /dev/input/event1
[root:~/Test]#
[root:~/Test]# hexdump /dev/input/event1
[  367.046446] /home/book/code/test/input_key.c gpio_key_isr line is 79
[  367.298456] /home/book/code/test/input_key.c gpio_key_isr line is 79
[  367.554496] /home/book/code/test/input_key.c timer_function line is 42
[  370.181289] /home/book/code/test/input_key.c gpio_key_isr line is 79
[  370.434402] /home/book/code/test/input_key.c timer_function line is 42
0000000 0172 0000 e725 0005 0001 0026 0001 0000
0000010 0172 0000 e725 0005 0000 0000 0000 0000
0000020 0172 0000 c47e 0009 0001 0026 0002 0000
[  370.969778] /home/book/code/test/input_key.c gpio_key_isr line is 79
00000e0 0172 0000 5834 000e 0001 0026 0002 0000
00000f0 0172 0000 5834 000e 0000 0000 0001 0000

三、实例测试

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <linux/input.h>

static struct input_event key_event;
int main(int argc, char **argv)
{
	int fd;
	int ret;
	
	/* 1. 判断参数 */
	if (argc != 2) 
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	while(1)
	{
		ret = read(fd, &key_event, sizeof(key_event));
		if(ret > 0)
		{
			switch(key_event.code)
			{
				case KEY_L:
					printf("code is KEY_L, value is %d\r\n", key_event.value);
					break;
				case KEY_S:
					printf("code is KEY_S, value is %d\r\n", key_event.value);
					break;
				default:
					//printf("type is %d ,code is %d, value is %d\r\n",key_event.type, key_event.code, key_event.value);
					break;
			}
		}
		else
		{
			printf("read error\r\n");
		}
	}

	close(fd);
	
	return 0;
}

测试程序读设备节点文件,然后将对应上报的事件进行解析并打印,测试打印如下:

[root:~/Test]# ls
input_key.ko  input_test
[root:~/Test]# chmod +x input_test
[root:~/Test]#
[root:~/Test]# ./input_test /dev/input/event1
[  923.278688] /home/book/code/test/input_key.c gpio_key_isr line is 79
[  923.534503] /home/book/code/test/input_key.c timer_function line is 42
code is KEY_L, value is 1
[  923.568936] /home/book/code/test/input_key.c gpio_key_isr line is 79
code is KEY_L, value is 2
[  923.824495] /home/book/code/test/input_key.c timer_function line is 42
code is KEY_L, value is 0
[  927.180227] /home/book/code/test/input_key.c gpio_key_isr line is 79
[  927.434446] /home/book/code/test/input_key.c timer_function line is 42
code is KEY_S, value is 1
code is KEY_S, value is 2
[  927.707812] /home/book/code/test/input_key.c gpio_key_isr line is 79
code is KEY_S, value is 2
code is KEY_S, value is 2
[  927.964523] /home/book/code/test/input_key.c timer_function line is 42
code is KEY_S, value is 0
[  930.993742] /home/book/code/test/input_key.c gpio_key_isr line is 79
[  931.244530] /home/book/code/test/input_key.c timer_function line is 42
code is KEY_S, value is 1
[  931.293970] /home/book/code/test/input_key.c gpio_key_isr line is 79
code is KEY_S, value is 2
[  931.544497] /home/book/code/test/input_key.c timer_function line is 42
code is KEY_S, value is 0
[  932.554714] /home/book/code/test/input_key.c gpio_key_isr line is 79
[  932.771783] /home/book/code/test/input_key.c gpio_key_isr line is 79
[  933.024525] /home/book/code/test/input_key.c timer_function line is 42

可以看到按键按下value为1,持续按下value为2,按键释放时value为0。这里value为何会为2,猜测是与我们设置事件类型有EV_REP有关,这里没有再深入分析了。

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
在开发输入子系统设备驱动时,通常遵循以下步骤: 1. 包含必要的头文件:在驱动程序的源文件中,你需要包含一些必要的头文件,例如`linux/input.h`、`linux/module.h`、`linux/init.h`等。 2. 定义驱动模块:使用`module_init`宏定义一个初始化函数,用来加载驱动程序。例如: ```c static int __init myinput_init(void) { // 驱动初始化逻辑 return 0; } module_init(myinput_init); ``` 3. 注册输入设备:在初始化函数中,你需要创建一个输入设备并进行注册。你可以使用`input_allocate_device`函数分配一个输入设备结构体,并设置一些属性,例如设备名称、支持的事件类型等。然后,使用`input_register_device`函数注册输入设备。例如: ```c static int __init myinput_init(void) { struct input_dev *myinput_dev; myinput_dev = input_allocate_device(); if (!myinput_dev) { pr_err("Failed to allocate input device\n"); return -ENOMEM; } // 设置设备名称、支持的事件类型等 input_register_device(myinput_dev); return 0; } ``` 4. 处理输入事件:注册完输入设备后,你需要实现一个中断处理函数或者定时器处理函数,用来处理输入事件。当触发输入事件时,驱动程序会调用该函数进行处理。你可以使用`input_report_*`系列函数上报输入事件,例如鼠标移动、按键按下等。例如: ```c static irqreturn_t myinput_interrupt(int irq, void *dev_id) { // 处理输入事件的逻辑 input_report_key(myinput_dev, KEY_A, 1); // 模拟按下 A 键 input_sync(myinput_dev); // 同步输入事件 return IRQ_HANDLED; } ``` 这只是一个简单的示例,实际的输入子系统设备驱动可能还需要处理更多的细节和特定的硬件接口。更详细的编写方法和实现细节可以参考Linux内核源码中的驱动示例和相关文档。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值