Linux驱动input子系统基础之按键

目录

一、什么是input子系统

​编辑

二、input子系统简介

三、应用层编程实践

1、确定设备文件名

2、标准接口打开并读取文件

3、读取struct input_event

4、解析键盘或鼠标事件数据

完整应用层程序:

四、input子系统架构总览

输入设备驱动层源码分析(轮询方式)(⭐)

输入核心层源码分析-------- input.c

【1】核心层模块注册input_init

【2】核心层向设备驱动层提供的接口函数

【3】核心层向handler层即事件驱动层的接口函数

输入事件驱动层(handler层)源码分析------- evdev.c

轮询方式驱动按键源码分析(Button-x210.c)

【1】先找到bsp中按键驱动源码

【2】按键驱动源码分析

中断方式按键驱动实战:

1.有模板:

2.步骤:

2.编写request_irq():请求和注册一个中断处理函数

3.实践


一、什么是input子系统

linux中管理所有的输入类设备的体系。(例如鼠标、触摸屏、按键、键盘等等)

input 子系统分为 input 驱动层、input 核心层、input 事件处理层,最终给用户空间提供可访问的设备节点,input 子系统框架如图所示:

linux中输入设备的编程模型

struct input_event(kernel/include/linux/input.h)

struct input_event {
	struct timeval time;//时间(时刻)
	__u16 type;//类型的编号(以键盘为例)
	__u16 code;//编码值(那个按键)
	__s32 value;//操作值(谈起还是按下)
};  

用这个结构体描述一个输入类事件,例如按一下按键或者动一下鼠标就是一个事件

二、input子系统简介

(1)input子系统解决了什么问题:

将各种不同类型的输入设备、不同的寄存器、不同操作方法囊括起来

(2)input子系统分4个部分
  应用层 + input event (生成一个struct input_event变量并从驱动层传输到应用层)+ input core(驱动框架) + 硬件驱动(驱动工程师编写的硬件操作部分)

(3)input子系统如何工作
  以鼠标为例:在未有任何事件发生时,这部分代码处于静止状态,等待中断,鼠标按下触发中断,将事件上报到input core,将数据生成一个input event结构体变量,将这个传输到应用层(信息包括时间、鼠标、左键、按下)

三、应用层编程实践

1、确定设备文件名

(1)应用层操作驱动有2条路:/dev目录下的设备文件,/sys目录下的属性文件

(2)input子系统用的/dev目录下的设备文件,具体一般都是在 /dev/input/eventn

(3)用cat命令来确认某个设备文件名对应哪个具体设备

2、标准接口打开并读取文件

#define DEVICE_KEY			"/dev/input/event1"
int fd = -1;
fd=open(DEVICE_KEY,O_RDONLY);
if(fd<0)
{
    perror("open");
    return -1;
}

3、读取struct input_event

struct input_event dev;
memset(&dev,0,sizeof(struct input_event));
ret=read(fd,&dev,sizeof(struct input_event));

4、解析键盘或鼠标事件数据

printf("------------------------------------\n");
printf("type:%d\n", dev.type);
printf("code:%d\n", dev.code);
printf("value:%d\n", dev.value);
printf("\n");
/*kernel/include/linux/input.h
 * Event types
 */

#define EV_SYN			0x00 //同步类型,若struct input_event 结构体中的type值为0,
表示这个数据是一个同步数据包,用于在应用层和驱动层数据同步不同输入设备的事件,
在一次数据上报完成后会发一个同步数据包,同步数据包之后就是下一个事件,两个同步数据包之间为一次事件。

