我的环境:
Fedora14 内核版本,2.6.38.1
开发板:TQ2440
移植内核版本:2.6.30.4
Linux输入子系统就是一个基于分层模式的系统,其基本的层次分解如下图所示。
在图中我们可以发现输入子系统主要包括三个部分设备驱动层(input driver)、核心层(input core)和输入事件驱动层。输入子系统的划分使得输入设备的驱动程序设计越来越简单,但是其中的思想采用我们学习的重点和难点。
Input子系统处理输入事务,任何输入设备的驱动程序都可以通过Input输入子系统提供的接口注册到内核,利用子系统提供的功能来与用户空间交互。输入设备一般包括键盘,鼠标,触摸屏等,在内核中都是以输入设备出现的。下面分析input输入子系统的结构,以及功能实现。
1. Input子系统是分层结构的,总共分为三层:硬件驱动层,子系统核心层,事件处理层。
(1)、其中硬件驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,需要驱动程序的作者来编写。
(2)、子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。
(3)、事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。
2. 各层之间通信的基本单位就是事件,任何一个输入设备的动作都可以抽象成一种事件,如键盘的按下,触摸屏的按下,鼠标的移动等。事件有三种属性:类型(type),编码(code),值(value),Input子系统支持的所有事件都定义在input.h中,包括所有支持的类型,所属类型支持的编码等。事件传送的方向是硬件驱动层-->子系统核心-->事件处理层-->用户空间。
在驱动程序设计中,我们对于设备的驱动设计主要集中在设备驱动层的实现,但是这与之前的设备驱动开发存在较大的差别,主要是因为设备驱动不再是编写基本操作的实现过程,也就是不在是对struct file_operations 这个结构体对象的填充和实现。在输入设备驱动中的主要实现包括下面几个过程:
1、分配一个输入设备对象。并完成响应结构体元素的填充,主要包括支持的事件类型和事件代号等。
分配对象的函数:
struct input_dev *input_allocate_device(void);
释放对象函数:
void input_free_device(struct input_dev *dev);
设置支持的事件类型和事件代码:
通常采用set_bit函数实现:
设置支持的事件类型(支持按键事件)
set_bit(EV_KEY, input_dev->evbit);
设置支持的事件代码(支持按键1)
set_bit(KEY_1, input_dev->keybit);
2、完成输入设备对象的注册,将设备对象注册到输入子系统当中去,当然也有对应的释放函数。
注册设备到内核:
int input_register_device(struct input_dev *dev);
注销设备:
void input_unregister_device(struct input_dev *dev);
3、向核心层(input core)汇报事件的发生以及传输事件类型和事件代码等。这一部分通常是采用中断的方法实现,在中断中向上一层次(Input Core)传送发生事件的事件类型、事件代号以及事件对应的值等。但是上报的内容结构体都是基于一个固定结构体的
struct input_event,在用户空间也可以采用这个结构体实现对事件的访问。
struct input_event {
/*事件发生的时间*/
struct timeval time;
/*事件类型*/
__u16 type;
/*事件代号*/
__u16 code;
/*事件对应的值*/
__s32 value;
};
基本的事件类型包括如下:
#define EV_SYN 0x00
/*常用的事件类型*/
#define EV_KEY 0x01
#define EV_REL 0x02
#define EV_ABS 0x03
#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)
支持的事件代号:
...
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_6 7
#define KEY_7 8
#define KEY_8 9
#define KEY_9 10
#define KEY_0 11
#define KEY_MINUS 12
#define KEY_EQUAL 13
#define KEY_BACKSPACE 14
…
主要的汇报函数如下:
/*汇报键值函数*/
void input_report_key(struct input_dev *dev, unsigned int code, int value);
/*汇报相对坐标值函数*/
void input_report_rel(struct input_dev *dev, unsigned int code, int value);
/*汇报绝对坐标值函数*/
void input_report_abs(struct input_dev *dev, unsigned int code, int value);
也可以采用更加一般的函数汇报,上面的三个函数是通过下面这个函数实现的。
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
完成上面的三个部分,一个输入设备的驱动程序也就完成了。但是其中的具体实现还需要阅读相关的源码。特别是设备的操作是如何通过输入设备实现等基本的操作是我们应该去关注的,设备的具体操作函数实质上已经因为一些共性被设计成了通用的接口,在输入子系统内部已经实现。
TQ2440中的按键主要是采用外部中断的形式实现,所以我在实验过程中也采用了外部中断的模式直接对按键进行操作。
具体的程序如下所示:驱动代码:
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/types.h>
#include<linux/slab.h>
#include<linux/string.h>
#include<linux/input.h>
#include<linux/irq.h>
#include<mach/gpio.h>
#include<mach/regs-gpio.h>
#include<asm/irq.h>
#include<asm/io.h>
#include<linux/interrupt.h>
#include<linux/delay.h> #define DEVICE_NAME "TQ2440_BUTTON" #define NUMBERS_BUTTONS 4 static const char * input_name = "Tq2440_button"; static struct input_dev *button_dev; /*中断集合*/
static const int irqs[NUMBERS_BUTTONS]={
IRQ_EINT0,
IRQ_EINT1,
IRQ_EINT2,
IRQ_EINT4,
}; /*端口集合*/
static const int gpios[NUMBERS_BUTTONS]={
S3C2410_GPF0,
S3C2410_GPF1,
S3C2410_GPF2,
S3C2410_GPF4,
};
/*事件代号集合*/
static const int keys[NUMBERS_BUTTONS]={
KEY_1,
KEY_2,
KEY_3,
KEY_4,
}; /*中断处理程序*/ static irqreturn_t button_interrupt(int irq,void * p)
{
int val = 0, i= 0;
for(i = 0; i < NUMBERS_BUTTONS; ++ i)
{
/*如果中断号正确*/
if(irqs[i] == irq)
{
/*读取端口的值*/
val = s3c2410_gpio_getpin(gpios[i]);
/*汇报端口的值,实质上是将事件key[i]汇报*/
input_report_key(button_dev,keys[i],val);
/*汇报同步,也就是完成汇报工作*/
input_sync(button_dev);
break;
}
}
/*返回值*/
return IRQ_RETVAL(IRQ_HANDLED);
} /*设备初始化函数*/ static int __init tq2440_button_init(void)
{
int err,i = 0;
/*主要要先申请设备,然后申请中断*/
/*申请输入设备*/
button_dev = input_allocate_device();
if(NULL == button_dev)
{
printk(KERN_ERR "not enough memory\n");
err = - ENOMEM;
}
/*设置相应的事件处理类型,按键事件*/
set_bit(EV_KEY,button_dev->evbit); /*申请中断*/
for(i = 0; i < NUMBERS_BUTTONS; ++ i)
{
/*申请中断*/
request_irq(irqs[i],button_interrupt,IRQ_TYPE_EDGE_BOTH,
DEVICE_NAME,NULL);
/*设置案件的代码值,即code值*/
set_bit(keys[i],button_dev->keybit);
}
/*设备名,防止出现错误*/
button_dev->name = input_name;
/*注册输入设备*/
err = input_register_device(button_dev);
if(err)
{
printk(KERN_ERR "failed to register device\n");
goto err_free_dev;
}
printk("initialized\n"); return 0; /*错误处理*/
err_free_dev:
/*注销设备*/
input_unregister_device(button_dev);
return err;
} /*设备退出函数*/
static void __exit tq2440_button_exit(void)
{
int i = 0;
input_unregister_device(button_dev); /*释放中断号*/
for(i = 0; i < NUMBERS_BUTTONS; ++ i)
{
free_irq(irqs[i],NULL);
}
} /*加载和卸载*/
module_init(tq2440_button_init);
module_exit(tq2440_button_exit); /*LICENSE和作者信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("GP-<gp19861112@yahoo.com.cn>");测试代码: #include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<linux/input.h> /*事件结构体*/
struct input_event buff; int main(int argc ,char **argv)
{
int fd;
int count;
/*打开设备文件*/
fd = open("/dev/event0",O_RDWR); if(fd == -1)
{
printf("Open Failed!\n"); exit(-1);
} while(1)
{
/*读操作*/
if(count = read(fd,&buff,sizeof(struct input_event))!=0)
{
printf("type: %d\tcode: %d\t value: %d\n",buff.type,buff.code,buff.value);
}
}
close(fd);
exit(0);
}
测试效果:
从效果上来看,代码基本上实现了按键的识别,但是该驱动程序的问题是按键并不能实现消抖操作。
这一年中最后的一天,希望自己明年更美好,坚持写博客,作总结。