Linux Input 子系统

一、基本框架分析

输入子系统主要由三部分构成: 核心层(input.c)、事件处理层(evdev.c, joydev.c, mousedev.c 等等)、设备驱动层(具体硬件设备相关的驱动,比如按键驱动,触摸屏驱动等等 ) 

一个输入事件从产生到应用捕获的流程是: 产生事件(鼠标移动,触摸点击,按键等) -> 设备驱动捕获事件(通过硬件中断响应) , 上报事件(input_evnet) -> 事件处理层获取到事件, 上报用户 -> 用户获取事件进行逻辑处理。

事件处理层和设备层通过中间核心层接口进行注册和匹配,一个设备驱动可以匹配到多个事件处理层,产生的事件会上报到匹配到的事件处理层。

1、核心层 - input.c

这一层是linux内核实现的的一些通用的接口,可以向事件处理层和设备驱动层提供一些公用函数

  1. input_register_handler /input_unregister_handler -> 向事件处理层提供注册/释放一个hander的接口
  2. input_register_device /input_unregister_device   -> 向设备驱动层提供注册/释放设备的接口(设备驱动程序会用到这两个接口来注册input设备)
  3. input_register_handle/input_unregister_handle -> 设备和事件匹配成功后会注册一个handle,通过这个handle来绑定上面的handler和device
  4. input_event  -> 上报一个输入事件,由device驱动层调用上报到应用端去处理(设备驱动程序会调用该接口来上报事件,比如按键,鼠标等等)

以上就是input 子系统常用的接口,更多接口可阅读源码(driver\input\input.c)

2、事件处理层 - input_handler

这一层一般也是内核自己写好几个典型的事件处理驱动,比如 evdev.c, joydev.c,mousedev.c,keyboard.c. 调用 input_register_handler 来注册一个handler,注册handler的时候会去查找有没有匹配的device, 如果匹配成功, handler.connect 函数就会被调用。系统实现的这些事件处理驱动就够用了,不需要自己再去添加。input_handler的结构如下:

struct input_handler {

	void *private;

	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	void (*events)(struct input_handle *handle,
		       const struct input_value *vals, unsigned int count);
	bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	bool (*match)(struct input_handler *handler, struct input_dev *dev);
	int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
	void (*disconnect)(struct input_handle *handle);
	void (*start)(struct input_handle *handle);

	bool legacy_minors;
	int minor;
	const char *name;

	const struct input_device_id *id_table;

	struct list_head	h_list;
	struct list_head	node;
};

3、设备驱动层 - input_device

这一层就是具体的驱动程序,这一层就是需要我们自己来实现的。我们先分配一个input_dev结构指针,然后可以调用input_register_device 函数来注册一个input_device,同时在注册的时候也会去扫描有没有匹配的handler, 如果有匹配的,handler.connect 就会被调用。注册一个input device的具体步骤如下:

  1. 分配一个input_dev   -- input_allocate_device
  2. 初始化input_dev成员变量
  3. 做一些硬件相关的操作,比如中断申请等
  4. 注册一个input_dev  --  input_register_device

4、input_dev、input_handler 以及 input_handle之间的关系

依赖关系图如下:

 注册input_handler的时候会去扫描input_dev_list 链表中有没有匹配的input_dev。同时在注册input_dev的时候也会去扫描input_handler_list 链表查找是否有与之匹配的input_handler 。如果input_device 和 input_handler有匹配成功的,input_handler ->connect函数就会调用。

connect 函数里面会为input_device 分配一个minor(次设备号),并且注册一个handle,将input_dev 和 input_handler 地址分别赋值给 handle.dev 和 handle.handler,通过一个handle将input_dev和input_handler 绑定起来。

input_register_handle 调用时,将handle->d_node 添加到 input_dev->h_list,  将handle->h_node 添加到input_handler->h_list中,这样在input_dev中就能通过input_dev->h_list 找到input_handle, 然后根据input_handle->handler成员找到对应的事件处理层。同理也可以通过input_handler ->h_list 找到input_handle, 然后再通过input_handle->dev 成员找到对应的input_dev。至此input_dev和input_handler就能通过一个input_handle 一一对应起来。设备和事件处理层就连同通了,事件处理层上面的就是用户层。input_register_handle源码如下:

/**
 * input_register_handle - register a new input handle
 * @handle: handle to register
 *
 * This function puts a new input handle onto device's
 * and handler's lists so that events can flow through
 * it once it is opened using input_open_device().
 *
 * This function is supposed to be called from handler's
 * connect() method.
 */
int input_register_handle(struct input_handle *handle)
{
	struct input_handler *handler = handle->handler;
	struct input_dev *dev = handle->dev;
	int error;

	/*
	 * We take dev->mutex here to prevent race with
	 * input_release_device().
	 */
	error = mutex_lock_interruptible(&dev->mutex);
	if (error)
		return error;

	/*
	 * Filters go to the head of the list, normal handlers
	 * to the tail.
	 */
	if (handler->filter)
		list_add_rcu(&handle->d_node, &dev->h_list);
	else
		list_add_tail_rcu(&handle->d_node, &dev->h_list);

	mutex_unlock(&dev->mutex);

	/*
	 * Since we are supposed to be called from ->connect()
	 * which is mutually exclusive with ->disconnect()
	 * we can't be racing with input_unregister_handle()
	 * and so separate lock is not needed here.
	 */
	list_add_tail_rcu(&handle->h_node, &handler->h_list);

	if (handler->start)
		handler->start(handle);

	return 0;
}

