设计师自己做网站,人才市场网站源码,网站优化的方法与技巧,网站建设公司如何运营目录input 子系统input 子系统简input 驱动编写流程input_event 结构体硬件原理图分析实验程序编写修改设备树文件按键input 驱动程序编写编写测试APP运行测试编译驱动程序和测试APP运行测试Linux 自带按键驱动程序的使用自带按键驱动程序源码简析自带按键驱动程序的使用按键、…
目录input 子系统input 子系统简input 驱动编写流程input_event 结构体硬件原理图分析实验程序编写修改设备树文件按键input 驱动程序编写编写测试APP运行测试编译驱动程序和测试APP运行测试Linux 自带按键驱动程序的使用自带按键驱动程序源码简析自带按键驱动程序的使用按键、鼠标、键盘、触摸屏等都属于输入(input)设备Linux 内核为此专门做了一个叫做input子系统的框架来处理输入事件。输入设备本质上还是字符设备只是在此基础上套上了input 框架用户只需要负责上报输入事件比如按键值、坐标等信息input 核心层负责处理这些事件。 本章我们就来学习一下Linux 内核中的input 子系统。
input 子系统
input 子系统简
input 就是输入的意思因此input 子系统就是管理输入的子系统和pinctrl、gpio 子系统一样都是Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备不同的输入设备所代表的含义不同按键和键盘就是代表按键信息鼠标和触摸屏代表坐标信息因此在应用层的处理就不同对于驱动编写者而言不需要去关心 应用层的事情我们只需要按照要求上报这些输入事件即可。为此input 子系统分为input 驱动层、input 核心层、input 事件处理层最终给用户空间提供可访问的设备节点input 子系统框架如图58.1.1.1 所示 图58.1.1.1 中左边就是最底层的具体设备比如按键、USB 键盘/鼠标等中间部分属于Linux 内核空间分为驱动层、核心层和事件层最右边的就是用户空间所有的输入设备以文件的形式供用户应用程序使用。可以看出input 子系统用到了我们前面讲解的驱动分层模型我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层这三个层的分工如下 驱动层输入设备的具体驱动程序比如按键驱动程序向内核层报告输入内容。 核心层承上启下为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。 事件层主要和用户空间进行交互。
input 驱动编写流程
input 核心层会向Linux 内核注册一个字符设备大家找到drivers/input/input.c 这个文件input.c 就是input 输入子系统的核心层此文件里面有如下所示代码
1767 struct class input_class {
1768 .name input,
1769 .devnode input_devnode,
1770 };
......
2414 static int __init input_init(void)
2415 {
2416 int err;
2417
2418 err class_register(input_class);
2419 if (err) {
2420 pr_err(unable to register input_dev class\n);
2421 return err;
2422 }
2423
2424 err input_proc_init();
2425 if (err)
2426 goto fail1;
2427
2428 err register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
2429 INPUT_MAX_CHAR_DEVICES, input);
2430 if (err) {
2431 pr_err(unable to register char major %d, INPUT_MAJOR);
2432 goto fail2;
2433 }
2434
2435 return 0;
2436
2437 fail2: input_proc_exit();
2438 fail1: class_unregister(input_class);
2439 return err;
2440 }
第2418 行注册一个input 类这样系统启动以后就会在/sys/class 目录下有一个input 子目录如图58.1.2.1 所示
第2428~2429 行注册一个字符设备主设备号为INPUT_MAJORINPUT_MAJOR 定义在include/uapi/linux/major.h 文件中定义如下
#define INPUT_MAJOR 13因此input 子系统的所有设备主设备号都为13我们在使用input 子系统处理输入设备的时候就不需要去注册字符设备了我们只需要向系统注册一个input_device 即可。 1、注册input_dev 在使用input 子系统的时候我们只需要注册一个input 设备即可input_dev 结构体表示input设备此结构体定义在include/linux/input.h 文件中定义如下(有省略)
121 struct input_dev {
122 const char *name;
123 const char *phys;
124 const char *uniq;
125 struct input_id id;
126
127 unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
128
129 unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图*/
130 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的位图*/
131 unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 相对坐标的位图*/
132 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 绝对坐标的位图*/
133 unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 杂项事件的位图*/
134 unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /*LED相关的位图*/
135 unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];/* sound有关的位图*/
136 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 压力反馈的位图*/
137 unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /*开关状态的位图*/
......
189 bool devres_managed;
190 };第129 行evbit 表示输入事件类型可选的事件类型定义在include/uapi/linux/input.h 文件中事件类型如下
#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 /* LED */
#define EV_SND 0x12 /* sound(声音) */
#define EV_REP 0x14 /* 重复事件*/
#define EV_FF 0x15 /* 压力事件*/
#define EV_PWR 0x16 /* 电源事件*/
#define EV_FF_STATUS 0x17 /* 压力状态事件*/比如本章我们要使用到按键那么就需要注册EV_KEY 事件如果要使用连按功能的话还需要注册EV_REP 事件。
继续回到示例代码58.1.2.2 中第129 行~137 行的evbit、keybit、relbit 等等都是存放不同事件对应的值。比如我们本章要使用按键事件因此要用到keybitkeybit 就是按键事件使用的位图Linux 内核定义了很多按键值这些按键值定义在include/uapi/linux/input.h 文件中按键值如下
215 #define KEY_RESERVED 0
216 #define KEY_ESC 1
217 #define KEY_1 2
218 #define KEY_2 3
219 #define KEY_3 4
220 #define KEY_4 5
221 #define KEY_5 6
222 #define KEY_6 7
223 #define KEY_7 8
224 #define KEY_8 9
225 #define KEY_9 10
226 #define KEY_0 11
......
794 #define BTN_TRIGGER_HAPPY39 0x2e6
795 #define BTN_TRIGGER_HAPPY40 0x2e7我们可以将开发板上的按键值设置为示例代码58.1.2.4 中的任意一个比如我们本章实验会将I.MX6U-ALPHA 开发板上的KEY 按键值设置为KEY_0。
在编写input 设备驱动的时候我们需要先申请一个input_dev 结构体变量使用input_allocate_device 函数来申请一个input_dev此函数原型如下所示
struct input_dev *input_allocate_device(void)函数参数和返回值含义如下 参数无。 返回值申请到的input_dev。 如果要注销的input 设备的话需要使用input_free_device 函数来释放掉前面申请到的input_devinput_free_device 函数原型如下
void input_free_device(struct input_dev *dev)函数参数和返回值含义如下 dev需要释放的input_dev。 返回值无。 申请好一个input_dev 以后就需要初始化这个input_dev需要初始化的内容主要为事件类型(evbit)和事件值(keybit)这两种。input_dev 初始化完成以后就需要向Linux 内核注册input_dev了需要用到input_register_device 函数此函数原型如下
int input_register_device(struct input_dev *dev)函数参数和返回值含义如下 dev要注册的input_dev 。 返回值0input_dev 注册成功负值input_dev 注册失败。 同样的注销input 驱动的时候也需要使用input_unregister_device 函数来注销掉前面注册的input_devinput_unregister_device 函数原型如下
void input_unregister_device(struct input_dev *dev)函数参数和返回值含义如下 dev要注销的input_dev 。 返回值无。 综上所述input_dev 注册过程如下
①、使用input_allocate_device 函数申请一个input_dev。 ②、初始化input_dev 的事件类型以及事件值。 ③、使用input_register_device 函数向Linux 系统注册前面初始化好的input_dev。 ④、卸载input 驱动的时候需要先使用input_unregister_device 函数注销掉注册的input_dev然后使用input_free_device 函数释放掉前面申请的input_dev。input_dev 注册过程示例代码如下所示
1 struct input_dev *inputdev; /* input结构体变量*/
2
3 /* 驱动入口函数*/
4 static int __init xxx_init(void)
5 {
6 ......
7 inputdev input_allocate_device(); /* 申请input_dev */
8 inputdev-name test_inputdev; /* 设置input_dev名字*/
9
10 /*********第一种设置事件和事件值的方法***********/
11 __set_bit(EV_KEY, inputdev-evbit); /* 设置产生按键事件*/
12 __set_bit(EV_REP, inputdev-evbit); /* 重复事件*/
13 __set_bit(KEY_0, inputdev-keybit); /*设置产生哪些按键值*/
14 /************************************************/
15
16 /*********第二种设置事件和事件值的方法***********/
17 keyinputdev.inputdev-evbit[0] BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
18 keyinputdev.inputdev-keybit[BIT_WORD(KEY_0)] |
BIT_MASK(KEY_0);
19 /************************************************/
20
21 /*********第三种设置事件和事件值的方法***********/
22 keyinputdev.inputdev-evbit[0] BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
23 input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
24 /************************************************/
25
26 /* 注册input_dev */
27 input_register_device(inputdev);
28 ......
29 return 0;
30 }
31
32 /* 驱动出口函数*/
33 static void __exit xxx_exit(void)
34 {
35 input_unregister_device(inputdev); /* 注销input_dev */
36 input_free_device(inputdev); /* 删除input_dev */
37 }
第1 行定义一个input_dev 结构体指针变量。
第4~ 30 行驱动入口函数在此函数中完成input_dev 的申请、设置、注册等工作。第7行调用input_allocate_device 函数申请一个input_dev。第10~23 行都是设置input 设备事件和按键值这里用了三种方法来设置事件和按键值。第27 行调用input_register_device 函数向Linux内核注册inputdev。
第33~37 行驱动出口函数第35 行调用input_unregister_device 函数注销前面注册的input_dev第36 行调用input_free_device 函数删除前面申请的input_dev。
2、上报输入事件 当我们向Linux 内核注册好input_dev 以后还不能高枕无忧的使用input 设备input 设备都是具有输入功能的但是具体是什么样的输入值Linux 内核是不知道的我们需要获取到具体的输入值或者说是输入事件然后将输入事件上报给Linux 内核。比如按键我们需要在按键中断处理函数或者消抖定时器中断函数中将按键值上报给Linux 内核这样Linux 内核才 能获取到正确的输入值。不同的事件其上报事件的API 函数不同我们依次来看一下一些常用的事件上报API 函数。
首先是input_event 函数此函数用于上报指定的事件以及对应的值函数原型如下
void input_event(struct input_dev *dev,
unsigned int type,
unsigned int code,
int value)函数参数和返回值含义如下 dev需要上报的input_dev。 type: 上报的事件类型比如EV_KEY。 code事件码也就是我们注册的按键值比如KEY_0、KEY_1 等等。 value事件值比如1 表示按键按下0 表示按键松开。 返回值无。 input_event 函数可以上报所有的事件类型和事件值Linux 内核也提供了其他的针对具体事件的上报函数这些函数其实都用到了input_event 函数。比如上报按键所使用的input_report_key 函数此函数内容如下
static inline void input_report_key(struct input_dev *dev,
unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}从示例代码58.1.2.6 可以看出input_report_key 函数的本质就是input_event 函数如果要上报按键事件的话还是建议大家使用input_report_key 函数。 同样的还有一些其他的事件上报函数这些函数如下所示
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_report_ff_status(struct input_dev *dev, unsigned int code, int value)
void input_report_switch(struct input_dev *dev, unsigned int code, int value)
void input_mt_sync(struct input_dev *dev)
当我们上报事件以后还需要使用input_sync 函数来告诉Linux 内核input 子系统上报结束input_sync 函数本质是上报一个同步事件此函数原型如下所示
void input_sync(struct input_dev *dev)函数参数和返回值含义如下 dev需要上报同步事件的input_dev。 返回值无。 综上所述按键的上报事件的参考代码如下所示
1 /* 用于按键消抖的定时器服务函数*/
2 void timer_function(unsigned long arg)
3 {
4 unsigned char value;
5
6 value gpio_get_value(keydesc-gpio); /* 读取IO值*/
7 if(value 0){ /* 按下按键*/
8 /* 上报按键值*/
9 input_report_key(inputdev, KEY_0, 1); /* 最后一个参数1按下*/
10 input_sync(inputdev); /* 同步事件*/
11 } else { /* 按键松开*/
12 input_report_key(inputdev, KEY_0, 0); /* 最后一个参数0松开*/
13 input_sync(inputdev); /* 同步事件*/
14 }
15 }第6 行获取按键值判断按键是否按下。
第9~10 行如果按键值为0 那么表示按键被按下了如果按键按下的话就要使用input_report_key 函数向Linux 系统上报按键值比如向Linux 系统通知KEY_0 这个按键按下了。 第12~13 行如果按键值为1 的话就表示按键没有按下是松开的。向Linux 系统通知KEY_0 这个按键没有按下或松开了。
input_event 结构体
Linux 内核使用input_event 这个结构体来表示所有的输入事件input_envent 结构体定义在include/uapi/linux/input.h 文件中结构体内容如下
24 struct input_event {
25 struct timeval time;
26 __u16 type;
27 __u16 code;
28 __s32 value;
29 };
我们依次来看一下input_event 结构体中的各个成员变量 time时间也就是此事件发生的时间为timeval 结构体类型timeval 结构体定义如下
1 typedef long __kernel_long_t;
2 typedef __kernel_long_t __kernel_time_t;
3 typedef __kernel_long_t __kernel_suseconds_t;
4
5 struct timeval {
6 __kernel_time_t tv_sec; /* 秒*/
7 __kernel_suseconds_t tv_usec; /* 微秒*/
8 };从示例代码58.1.3.2 可以看出tv_sec 和tv_usec 这两个成员变量都为long 类型也就是32位这个一定要记住后面我们分析event 事件上报数据的时候要用到。 type事件类型比如EV_KEY表示此次事件为按键事件此成员变量为16 位。 code事件码比如在EV_KEY 事件中code 就表示具体的按键码如KEY_0、KEY_1等等这些按键。此成员变量为16 位。 value值比如EV_KEY 事件中value 就是按键值表示按键有没有被按下如果为1 的话说明按键按下如果为0 的话说明按键没有被按下或者按键松开了。 input_envent 这个结构体非常重要因为所有的输入设备最终都是按照input_event 结构体呈现给用户的用户应用程序可以通过input_event 来获取到具体的输入事件或相关的值比如按键值等。关于input 子系统就讲解到这里接下来我们就以开发板上的KEY0 按键为例讲解一下如何编写input 驱动。
硬件原理图分析
本章实验硬件原理图参考15.2 小节即可。
实验程序编写
本实验对应的例程路径为开发板光盘- 2、Linux 驱动例程- 20_input。
修改设备树文件
直接使用49.3.1 小节创建的key 节点即可。
按键input 驱动程序编写
新建名为“20_input”的文件夹然后在20_input 文件夹里面创建vscode 工程工作区命名为“keyinput”。工程创建好以后新建keyinput.c 文件在keyinput.c 里面输入如下内容
#include linux/types.h
#include linux/kernel.h
#include linux/delay.h
#include linux/ide.h
#include linux/init.h
#include linux/module.h
#include linux/errno.h
#include linux/gpio.h
#include linux/cdev.h
#include linux/device.h
#include linux/of.h
#include linux/of_address.h
#include linux/of_gpio.h
#include linux/input.h
#include linux/semaphore.h
#include linux/timer.h
#include linux/of_irq.h
#include linux/irq.h
#include asm/mach/map.h
#include asm/uaccess.h
#include asm/io.h
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : keyinput.c
作者 : 左忠凯
版本 : V1.0
描述 : Linux按键input子系统实验
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/8/21 左忠凯创建
***************************************************************/
#define KEYINPUT_CNT 1 /* 设备号个数 */
#define KEYINPUT_NAME keyinput /* 名字 */
#define KEY0VALUE 0X01 /* KEY0按键值 */
#define INVAKEY 0XFF /* 无效的按键值 */
#define KEY_NUM 1 /* 按键数量 *//* 中断IO描述结构体 */
struct irq_keydesc {int gpio; /* gpio */int irqnum; /* 中断号 */unsigned char value; /* 按键对应的键值 */char name[10]; /* 名字 */irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};/* keyinput设备结构体 */
struct keyinput_dev{dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */struct device_node *nd; /* 设备节点 */struct timer_list timer;/* 定义一个定时器*/struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */unsigned char curkeynum; /* 当前的按键号 */struct input_dev *inputdev; /* input结构体 */
};struct keyinput_dev keyinputdev; /* key input设备 *//* description : 中断服务函数开启定时器延时10ms* 定时器用于按键消抖。* param - irq : 中断号 * param - dev_id : 设备结构。* return : 中断执行结果*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{struct keyinput_dev *dev (struct keyinput_dev *)dev_id;dev-curkeynum 0;dev-timer.data (volatile long)dev_id;mod_timer(dev-timer, jiffies msecs_to_jiffies(10)); /* 10ms定时 */return IRQ_RETVAL(IRQ_HANDLED);
}/* description : 定时器服务函数用于按键消抖定时器到了以后* 再次读取按键值如果按键还是处于按下状态就表示按键有效。* param - arg : 设备结构变量* return : 无*/
void timer_function(unsigned long arg)
{unsigned char value;unsigned char num;struct irq_keydesc *keydesc;struct keyinput_dev *dev (struct keyinput_dev *)arg;num dev-curkeynum;keydesc dev-irqkeydesc[num];value gpio_get_value(keydesc-gpio); /* 读取IO值 */if(value 0){ /* 按下按键 *//* 上报按键值 *///input_event(dev-inputdev, EV_KEY, keydesc-value, 1);input_report_key(dev-inputdev, keydesc-value, 1);/* 最后一个参数表示按下还是松开1为按下0为松开 */input_sync(dev-inputdev);} else { /* 按键松开 *///input_event(dev-inputdev, EV_KEY, keydesc-value, 0);input_report_key(dev-inputdev, keydesc-value, 0);input_sync(dev-inputdev);}
}/** description : 按键IO初始化* param : 无* return : 无*/
static int keyio_init(void)
{unsigned char i 0;char name[10];int ret 0;keyinputdev.nd of_find_node_by_path(/key);if (keyinputdev.nd NULL){printk(key node not find!\r\n);return -EINVAL;} /* 提取GPIO */for (i 0; i KEY_NUM; i) {keyinputdev.irqkeydesc[i].gpio of_get_named_gpio(keyinputdev.nd ,key-gpio, i);if (keyinputdev.irqkeydesc[i].gpio 0) {printk(cant get key%d\r\n, i);}}/* 初始化key所使用的IO并且设置成中断模式 */for (i 0; i KEY_NUM; i) {memset(keyinputdev.irqkeydesc[i].name, 0, sizeof(name)); /* 缓冲区清零 */sprintf(keyinputdev.irqkeydesc[i].name, KEY%d, i); /* 组合名字 */gpio_request(keyinputdev.irqkeydesc[i].gpio, name);gpio_direction_input(keyinputdev.irqkeydesc[i].gpio); keyinputdev.irqkeydesc[i].irqnum irq_of_parse_and_map(keyinputdev.nd, i);}/* 申请中断 */keyinputdev.irqkeydesc[0].handler key0_handler;keyinputdev.irqkeydesc[0].value KEY_0;for (i 0; i KEY_NUM; i) {ret request_irq(keyinputdev.irqkeydesc[i].irqnum, keyinputdev.irqkeydesc[i].handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, keyinputdev.irqkeydesc[i].name, keyinputdev);if(ret 0){printk(irq %d request failed!\r\n, keyinputdev.irqkeydesc[i].irqnum);return -EFAULT;}}/* 创建定时器 */init_timer(keyinputdev.timer);keyinputdev.timer.function timer_function;/* 申请input_dev */keyinputdev.inputdev input_allocate_device();keyinputdev.inputdev-name KEYINPUT_NAME;
#if 0/* 初始化input_dev设置产生哪些事件 */__set_bit(EV_KEY, keyinputdev.inputdev-evbit); /* 设置产生按键事件 */__set_bit(EV_REP, keyinputdev.inputdev-evbit); /* 重复事件比如按下去不放开就会一直输出信息 *//* 初始化input_dev设置产生哪些按键 */__set_bit(KEY_0, keyinputdev.inputdev-keybit);
#endif#if 0keyinputdev.inputdev-evbit[0] BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);keyinputdev.inputdev-keybit[BIT_WORD(KEY_0)] | BIT_MASK(KEY_0);
#endifkeyinputdev.inputdev-evbit[0] BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);/* 注册输入设备 */ret input_register_device(keyinputdev.inputdev);if (ret) {printk(register input device failed!\r\n);return ret;}return 0;
}/** description : 驱动入口函数* param : 无* return : 无*/
static int __init keyinput_init(void)
{keyio_init();return 0;
}/** description : 驱动出口函数* param : 无* return : 无*/
static void __exit keyinput_exit(void)
{unsigned int i 0;/* 删除定时器 */del_timer_sync(keyinputdev.timer); /* 删除定时器 *//* 释放中断 */for (i 0; i KEY_NUM; i) {free_irq(keyinputdev.irqkeydesc[i].irqnum, keyinputdev);}/* 释放input_dev */input_unregister_device(keyinputdev.inputdev);input_free_device(keyinputdev.inputdev);
}module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_LICENSE(GPL);
MODULE_AUTHOR(zuozhongkai);
keyinput.c 文件内容其实就是实验“13_irq”中的imx6uirq.c 文件中修改而来的只是将其中与字符设备有关的内容进行了删除加入了input_dev 相关的内容我们简单来分析一下示例代码58.3.2.1 中的程序。
第57 行在设备结构体中定义一个input_dev 指针变量。
第93~102 行在按键消抖定时器处理函数中上报输入事件也就是使用input_report_key函数上报按键事件以及按键值最后使用input_sync 函数上报一个同步事件这一步一定得做
第156~180 行使用input_allocate_device 函数申请input_dev然后设置相应的事件以及事件码(也就是KEY 模拟成那个按键这里我们设置为KEY_0)。最后使用input_register_device函数向Linux 内核注册input_dev。
第211~212 行当注销input 设备驱动的时候使用input_unregister_device 函数注销掉前面注册的input_dev最后使用input_free_device 函数释放掉前面申请的input_dev。
编写测试APP
新建keyinputApp.c 文件然后在里面输入如下所示内容
#include stdio.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include sys/ioctl.h
#include fcntl.h
#include stdlib.h
#include string.h
#include poll.h
#include sys/select.h
#include sys/time.h
#include signal.h
#include fcntl.h
#include linux/input.h
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : keyinputApp.c
作者 : 左忠凯
版本 : V1.0
描述 : input子系统测试APP。
其他 : 无
使用方法 ./keyinputApp /dev/input/event1
论坛 : www.openedv.com
日志 : 初版V1.0 2019/8/26 左忠凯创建
***************************************************************//* 定义一个input_event变量存放输入事件信息 */
static struct input_event inputevent;/** description : main主程序* param - argc : argv数组元素个数* param - argv : 具体参数* return : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd;int err 0;char *filename;filename argv[1];if(argc ! 2) {printf(Error Usage!\r\n);return -1;}fd open(filename, O_RDWR);if (fd 0) {printf(Cant open file %s\r\n, filename);return -1;}while (1) {err read(fd, inputevent, sizeof(inputevent));if (err 0) { /* 读取数据成功 */switch (inputevent.type) {case EV_KEY:if (inputevent.code BTN_MISC) { /* 键盘键值 */printf(key %d %s\r\n, inputevent.code, inputevent.value ? press : release);} else {printf(button %d %s\r\n, inputevent.code, inputevent.value ? press : release);}break;/* 其他类型的事件自行处理 */case EV_REL:break;case EV_ABS:break;case EV_MSC:break;case EV_SW:break;}} else {printf(读取数据失败\r\n);}}return 0;
}第58.1.3 小节已经说过了Linux 内核会使用input_event 结构体来表示输入事件所以我们要获取按键输入信息那么必须借助于input_event 结构体。第28 行定义了一个inputevent 变量此变量为input_event 结构体类型。
第56 行当我们向Linux 内核成功注册input_dev 设备以后会在/dev/input 目录下生成一个名为“eventX(X0….n)”的文件这个/dev/input/eventX 就是对应的input 设备文件。我们读取这个文件就可以获取到输入事件信息比如按键值什么的。使用read 函数读取输入设备文件也就是/dev/input/eventX读取到的数据按照input_event 结构体组织起来。获取到输入事件以后(input_event 结构体类型)使用switch case 语句来判断事件类型本章实验我们设置的事件类型为EV_KEY因此只需要处理EV_KEY 事件即可。比如获取按键编号(KEY_0 的编号为11)、获取按键状态按下还是松开的
运行测试
编译驱动程序和测试APP
1、编译驱动程序 编写Makefile 文件本章实验的Makefile 文件和第四十章实验基本一样只是将obj-m 变量的值改为“keyinput.o”Makefile 内容如下所示
KERNELDIR : /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH : $(shell pwd)obj-m : keyinput.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M$(CURRENT_PATH) clean第4 行设置obj-m 变量的值为“keyinput.o”。 输入如下命令编译出驱动模块文件
make -j32编译成功以后就会生成一个名为“keyinput.ko”的驱动模块文件。 2、编译测试APP 输入如下命令编译测试keyinputApp.c 这个测试程序
arm-linux-gnueabihf-gcc keyinputApp.c -o keyinputApp编译成功以后就会生成keyinputApp 这个应用程序。
运行测试
将上一小节编译出来keyinput.ko 和keyinputApp 这两个文件拷贝到rootfs/lib/modules/4.1.15目录中重启开发板进入到目录lib/modules/4.1.15 中。在加载keyinput.ko 驱动模块之前先 看一下/dev/input 目录下都有哪些文件结果如图58.4.2.1 所示
从图58.4.2.1 可以看出当前/dev/input 目录只有event0 和mice 这两个文件。接下来输入如下命令加载keyinput.ko 这个驱动模块。
depmod //第一次加载驱动的时候需要运行此命令
modprobe keyinput.ko //加载驱动模块当驱动模块加载成功以后再来看一下/dev/input 目录下有哪些文件结果如图58.4.2.2 所示
从图58.4.2.2 可以看出多了一个event1 文件因此/dev/input/event1 就是我们注册的驱动所对应的设备文件。keyinputApp 就是通过读取/dev/input/event1 这个文件来获取输入事件信息的输入如下测试命令
./keyinputApp /dev/input/event1然后按下开发板上的KEY 按键结果如图58.4.2.3 所示 从图58.4.2.3 可以看出当我们按下或者释放开发板上的按键以后都会在终端上输出相应的内容提示我们哪个按键按下或释放了在Linux 内核中KEY_0 为11。 另外我们也可以不用keyinputApp 来测试驱动可以直接使用hexdump 命令来查看/dev/input/event1 文件内容输入如下命令
hexdump /dev/input/event1然后按下按键终端输出如图58.4.2.4 所示信息 图58.4.2.4 就是input_event 类型的原始事件数据值采用十六进制表示这些原始数据的含义如下
/*****************input_event类型********************/
/* 编号*/ /* tv_sec */ /* tv_usec */ /* type */ /* code */ /* value */
0000000 0c41 0000 d7cd 000c 0001 000b 0001 0000
0000010 0c41 0000 d7cd 000c 0000 0000 0000 0000
0000020 0c42 0000 54bb 0000 0001 000b 0000 0000
0000030 0c42 0000 54bb 0000 0000 0000 0000 0000type 为事件类型查看示例代码58.1.2.3 可知EV_KEY 事件值为1EV_SYN 事件值为0。因此第1 行表示EV_KEY 事件第2 行表示EV_SYN 事件。code 为事件编码也就是按键号查看示例代码58.1.2.4 可以KEY_0 这个按键编号为11对应的十六进制为0xb因此第1 行表示KEY_0 这个按键事件最后的value 就是按键值为1 表示按下为0 的话表示松开。
综上所述示例代码58.4.2.1 中的原始事件值含义如下 第1 行按键(KEY_0)按下事件。 第2 行EV_SYN 同步事件因为每次上报按键事件以后都要同步的上报一个EV_SYN 事件。 第3 行按键(KEY_0)松开事件。 第4 行EV_SYN 同步事件和第2 行一样。
Linux 自带按键驱动程序的使用
自带按键驱动程序源码简析
Linux 内核也自带了KEY 驱动如果要使用内核自带的KEY 驱动的话需要配置Linux 内核不过Linux 内核一般默认已经使能了KEY 驱动但是我们还是要检查一下。按照如下路径找到相应的配置选项
- Device Drivers- Input device support- Generic input layer (needed for keyboard, mouse, ...) (INPUT [y])- Keyboards (INPUT_KEYBOARD [y])-GPIO Buttons选中“GPIO Buttons”选项将其编译进Linux 内核中如图58.5.1.1 所示 选中以后就会在.config 文件中出现“CONFIG_KEYBOARD_GPIOy”这一行Linux 内核就会根据这一行来将KEY 驱动文件编译进Linux 内核。Linux 内核自带的KEY 驱动文件为drivers/input/keyboard/gpio_keys.cgpio_keys.c 采用了platform 驱动框架在KEY 驱动上使用了input 子系统实现。在gpio_keys.c 文件中找到如下所示内容
673 static const struct of_device_id gpio_keys_of_match[] {
674 { .compatible gpio-keys, },
675 { },
676 };
......
842 static struct platform_driver gpio_keys_device_driver {
843 .probe gpio_keys_probe,
844 .remove gpio_keys_remove,
845 .driver {
846 .name gpio-keys,
847 .pm gpio_keys_pm_ops,
848 .of_match_table of_match_ptr(gpio_keys_of_match),
849 }
850 };
851
852 static int __init gpio_keys_init(void)
853 {
854 return platform_driver_register(gpio_keys_device_driver);
855 }
856
857 static void __exit gpio_keys_exit(void)
858 {
859 platform_driver_unregister(gpio_keys_device_driver);
860 }
从示例代码58.5.1.1 可以看出这就是一个标准的platform 驱动框架如果要使用设备树来描述KEY 设备信息的话设备节点的compatible 属性值要设置为“gpio-keys”。当设备和驱动匹配以后gpio_keys_probe 函数就会执行gpio_keys_probe 函数内容如下(为了篇幅有缩减)
689 static int gpio_keys_probe(struct platform_device *pdev)
690 {
691 struct device *dev pdev-dev;
692 const struct gpio_keys_platform_data *pdata
dev_get_platdata(dev);
693 struct gpio_keys_drvdata *ddata;
694 struct input_dev *input;
695 size_t size;
696 int i, error;
697 int wakeup 0;
698
699 if (!pdata) {
700 pdata gpio_keys_get_devtree_pdata(dev);
701 if (IS_ERR(pdata))
702 return PTR_ERR(pdata);
703 }
......
713 input devm_input_allocate_device(dev);
714 if (!input) {
715 dev_err(dev, failed to allocate input device\n);
716 return -ENOMEM;
717 }
718
719 ddata-pdata pdata;
720 ddata-input input;
721 mutex_init(ddata-disable_lock);
722
723 platform_set_drvdata(pdev, ddata);
724 input_set_drvdata(input, ddata);
725
726 input-name pdata-name ? : pdev-name;
727 input-phys gpio-keys/input0;
第700 行调用gpio_keys_get_devtree_pdata 函数从设备树中获取到KEY 相关的设备节点信息。 第713 行使用devm_input_allocate_device 函数申请input_dev。 第726~735初始化input_dev。 第739 行设置input_dev 事件这里设置了EV_REP 事件。 第745 行调用gpio_keys_setup_key 函数继续设置KEY此函数会设置input_dev 的EV_KEY 事件已经事件码(也就是KEY 模拟为哪个按键)。 第760 行调用input_register_device 函数向Linux 系统注册input_dev。 我们接下来再来看一下gpio_keys_setup_key 函数此函数内容如下
437 static int gpio_keys_setup_key(struct platform_device *pdev,
438 struct input_dev *input,
439 struct gpio_button_data *bdata,
440 const struct gpio_keys_button *button)
441 {
442 const char *desc button-desc ? button-desc : gpio_keys;
443 struct device *dev pdev-dev;
444 irq_handler_t isr;
445 unsigned long irqflags;
446 int irq;
447 int error;
448
449 bdata-input input;
450 bdata-button button;
451 spin_lock_init(bdata-lock);
452
453 if (gpio_is_valid(button-gpio)) {
454
455 error devm_gpio_request_one(pdev-dev, button-gpio,
456 GPIOF_IN, desc);
457 if (error 0) {
458 dev_err(dev, Failed to request GPIO %d, error %d\n,
459 button-gpio, error);
460 return error;
......
488 isr gpio_keys_gpio_isr;
489 irqflags IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
490
491 } else {
492 if (!button-irq) {
493 dev_err(dev, No IRQ specified\n);
494 return -EINVAL;
495 }
496 bdata-irq button-irq;
......
506
507 isr gpio_keys_irq_isr;
508 irqflags 0;
509 }
510
511 input_set_capability(input, button-type ?: EV_KEY,
button-code);
......
540 return 0;
541 }
第511 行调用input_set_capability 函数设置EV_KEY 事件以及KEY 的按键类型也就是KEY 作为哪个按键我们会在设备树里面设置指定的KEY 作为哪个按键。 一切都准备就绪以后剩下的就是等待按键按下然后向Linux 内核上报事件事件上报是在gpio_keys_irq_isr 函数中完成的此函数内容如下
392 static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
393 {
394 struct gpio_button_data *bdata dev_id;
395 const struct gpio_keys_button *button bdata-button;
396 struct input_dev *input bdata-input;
397 unsigned long flags;
398
399 BUG_ON(irq ! bdata-irq);
400
401 spin_lock_irqsave(bdata-lock, flags);
402
403 if (!bdata-key_pressed) {
404 if (bdata-button-wakeup)
405 pm_wakeup_event(bdata-input-dev.parent, 0);
406
407 input_event(input, EV_KEY, button-code, 1);
408 input_sync(input);
409
410 if (!bdata-release_delay) {
411 input_event(input, EV_KEY, button-code, 0);
412 input_sync(input);
413 goto out;
414 }
415
416 bdata-key_pressed true;
417 }
418
419 if (bdata-release_delay)
420 mod_timer(bdata-release_timer,
421 jiffies msecs_to_jiffies(bdata-release_delay));
422 out:
423 spin_unlock_irqrestore(bdata-lock, flags);
424 return IRQ_HANDLED;
425 }gpio_keys_irq_isr 是按键中断处理函数第407 行向Linux 系统上报EV_KEY 事件表示按键按下。第408 行使用input_sync 函数向系统上报EV_REP 同步事件。
综上所述Linux 内核自带的gpio_keys.c 驱动文件思路和我们前面编写的keyinput.c 驱动文件基本一致。都是申请和初始化input_dev设置事件向Linux 内核注册input_dev。最终在按键中断服务函数或者消抖定时器中断服务函数中上报事件和按键值。
自带按键驱动程序的使用
要使用Linux 内核自带的按键驱动程序很简单只需要根据 Documentation/devicetree/bindings/input/gpio-keys.txt 这个文件在设备树中添加指定的设备节点即可节点要求如下 ①、节点名字为“gpio-keys”。 ②、gpio-keys 节点的compatible 属性值一定要设置为“gpio-keys”。 ③、所有的KEY 都是gpio-keys 的子节点每个子节点可以用如下属性描述自己 gpiosKEY 所连接的GPIO 信息。 interruptsKEY 所使用GPIO 中断信息不是必须的可以不写。 labelKEY 名字 linux,codeKEY 要模拟的按键也就是示例代码58.1.2.4 中的这些按键。 ④、如果按键要支持连按的话要加入autorepeat。 打开imx6ull-alientek-emmc.dts根据上面的要求创建对应的设备节点设备节点内容如下所示
1 gpio-keys {
2 compatible gpio-keys;
3 #address-cells 1;
4 #size-cells 0;
5 autorepeat;
6 key0 {
7 label GPIO Key Enter;
8 linux,code KEY_ENTER;
9 gpios gpio1 18 GPIO_ACTIVE_LOW;
10 };
11 };第5 行autorepeat 表示按键支持连按。 第6~10 行ALPHA 开发板KEY 按键信息名字设置为“GPIO Key Enter”这里我们将开发板上的KEY 按键设置为“EKY_ENTER”这个按键也就是回车键效果和键盘上的回车键一样。后面学习LCD 驱动的时候需要用到此按键因为Linux 内核设计的10 分钟以后LCD关闭也就是黑屏就跟我们用电脑或者手机一样一定时间以后关闭屏幕。这里将开发板上的KEY 按键注册为回车键当LCD 黑屏以后直接按一下KEY 按键即可唤醒屏幕就跟当电 脑熄屏以后按下回车键即可重新打开屏幕一样。
最后设置KEY 所使用的IO 为GPIO1_IO18一定要检查一下设备树看看此GPIO 有没有被用到其他外设上如果有的话要删除掉相关代码
重新编译设备树然后用新编译出来的imx6ull-alientek-emmc.dtb 启动Linux 系统系统启动以后查看/dev/input 目录看看都有哪些文件结果如图58.5.2.1 所示 从图58.5.2.1 可以看出存在event1 这个文件这个文件就是KEY 对应的设备文件使用hexdump 命令来查看/dev/input/event1 文件输入如下命令
hexdump /dev/input/event1然后按下ALPHA 开发板上的按键终端输出图58.5.2.2 所示内容
如果按下KEY 按键以后会在终端上输出图58.5.2.2 所示的信息那么就表示Linux 内核的按键驱动工作正常。至于图58.5.2.2 中内容的含义大家就自行分析这个已经在58.4.2 小节详细的分析过了这里就不再讲解了。 大家如果发现按下KEY 按键以后没有反应那么请检查一下三方面 ①、是否使能Linux 内核KEY 驱动。 ②、设备树中gpio-keys 节点是否创建成功。 ③、在设备树中是否有其他外设也使用了KEY 按键对应的GPIO但是我们并没有删除掉这些外设信息。检查Linux 启动log 信息看看是否有类似下面这条信息
gpio-keys gpio_keysFailed to request GPIO 18, error -16上述信息表示GPIO 18 申请失败失败的原因就是有其他的外设正在使用此GPIO。