#define EV_KEY			0x01//按键,键盘一定是按键,按键不一定是键盘
#define EV_REL			0x02//relative,相对的,对应鼠标的移动使用相对坐标
#define EV_ABS			0x03//absolute,绝对的,对应触摸屏(使用全局坐标,绝对坐标系)
#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)
/*

完整应用层程序:

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

#define DEVICE_KEY    "/dev/input/event1"
#define DEVICE_MOUSE  "/dev/input/event3"


int main(int argc, char *argv[])
{
	int fd = -1, ret = -1;
	struct input_event dev;
	
	//第一步:打开设备文件
	//fd = open(DEVICE_KEY, O_RDONLY);
	fd = open(DEVICE_MOUSE, O_RDONLY);
	if (fd < 0)
	{
		perror("open failure.");
		return -1;
	}

	while(1)
	{
		//第二步:读取一个event事件包	
		memset(&dev, 0, sizeof(struct input_event));
		ret = read(fd, &dev, sizeof(struct input_event));
		if (ret != sizeof(struct input_event))
		{
			perror("read failure.");
			break;
		}
		
		//第三步:解析event包,知晓发生了什么样的输入事件
		printf("------------------------------------\n");
		printf("type:%d\n", dev.type);
		printf("code:%d\n", dev.code);
		printf("value:%d\n", dev.value);
		printf("\n");
		
	}

	//第四步:关闭设备文件
	close(fd);

	return 0;
}	

程序执行结果:

操作键盘:

type:4//键盘
code:4//实验发现按键时不论哪个键,这个值一直是4
value:6 //代表按下的是哪个键

------------------------------------
type:1//按键
code:6
value:0

------------------------------------
type:0//同步数据包
code:0
value:0

------------------------------------
type:4
code:4
value:20

------------------------------------
type:1
code:20
value:0

------------------------------------
type:0
code:0
value:0
操作鼠标:

type:0
code:0
value:0

------------------------------------
type:1//按键,操作鼠标上的按键时
code:272
value:1//按下

------------------------------------
type:0
code:0
value:0

------------------------------------
type:1
code:272
value:0//弹起

------------------------------------
type:3//移动鼠标
code:0
value:39677

------------------------------------
type:0
code:0
value:0

------------------------------------
type:3
code:0//0代表REL_X水平方向移动,1代表REL_Y竖直方向,REL:相对坐标
value:39718

------------------------------------
type:0
code:0
value:0

四、input子系统架构总览

【1】input子系统分为三层

  • 最上层:输入事件驱动层,evdev.c和mousedev.c和joydev.c属于这一层
  • 中间层:输入核心层,input.c属于这一层
  • 最下层:输入设备驱动层,drivers/input/xxx 文件夹下

【2】input类设备驱动开发方法

  • 输入事件驱动层和输入核心层不需要动,只需要编写设备驱动层
  • 设备驱动层编写的接口和调用模式已定义好,驱动工程师的核心工作量是对具体输入设备硬件的操作和性能调优。

输入设备驱动层源码分析(轮询方式)(⭐)


【1】先找到bsp中按键驱动源码

  • 锁定目标:板载按键驱动
  • 确认厂家提供的BSP是否已经有驱动
  • 找到bsp中的驱动源码
    在这里插入图片描述
    在这里插入图片描述

【2】按键驱动源码初步分析

  • 模块装载分析
    在这里插入图片描述

  • 平台总线相关分析

  • 确定重点:probe函数
    在这里插入图片描述

【3】源码细节实现分析

  • gpio_request

在这里插入图片描述

  • input_allocate_device
    在这里插入图片描述

  • input_register_device
    在这里插入图片描述

  • timer

在这里插入图片描述

输入核心层源码分析-------- input.c


【1】核心层模块注册input_init

  • class_register
  • input_proc_init
  • register_chrdev

在这里插入图片描述

【2】核心层向设备驱动层提供的接口函数

  • input_allocate_device:分配一块input_dev结构体类型大小的内存

在这里插入图片描述

  • input_set_capability:设置输入设备可以上报哪些输入事件
  •  函数原型:input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
  •  dev就是设备的input_dev结构体变量
  •  type表示设备可以上报的事件类型
  •  code表示上报这类事件中的那个事件

注意:input_set_capability函数一次只能设置一个具体事件,如果设备可以上报多个事件,则需要重复调用这个函数来进行设置
例如:

input_set_capability(dev, EV_KEY, KEY_Q);     // 至于函数内部是怎么设置的,将会在后面进行分析。
input_set_capability(dev, EV_KEY, KEY_W);
input_set_capability(dev, EV_KEY, KEY_E);
  • input_register_device:下层的设备驱动层向input核心层注册设备
    在这里插入图片描述
    在这里插入图片描述

input_attach_handler就是input_register_device函数中用来对下层的设备驱动和上层的handler进行匹配的一个函数,只有匹配成功之后就会调用上层handler中的connect函数进行连接绑定。

【3】核心层向handler层即事件驱动层的接口函数

  • input_register_handler
    在这里插入图片描述

  • input_register_handle:简单的挂接一些数据结构,注意区分即可
    在这里插入图片描述

输入事件驱动层(handler层)源码分析------- evdev.c


在这里插入图片描述

【1】input_handler

在这里插入图片描述

 【2】evdev_event
在这里插入图片描述

【3】evdev_connect

在这里插入图片描述
在这里插入图片描述

轮询方式驱动按键源码分析(Button-x210.c)

【1】先找到bsp中按键驱动源码

  • 锁定目标:板载按键驱动
  • 确认厂家提供的BSP是否已经有驱动
  • 找到bsp中的驱动源码
    在这里插入图片描述
    在这里插入图片描述

【2】按键驱动源码分析

模块装载分析
在这里插入图片描述

平台总线相关分析

  • 确定重点:probe函数
    在这里插入图片描述

probe函数实现细节分析

  • gpio_request

在这里插入图片描述

  • input_allocate_device
    在这里插入图片描述

  • input_register_device
    在这里插入图片描述

  • timer

在这里插入图片描述

中断方式按键驱动实战:

1.有模板:

kernel/Documentation/input/input-programming.txt
(1)input类设备驱动模式非常固定,用参考模版修改即可
(2)新建驱动项目并粘贴模版内容

2.步骤:

1.分配一个 GPIO 引脚资源,将该引脚配置为 EINT2 模式,按键对应的GPIO口设为外部中断模式

error = gpio_request(S5PV210_GPH0(2), "GPH0_2");
	if(error)
		printk("key-s5pv210: request gpio GPH0(2) fail");
	s3c_gpio_cfgpin(S5PV210_GPH0(2), S3C_GPIO_SFN(0x0f));		// eint2模式

2.编写request_irq():请求和注册一个中断处理函数

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
{
	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

PS:裸机中中断处理程序不需要返回,但操作系统中是需要返回的,操作系统需要对系统的软硬件情况做一个完全的掌控。

第一个参数:unsigned int irq:

先看看操作的按键对应的GPIO分别是哪些,本实践我驱动LEFT按键。

/*
 * 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)
 */

