1实验目的和内容
实验目的:(1)通过实验,了解在linux输入子系统框架中编写输入设备驱动程序的步骤;
(2)体会与之前章节讲的编写驱动的方法之间的差异。
实验内容:在linux输入子系统中编写按键驱动程序,按键S2、S3、S4、S5按下时,代表"L"、"S"、"ENTER"、"LEFTSHIFT"等操作功能。
2知识回顾
前面章节讲到的自己编写驱动的方法,主要包含以下步骤:
-
定义file_operation结构体,实现open、read、write等接口函数;
-
调用register_chrdev函数注册设备;
-
定义入口函数;
-
定义出口函数。
上一章节分析的输入子系统的结构,共分为三层:Input driver、InputCore、EventHandler。其中Input driver层实现对硬件设备的读写访问,中断设置,并将硬件产生的事件转换为InputCore层定义的规范提交给EventHandler。因此,是本节编写驱动程序时需要重点实现的部分。InputCore层,也就是我们的drivers/input/input.c文件,内核已经提供了完整的代码程序,不用再做修改。EventHandler层主要是用于支持输入设备与用户空间之间的交互,linux内核已经自带了一部分事件处理器,可支持大部分的输入设备,例如:Evdev.c、mousedev.c等。其中Evdev.c中的evdev_handler的id_table:evdev_ids定义如下:
可以支持所有的输入设备。所以本节实验也不需要对EventHandler层的代码再做任何修改。那么接下来将重点放到Input driver层。
3实验原理简介
Input driver设备驱动层是与硬件紧密相关的,其主要工作:向InputCore报告同步、按键等事件,让驱动事件经由inputcore和Eventhandler到达用户空间。
input_dev结构体
-
struct input_dev {
-
-
void *private;
-
-
const char *name; //输入设备的名称
-
const char *phys; //输入设备节点的名称
-
const char *uniq; //输入设备的唯一ID号,类似于mac地址
-
struct input_id id; // 输入设备的唯一标识,用于和eventhandler层进行匹配
-
-
unsigned long evbit[NBITS(EV_MAX)]; //设备支持的事件类型
-
unsigned long keybit[NBITS(KEY_MAX)]; //设备支持的按键类型
-
unsigned long relbit[NBITS(REL_MAX)]; //可产生的相对位移事件
-
unsigned long absbit[NBITS(ABS_MAX)];
-
unsigned long mscbit[NBITS(MSC_MAX)];
-
unsigned long ledbit[NBITS(LED_MAX)];
-
unsigned long sndbit[NBITS(SND_MAX)];
-
unsigned long ffbit[NBITS(FF_MAX)];
-
unsigned long swbit[NBITS(SW_MAX)];
-
-
unsigned int keycodemax;
-
unsigned int keycodesize;
-
void *keycode;
-
......
-
-
struct list_head h_list;
-
struct list_head node;
-
};
驱动层的主要函数接口如下表:
接口 |
功能 |
struct input_dev *input_allocate_device(void) |
为一个新的输入设备申请空间 |
static inline void set_bit(int nr, volatile void * addr) |
设置设备支持哪些事件 eg: set_bit(EV_KEY, buttons_dev->evbit); |
int input_register_device(struct input_dev *dev) |
注册输入设备 |
void input_unregister_device(struct input_dev *dev) |
卸载输入设备 |
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) |
报告新的输入事件 dev:要上报的输入设备 type:上报的事件类型 code:上报的事件code value:上报的事件值 |
static inline void input_sync(struct input_dev *dev) |
报告同步事件,通知InputCore层子系统input_event报告结束 |
结合上述函数接口,我们需要在驱动层完成如下工作:分配、设置、注册一个结构体,并完成硬件相关的操作。
按照驱动的要求,定义模块的入口函数和出口函数。
编写入口函数buttons_init
-
int buttons_init(void)
-
{
-
int i, error;
-
-
/* 1、分配一个input_dev空间*/
-
buttons_dev = input_allocate_device();
-
if (!buttons_dev)
-
return -ENOMEM;
-
-
/* 2、设置input_dev结构体 */
-
/* 2.1、buttons_dev可以产生按键类的事件 */
-
set_bit(EV_KEY, buttons_dev->evbit);
-
set_bit(EV_REP, buttons_dev->evbit);
-
-
/* 2.2 能产生按键类事件中的哪一类事件:L S ENTER LEFTSHIFT */
-
set_bit(KEY_L, buttons_dev->keybit);
-
set_bit(KEY_S, buttons_dev->keybit);
-
set_bit(KEY_ENTER, buttons_dev->keybit);
-
set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);
-
-
/* 3、注册buttons_dev */
-
error = input_register_device(buttons_dev);
-
if (error) {
-
printk(KERN_ERR "Unable to register buttons_dev ");
-
goto fail;
-
}
-
-
/* 4、硬件相关的配置 */
-
init_timer(&button_timer);
-
button_timer.function = button_timer_func;
-
add_timer(&button_timer);
-
-
for(i=0; i<4; i++)
-
{
-
request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE,
-
pins_desc[i].devname, &pins_desc[i]);
-
}
-
-
return 0;
-
fail:
-
for(i=0; i<4; i++)
-
free_irq(pins_desc[i].irq, &pins_desc[i]);
-
input_free_device(buttons_dev);
-
}
-
第6行,为按键输入设备buttons_dev申请空间;
-
第12行,设置buttons_dev支持按键事件;
-
第16~19行,设置4个物理按键对应的按键事件,S2、S3、S4、S5分别对应"L"、"S"、"ENTER"、"LEFTSHIFT"事件;
-
调用input_register_device函数注册buttons_dev设备;
-
第29~31行,关于定时器的设置,用于按键消抖处理;
-
第33~37行,关于中断的注册,采用中断的方式检测按键,为了简化程序,定义一个pins_desc[4]结构体数组用于管理按键相关的信息,其定义如下:
-
第40行,一些错误处理。
-
出口函数代码如下:
-
static void buttons_exit(void)
-
{
-
int i;
-
for(i=0; i<4; i++)
-
free_irq(pins_desc[i].irq, &pins_desc[i]);
-
del_timer(&button_timer);
-
input_unregister_device(buttons_dev);
-
input_free_device(buttons_dev);
-
}
驱动层检测到按键时,需要向上层发送按键事件。这里在定时器中断服务函数中,调用input_event函数发送对应的按键事件,code和value,再调用input_sync函数结束报告。
-
void button_timer_func(unsigned long arg)
-
{
-
struct pin_desc * pindesc = irq_pd;
-
unsigned int pinval ;
-
-
if(pindesc)
-
{
-
pinval = s3c2410_gpio_getpin(pindesc->pin);
-
-
if(pinval)/* 最后一个参数 松开:0 按下:1 */
-
{
-
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
-
input_sync(buttons_dev);
-
}
-
else
-
{
-
input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
-
input_sync(buttons_dev);
-
}
-
}
-
}
至此,基于输入子系统的按键驱动程序就已编写结束。
4实验验证
将编写的驱动代码进行编译、挂载。通过ls -l /dev/event*命令,可以查看到已经挂载的驱动设备/dev/event1。
方法一
使用hexdump /dev/event1命令,查看/dev/event1的十六进制编码。
这里我们需要查看这些数据都表示些什么含义?
首先找到evdev_read函数,该函数中通过evdev_event_to_user函数将input_event事件发送至用户空间,其中input_event结构体定义如下:
对应到显示的数据的含义如图所示。
从读出的16进制数据显示,说明按键驱动可以正常工作。
方法二
若未使用QT,可执行cat /dev/tty1命令,再按下按键时,就会输出对应的按键值。
若在执行exec 0</dev/tty1命令,将/dev/tty1设置为标准输入,此时就可通过按键输入ls命令,并在终端上显示执行结果。