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\n");??
????????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命令,并在终端上显示执行结果。
2linux输入子系统-按键驱动程序
原文:https://www.cnblogs.com/beijiqie1104/p/11424982.html