【Linux驱动: Input子系统分析】

input子系统总体架构

输入子系统由核心层(Input Core)、驱动层和事件处理层(Event Handler)三部份组成,如下图所示,一个输入事件通过底层硬件-> Driver -> InputCore ->Eventhandler -> userspace 的顺序到达用户空间传给应用程序。
在这里插入图片描述



调用层次关系:
在这里插入图片描述

驱动层: 将底层的硬件输入事件转化为统一事件形式,向输入核心(Input Core)汇报。

核心层: 承上启下:对输入子系统进行了抽象, 对下提供了设备驱动的注册接口,对上提供了Event Handler层的注册接口。

事件层: 和用户层交互,提供设备的read和write等函数。

主要的数据结构

数据结构用途位置使用的地方
Struct input_dev驱动层物理Input设备的基本数据结构input.h通常在具体的设备驱动中分配和填充
Struct Evdev 、Struct Mousedev、Struct Keybdev…Event Handler层逻辑Input设备的数据结构Evdev.c、Mousedev.c、Keybdev.cEvdev.c/Mouedev.c …中分配
Struct Input_handler定义一个Event Handler类型的结构体Input.hEvent Handler层,定义一个具体的Event Handler
Struct Input_handle用来创建关联驱动层Dev和Handler链表的链表项结构Input.hEvent Handler层中分配,包含在Evdev/Mousedev…中。
Struct input_event驱动层向核心层上报数据的结构Input.h在驱动层中断函数中创建

其中input_dev和input_handler两个结构体尤为重要,分别代表了驱动层和hadler层对象的数据结构。
在这里插入图片描述
在这里插入图片描述
这两个结构体匹配之后使用input_handle保存2者之间的联系。
在这里插入图片描述

handler 事件层

handler事件层负责把核心层上报的数据进行转换,然后上报到用户区间。其中转换的数据格式是固定的,在/linux/input.h中定义如下:
在这里插入图片描述
需要注意的是,hander的类型不只一种,可以是key_board handler也可以是evdev 等如下图:
在这里插入图片描述
每个handlder都可以用上面介绍到的struct input_handler结构体表示并进行注册。注册接口和注册流程如下:

  • 注册接口: int input_register_handler(struct input_handler *handler)
    。 把input_handler 放入input.c的input_handler_list链表
    。 对于input.c的input_dev_list链表中的每一个input_dev,进行一一比较
    。 如果匹配则调用input_handler.connect

以evder_handler为例
在这里插入图片描述

核心层

核心层主要就是input.c和input.h维持的代码,主要是创建了input类和对应的字符设备,提供文件系统下的操作目录。
提供且维护两个链表,用来维持管理和匹配上下两层的handler和input_dev。
在这里插入图片描述
提供了响应的接口给上下两层进行注册,卸载,消息上报等操作。

int input_register_device(struct input_dev *dev)
void input_unregister_device(struct input_dev *dev)
int input_register_handler(struct input_handler *handler)
void input_unregister_handler(struct input_handler *handler)
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)//调用input_match_device 进行匹配和handler->connect(handler, dev, id);进行链接
struct input_dev *input_allocate_device(void)
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)//用来进行事件上报,一般在中断或者时间中断轮询处理函数中使用(最后其实是调用之前注册的handler->event时间进行处理,一般是唤醒一个等待事件)
static inline void input_sync(struct input_dev *dev); // 事件同步,实质也是 input_event

驱动层

驱动层就是对接具体设备的一层了,该层是需要驱动开发者根据具体硬件和需求去实现的。
除了完成对硬件的操作(主要是读),驱动层主要利用核心层的接口进行注册、卸载、消息上报等功能。
每个驱动层设备用上面介绍的数据结构input_dev进行描述。

  • 注册接口:int input_register_device(struct input_dev *dev)
    。 把input_dev放入input.c的input_dev_list链表
    。 对于input.c的input_handler_list链表中的每一个input_handler,一一比较
    。 如果匹配则调用input_handler.connect

