目录
输入事件驱动层(handler层)源码分析------- evdev.c
2.编写request_irq():请求和注册一个中断处理函数
一、什么是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