二、 基于input子系统实现的按键驱动实例

这个实例是基于 imx6ull 开发板, Linux4.9.80 内核实现的, 代码如下:


#include <linux/device.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/timer.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/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 <asm/current.h>
#include <asm/io.h>


static struct input_dev *input_key_dev;

struct input_gpio_key{
	int m_gpio;   //引脚号
	struct gpio_desc * m_gpiod;
	int m_irq;
	int m_flag;
	int m_code;
	struct timer_list m_timer;
} ;

static struct input_gpio_key * input_keys_gpio;

static irqreturn_t input_key_irq(int irq, void *dev_id)
{
     struct input_gpio_key * key_desc = (struct input_gpio_key *)dev_id;
	 printk(KERN_INFO "irq:key_desc->m_code=%d\n", key_desc->m_code);
     mod_timer(&key_desc->m_timer, jiffies+HZ/100);
     return IRQ_RETVAL(IRQ_HANDLED);
}


void  input_key_timer_fn(unsigned long  arg)
{
	int val;
	struct input_gpio_key * key_desc = (struct input_gpio_key *)arg;
	val = gpiod_get_value(key_desc->m_gpiod);

	/* 上报按键值 0: 按下 1: 抬起     */
	input_report_key(input_key_dev, key_desc->m_code, val);
    input_sync(input_key_dev);

}

static int input_key_probe(struct platform_device *pdev)
{
	int retval = 0;
	int  err, i;
	int count;
	enum of_gpio_flags flags;
	struct device_node * node = pdev->dev.of_node;
	count = of_gpio_count(node); 						//获取设备节点下 gpios属性有几个gpio
	if(!count){
		retval = -1;
		goto exit_entry;
	}
	input_keys_gpio = kmalloc(sizeof(struct input_gpio_key) * count, GFP_KERNEL);
	if(!input_keys_gpio){
		retval = -1;
		goto exit_entry;
	}
	for(i = 0; i < count; i++){
		input_keys_gpio[i].m_gpio = of_get_gpio_flags(node, i, &flags);
		input_keys_gpio[i].m_gpiod = gpio_to_desc(input_keys_gpio[i].m_gpio); //根据gpio引脚转换成gpio desc
		input_keys_gpio[i].m_irq = gpiod_to_irq(input_keys_gpio[i].m_gpiod);  //获取gpio中断号
		input_keys_gpio[i].m_flag = flags & OF_GPIO_ACTIVE_LOW;       //只获取低电平触发中断的标志
		input_keys_gpio[i].m_code = KEY_1+i;  //user key1,2
		setup_timer(&input_keys_gpio[i].m_timer, input_key_timer_fn, (unsigned long)&input_keys_gpio[i]);
		add_timer(&input_keys_gpio[i].m_timer);
	}

	//申请中断
	for(i = 0; i < count; i++){
		err = request_irq(input_keys_gpio[i].m_irq, input_key_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "input_gpio_key", &input_keys_gpio[i]);
	}
	
	input_key_dev = input_allocate_device(); //分配一个 input_dev 结构体
	if(input_key_dev == NULL){
		printk(KERN_ERR "input_allocate_device filed");
		retval = -1;
		goto exit_entry;
	}
	input_key_dev->name = "input_key_dev";

	//设置支持的输入事件类型
	set_bit(EV_KEY, input_key_dev->evbit);
	//set_bit(EV_LED, input_key_dev->evbit);

	//设置key支持的事件码(支持哪些按键)
	set_bit(KEY_1, input_key_dev->keybit);
	set_bit(KEY_2, input_key_dev->keybit);
	

	//注册一个输入设备
	retval = input_register_device(input_key_dev);

exit_entry:
	return retval;
}

static int input_key_remove(struct platform_device *pdev)
{
	int retval = 0;
	int count, i;

	count = of_gpio_count(pdev->dev.of_node);
	for(i = 0 ; i < count; i++){
		free_irq(input_keys_gpio[i].m_irq, &input_keys_gpio[i]);
		del_timer(&input_keys_gpio[i].m_timer);
	}
	kfree(input_keys_gpio);
	
	if(input_key_dev != NULL){
		input_unregister_device(input_key_dev);
		input_free_device(input_key_dev);
		input_key_dev = NULL;
	}

	return retval;
}


static const struct of_device_id	input_key_table[] = {
	{.compatible = "100ask,gpio_key"},
	{ },
};


static struct platform_driver input_key_driver = {
	.probe		= input_key_probe,
	.remove		= input_key_remove,
	.driver		= {
		.name	= "InputKey",    //会根据这个名字去匹配device
		.of_match_table = input_key_table,
	},
};

static int __init input_key_init(void)
{
	platform_driver_register(&input_key_driver);  //注册platfrom 驱动
	return 0;
}


static void __exit input_key_exit(void)
{
	platform_driver_unregister(&input_key_driver);	
}


module_init(input_key_init);
module_exit(input_key_exit);
MODULE_LICENSE("GPL");


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深耕嵌入式

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值