Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长
题目中用了“接纳”俩字。其实学习就是一个接纳的过程。接纳新的知识,从而转化为自己知识宝库的一部分。那今天学习的input子系统和今天的主题接纳有什么关系呢?接纳就是一个从输入转为为内在并输出应用的过程。
在日常的工作学习中,学会接纳,接纳同事同学朋友老师,更要学会接纳自己,接纳这个社会的不完美。
本笔记主要学习Linux input子系统实验,主要内容包括input子系统相关基础知识、原理图分析、驱动程序开发及测试、Linux自带按键驱动程序的使用。其中Linux自带按键驱动程序的使用将在下一节中进行。
一、input子系统
Linux内核专门做了一个input子系统框架来处理输入事件。输入设备本质上是字符设备,只是在此基础上套上了input框架,用户只需上报输入事件,input核心层负责处理这些事件。
1、input子系统简介
input子系统分为input驱动层、input核心层、input事件处理层,最终给用户空间提供可访问的设备节点。
input子系统框架如下图:
所有的输入设备以文件的形式供用户空间应用程序使用。
编写驱动程序时只需要关注中间的驱动层、核心层和时间层。
驱动层:输入设备具体的驱动程序。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交互。
2、input驱动编写流程
input核心层会向Linux内核注册一个字符设备,input.c就是input输入子系统的核心层。
err = class_register(&input_class);
注册一个 input 类,这样系统启动以后就会在/sys/class 目录下有一个 input 子目录。
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES, "input");
注册一个字符设备,主设备号为 INPUT_MAJOR,INPUT_MAJOR定义如下:
#define INPUT_MAJOR 13
input子系统所有设备的主设备号都是13,在使用input处理输入设备的时候就不需要去注册字符设备了。只需向系统注册一个input_device即可。
1)、注册input_dev
在使用input子系统时,只需要注册一个input设备即可。input_dev结构体标识input设备。
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图 */
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)]; /*开关状态的位图 */
......
evbit、 keybit、 relbit 等等都是存放不同事件对应的值。
使用input_allocate_device 函数来申请一个 input_dev。
struct input_dev *input_allocate_device(void)
使用 input_free_device 函数来释放掉前面申请到的input_dev:
void input_free_device(struct input_dev *dev)
初始化这个 input_dev,需要初始化的内容主要为事件类型(evbit)和事件值(keybit)这两种
input_dev初始化完成以后,需要使用input_register_device函数向Linux内核注册input_dev。
int input_register_device(struct input_dev *dev)
同样,使用input_unregister_device向Linux内核注销input_dev.
void input_unregister_device(struct input_dev *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。
struct input_dev *inputdev; /* input 结构体变量 */
定义一个 input_dev 结构体指针变量。
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 }
驱动入口函数,在此函数中完成 input_dev 的申请、设置、注册等工作。第 7行调用 input_allocate_device 函数申请一个 input_dev。第 10~23 行都是设置 input 设备事件和按键值,这里用了三种方法来设置事件和按键值。第 27 行调用 input_register_device 函数向 Linux内核注册 inputdev。
32 /* 驱动出口函数 */
33 static void __exit xxx_exit(void)
34 {
35 input_unregister_device(inputdev); /* 注销 input_dev */
36 input_free_device(inputdev); /* 删除 input_dev */
37 }
驱动出口函数,第 35 行调用 input_unregister_device 函数注销前面注册的input_dev,第 36 行调用 input_free_device 函数删除前面申请的 input_dev。
2)、上报输入事件
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_report_key 函数。
上报事件以后还需要使用 input_sync 函数来告诉 Linux 内核 input 子系统上报结束,
input_sync 函数本质是上报一个同步事件。
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 }
3、input_event结构体
Linux 内核使用 input_event 这个结构体来表示所有的输入事件。
24 struct input_event {
25 struct timeval time;
26 __u16 type;
27 __u16 code;
28 __s32 value;
29 };
time:时间,也就是此事件发生的时间,为 timeval 结构体类型。
type: 事件类型,比如 EV_KEY,表示此次事件为按键事件,此成员变量为 16 位。
code: 事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如: KEY_0、 KEY_1
等等这些按键。此成员变量为 16 位。
value: 值,比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下,如果为 1 的
话说明按键按下,如果为 0 的话说明按键没有被按下或者按键松开了。
二、硬件原理图
三、试验程序编写
1、修改设备树文件
1)、添加pinctrl节点
1 pinctrl_key: keygrp {
2 fsl,pins = <
3 MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
4 >;
5 };
2)、添加KEY设备节点
1 key {
2 #address-cells = <1>;
3 #size-cells = <1>;
4 compatible = "atkalpha-key";
5 pinctrl-names = "default";
6 pinctrl-0 = <&pinctrl_key>;
7 key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
8 status = "okay";
9 };
3)、检查 PIN 为 UART1_CTS_B 这个 PIN 有没有被其他的 pinctrl 节点使用,如果有使用的话就要屏蔽掉,然后再检查 GPIO1_IO18这个 GPIO 有没有被其他外设使用,如果有的话也要屏蔽掉。
2、按键input驱动程序编写
struct input_dev *inputdev; /* input 结构体 */
在设备结构体中定义一个 input_dev 指针变量。
93 if(value == 0){ /* 按下按键 */
94 /* 上报按键值 */
95 //input_event(dev->inputdev, EV_KEY, keydesc->value, 1);
96 input_report_key(dev->inputdev, keydesc->value, 1);/*1, 按下*/
97 input_sync(dev->inputdev);
98 } else { /* 按键松开 */
99 //input_event(dev->inputdev, EV_KEY, keydesc->value, 0);
100 input_report_key(dev->inputdev, keydesc->value, 0);
101 input_sync(dev->inputdev);
102 }
在按键消抖定时器处理函数中上报输入事件,也就是使用 input_report_key函数上报按键事件以及按键值,最后使用 input_sync 函数上报一个同步事件!
155 /* 申请 input_dev */
156 keyinputdev.inputdev = input_allocate_device();
157 keyinputdev.inputdev->name = KEYINPUT_NAME;
158 #if 0
159 /* 初始化 input_dev,设置产生哪些事件 */
160 __set_bit(EV_KEY, keyinputdev.inputdev->evbit); /*按键事件 */
161 __set_bit(EV_REP, keyinputdev.inputdev->evbit); /* 重复事件 */
162
163 /* 初始化 input_dev,设置产生哪些按键 */
164 __set_bit(KEY_0, keyinputdev.inputdev->keybit);
165 #endif
166
167 #if 0
168 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
169 keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |=
BIT_MASK(KEY_0);
170 #endif
171
使用 input_allocate_device 函数申请 input_dev,然后设置相应的事件以及事件码(也就是 KEY 模拟成那个按键,这里我们设置为 KEY_0)。最后使用 input_register_device函数向 Linux 内核注册 input_dev。
210 /* 释放 input_dev */
211 input_unregister_device(keyinputdev.inputdev);
212 input_free_device(keyinputdev.inputdev);
当注销 input 设备驱动的时候使用 input_unregister_device 函数注销掉前面注册的 input_dev,最后使用 input_free_device 函数释放掉前面申请的 input_dev。
3、编写测试APP
static struct input_event inputevent;
定义了一个 inputevent 变量,此变量为 input_event 结构体类型。
err = read(fd, &inputevent, sizeof(inputevent));
当我们向 Linux 内核成功注册 input_dev 设备以后,会在/dev/input 目录下生成一个名为“eventX(X=0….n)”的文件,这个/dev/input/eventX 就是对应的 input 设备文件。我们读取这个文件就可以获取到输入事件信息,比如按键值什么的。使用read函数读取输入设备文件,也就是/dev/input/eventX,读取到的数据按照 input_event 结构体组织起来。获取到输入事件以后(input_event 结构体类型)使用 switch case 语句来判断事件类型,本章实验我们设置的事件类型
为 EV_KEY,因此只需要处理 EV_KEY 事件即可。比如获取按键编号(KEY_0 的编号为 11)、
获取按键状态,按下还是松开的?
四、运行测试
1、编译驱动程序和测试APP
obj-m := keyinput.o
make -j32
编译成功以后就会生成一个名为“keyinput.ko”的驱动模块文件。
arm-linux-gnueabihf-gcc keyinputApp.c -o keyinputApp
编译成功以后就会生成 keyinputApp 这个应用程序。
2、运行测试
编译出来 keyinput.ko 和 keyinputApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15
目录中,重启开发板,进入到目录 lib/modules/4.1.15 中。
输入如下命令加载 keyinput.ko 这个驱动模块。
depmod //第一次加载驱动的时候需要运行此命令
modprobe keyinput.ko //加载驱动模块
当驱动模块加载成功以后再来看一下/dev/input 目录:
多了一个 event1 文件,因此/dev/input/event1 就是我们注册的驱动所对应的设备文件。
输入如下命令:
./keyinputApp /dev/input/event1
然后按下开发板上的 KEY 按键,结果如图:
当我们按下或者释放开发板上的按键以后都会在终端上输出相应的内容,提示我们哪个按键按下或释放了,在 Linux 内核中 KEY_0 为 11。
五、总结:
本笔记主要学习Linux input子系统实验,主要内容包括input子系统相关基础知识、原理图分析、驱动程序开发及测试、Linux自带按键驱动程序的使用。
本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。