因为unsigned int irq这个中断号并非数据手册的,而是内核里重新规定的,类似gpio的编号。

所以要去(kernel/arch\arm\mach-s5pv210\include\mach\irqs.h)中找到

并#define为BUTTON_IRQ:

#define BUTTON_IRQ    IRQ_EINT2

第二个参数:irq_handler_t handler   //中断处理程序

第三个参数:unsigned long flags:设置上升沿、下降沿、高低电平等等触发什么的

unsigned long flags:标志位
部分:
#define IRQF_TRIGGER_NONE	0x00000000
#define IRQF_TRIGGER_RISING	0x00000001//上升沿
#define IRQF_TRIGGER_FALLING	0x00000002//下降沿
#define IRQF_TRIGGER_HIGH	0x00000004
#define IRQF_TRIGGER_LOW	0x00000008
#define IRQF_TRIGGER_MASK	(IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
				 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE	0x00000010

第四个参数:const char *name:申请的中断号的名字

第五个参数:void *dev:传递参数,若不需要,传一个NULL进去即可。

写出来是这样:

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; 
	}
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_sync(button_dev);
	return IRQ_HANDLED; 
}

编写中断处理程序(把io口切换为input模式是为了把值读出来,都出来后再切换回去)

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_sync(button_dev);
	return IRQ_HANDLED; 
}

配置一个输入设备 button_dev,然后设置该输入设备支持的事件类型和按键:

告诉输入子系统,button_dev 是一个键盘类输入设备,支持左箭头键(KEY_LEFT)这个按键事件。当用户按下或释放左箭头键时,该事件将被生成并传递给输入子系统

button_dev = input_allocate_device();
	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);

注册配置好的输入设备 button_dev,以便将其添加到系统中,使其可以捕获和处理硬件事件

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;

3.实践

cat /proc/interrupts:查看已注册的中断

配置内核,禁用它原有的按键驱动,把使用gpio资源释放出来,修改arch/arm/mach-s5pv210/Makefile

obj-$(CONFIG_MACH_SMDKV210)     += button-x210.o

注释掉,不可把这个宏去掉,因为这个宏牵扯了很多的代码。

实验效果:
执行应用程序时按下按键left的实验现象

type:1
code:105
value:1

------------------------------------
type:0
code:0
value:0

------------------------------------
type:1
code:105
value:0

------------------------------------
type:0
code:0
value:0
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值