驱动实例–按键(时间轮询)

参考button-x210.c

驱动实例–按键(中断触发)

#include <linux/input.h> 
#include <linux/module.h> 
#include <linux/init.h>
#include <asm/irq.h> 
#include <asm/io.h>

#include <mach/irqs.h>			// arch/arm/mach-s5pv210/include/mach/irqs.h
#include <linux/interrupt.h>
#include <linux/gpio.h>

/*
 * X210:
 *
 * POWER  -> EINT1   -> GPH0_1
 * LEFT   -> EINT2   -> GPH0_2
 * DOWN   -> EINT3   -> GPH0_3
 * UP     -> KP_COL0 -> GPH2_0
 * RIGHT  -> KP_COL1 -> GPH2_1
 * MENU   -> KP_COL3 -> GPH2_3 (KEY_A)
 * BACK   -> KP_COL2 -> GPH2_2 (KEY_B)
 */

#define BUTTON_IRQ	IRQ_EINT2 //定义中断号,IRQ_EINT2 在mach/irqs.h中定义

static struct input_dev *button_dev;

static irqreturn_t button_interrupt(int irq, void *dummy) 
{ 
	int flag;

	s3c_gpio_cfgpin(S5PV210_GPH0(2), S3C_GPIO_SFN(0x0));		//配置为 input模式
	flag = gpio_get_value(S5PV210_GPH0(2));//读取值
	s3c_gpio_cfgpin(S5PV210_GPH0(2), S3C_GPIO_SFN(0x0f));		//重新配置为 eint2模式,为了下一次继续作为中断使用

	input_report_key(button_dev, KEY_LEFT, !flag);//调用了input核心层的接口,对数据进行上报,最后会调用handler层的input_event函数如:evdev_handler->event 即evdev_event,里面会唤醒响应的等待队列 wake_up_interruptible(&evdev->wait);
	input_sync(button_dev);//同上
	return IRQ_HANDLED; 
}

static int __init button_init(void) 
{ 
	int error;
	
	error = gpio_request(S5PV210_GPH0(2), "GPH0_2");//申请GPIO资源
	if(error)
		printk("key-s5pv210: request gpio GPH0(2) fail");
	s3c_gpio_cfgpin(S5PV210_GPH0(2), S3C_GPIO_SFN(0x0f));		//配置GPIO中断 eint2模式
	
	//申请中断资源
	if (request_irq(BUTTON_IRQ, button_interrupt, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "button-x210", NULL)) 
	{ 
		printk(KERN_ERR "key-s5pv210.c: Can't allocate irq %d\n", BUTTON_IRQ);
		return -EBUSY; 
	}
	button_dev = input_allocate_device();//申请分配一个input_dev内存
	if (!button_dev) 
	{ 
		printk(KERN_ERR "key-s5pv210.c: Not enough memory\n");
		error = -ENOMEM;
		goto err_free_irq; 
	}
	
	//对申请到的内存进行填充,主要是填充响应的事件、键值
	button_dev->evbit[0] = BIT_MASK(EV_KEY);//填充事件
	button_dev->keybit[BIT_WORD(KEY_LEFT)] = BIT_MASK(KEY_LEFT);//填充键值
	
	error = input_register_device(button_dev);//实际注册
	if (error) 
	{ 
		printk(KERN_ERR "key-s5pv210.c: Failed to register device\n");
		goto err_free_dev; 
	}
	return 0;
	
err_free_dev:
	input_free_device(button_dev);
err_free_irq:
	free_irq(BUTTON_IRQ, button_interrupt);
	return error; 
}

static void __exit button_exit(void) 
{ 
	input_unregister_device(button_dev); 
	free_irq(BUTTON_IRQ, button_interrupt); 
}

module_init(button_init); 
module_exit(button_exit); 

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zcc");
MODULE_DESCRIPTION("key driver for x210 button.");

参考:
https://blog.csdn.net/thisway_diy/article/details/119907677
https://blog.csdn.net/qq_17639223/article/details/119489552

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值