【TINY4412】LINUX移植笔记:(19)设备树KEY驱动

环境

宿主机 : 虚拟机 Ubuntu 16.04 LTS / X64
目标板[底板]: Tiny4412SDK - 1506
目标板[核心板]: Tiny4412 - 1412
LINUX内核: 4.12.0
交叉编译器: arm-none-linux-gnueabi-gcc(gcc version 4.8.3 20140320)
日期: 2017-9-9 20:30:09
作者: SY

简介

ARM支持矩阵键盘接口,参考手册:P55 Keypad Interface

  • 行:0~13
  • 列:0~7

最多支持 14 X 8 = 112 个按键

KEY

KEY

默认被上拉为高电平,按键按下检测到低电平。

由于开发板在 设计了按键,在 没有设计,看了源码samsung-keypad.c,并不支持 为0的情况,因此使用gpio_keys.c

设备树

参考文档:./Documentation/devicetree/bindings/input/gpio_keys.txt

exynos4.dtsi

1
2
3
4
5
6
7
8
9
10
/ {
interrupt-parent = <&gic>;
gic: interrupt-controller@10490000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x10490000 0x10000>, <0x10480000 0x10000>;
};
};

作为中断控制器的父类神一般的存在,对他的子类 interrupt 字段中的元素个数做了说明,包含3个元素<中断类型 中断号 触发方式>

exynos4412-pinctrl.dtsi

1
2
3
4
5
6
7
8
9
pinctrl_1: pinctrl@11000000 {
gpx3: gpx3 {
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
};

对他的子类 interrupt 字段中的元素个数做了说明,包含2个元素<引脚号 触发方式>

在自己的 exynos4412-tiny4412.dts 配置按键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
gpio_keys {
compatible = "gpio-keys";
pinctrl-names = "default";
key1 {
interrupt-parent = <&gpx3>;
interrupts = <2 IRQ_TYPE_EDGE_FALLING>;
gpios = <&gpx3 2 GPIO_ACTIVE_LOW>;
linux,code = <KEY_1>;
label = "key1";
debounce-interval = <10>;
wakeup-source;
};
key2 {
interrupt-parent = <&gpx3>;
interrupts = <3 IRQ_TYPE_EDGE_FALLING>;
gpios = <&gpx3 3 GPIO_ACTIVE_LOW>;
linux,code = <KEY_2>;
label = "key2";
debounce-interval = <10>;
wakeup-source;
};
key3 {
interrupt-parent = <&gpx3>;
interrupts = <4 IRQ_TYPE_EDGE_FALLING>;
gpios = <&gpx3 4 GPIO_ACTIVE_LOW>;
linux,code = <KEY_3>;
label = "key3";
debounce-interval = <10>;
wakeup-source;
};
key4 {
interrupt-parent = <&gpx3>;
interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
gpios = <&gpx3 5 GPIO_ACTIVE_LOW>;
linux,code = <KEY_4>;
label = "key4";
debounce-interval = <10>;
wakeup-source;
};
};
1
2
3
4
Device Drivers --->
Input device support --->
[*] Keyboards --->
<*> GPIO Buttons

应用程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/*
* beep driver for tiny4412
*
* Copyright (c) 2017
* Author: SY <1530454315@qq.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
struct input_event {
struct timeval time;
unsigned short int type;
unsigned short int code;
signed int value;
};
#if 0
static void help(void)
{
printf("Usage: ./key <id>\n");
}
#endif
bool esc = false;
static void sigint_handler(int dunno)
{
switch (dunno) {
case SIGINT:
esc = true;
printf("< Ctrl+C > Press.\n");
break;
default:
break;
}
}
static void* read_handler(void* data)
{
printf("thread run.\n");
int epfd = epoll_create1(0);
if (epfd < 0) {
perror("epoll_create1");
return NULL;
}
int evfd = open("/dev/input/event1", O_RDONLY);
if (evfd < 0) {
perror("[open]");
esc = true;
}
struct epoll_event epoll_event;
epoll_event.events = EPOLLIN;
epoll_event.data.fd = evfd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, evfd, &epoll_event) < 0) {
perror("[epoll_ctl]");
esc = true;
}
printf("start epoll...\n");
struct input_event event;
const int MAX_EVENT_NUMS = 10;
const int TIMEOUT = 100;
struct epoll_event *events = calloc(MAX_EVENT_NUMS, sizeof(struct epoll_event));
if (!events) {
perror("mem calloc");
esc = true;
}
while (esc == false) {
int nums = epoll_wait(epfd, events, MAX_EVENT_NUMS, TIMEOUT);
for (int i=0; i<nums; ++i) {
if (events[i].events & (EPOLLERR | EPOLLHUP)) {
perror("epoll");
continue;
} else if ((events[i].data.fd == evfd) && (events[i].events & EPOLLIN)) {
int ret = read(evfd, &event, sizeof(event));
if (ret < 0) {
break;
}
//printf("[key] nums=%d code=%d value=%d\n", nums, event.code, event.value);
switch (event.code) {
case KEY_1:
if (event.value) {
printf("[KEY1] Press.\n");
} else {
printf("[KEY1] Release.\n");
}
break;
case KEY_2:
if (event.value) {
printf("[KEY2] Press.\n");
} else {
printf("[KEY2] Release.\n");
}
break;
case KEY_3:
if (event.value) {
printf("[KEY3] Press.\n");
} else {
printf("[KEY3] Release.\n");
}
break;
case KEY_4:
if (event.value) {
printf("[KEY4] Press.\n");
} else {
printf("[KEY4] Release.\n");
}
break;
default:
break;
}
}
}
}
if (events) {
free(events);
}
close(epfd);
close(evfd);
printf("thread exit.\n");
pthread_exit(NULL);
return NULL;
}
int main(int argc, char **argv)
{
pthread_t thread_read;
int ret = pthread_create(&thread_read, NULL, read_handler, NULL);
if (ret) {
perror("[thread_create]");
return 1;
}
/* Register signal */
signal(SIGINT, sigint_handler);
pthread_join(thread_read, NULL);
printf("done!\n");
return 0;
}

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Linux modules compile
# Author : SY
# Time : 2017-8-10 22:29:24
#############################################################################
CC = arm-none-linux-gnueabi-gcc
CFLAGS = -Wall -std=gnu99 -pthread
TARGET = key
OBJS = $(TARGET).o
INSTALL_PATH = /opt/fs/rootfs/rootfs/tmp/
$(TARGET) : $(OBJS)
$(CC) $(CFLAGS) -o $@ $<
install:
chmod 755 $(TARGET)
cp $(TARGET) $(INSTALL_PATH)
clean:
rm -rf *.o $(TARGET)

编译

第一次编译出现警告

1
2
3
4
5
6
key.o: In function `main':
key.c:(.text+0x94): undefined reference to `pthread_create'
key.c:(.text+0xd0): undefined reference to `pthread_join'
collect2: error: ld returned 1 exit status
Makefile:15: recipe for target 'key' failed
make: *** [key] Error 1

解决:Undefined reference to pthread_create in Linux

Makefile 中加入 -pthread

再次编译

1
2
3
key.c: In function 'read_handler':
key.c:63:2: warning: implicit declaration of function 'epoll_createl' [-Wimplicit-function-declaration]
int epfd = epoll_createl(0);

老是提示变量未声明,找来找去始终找不到原因,我们使用的编译器:arm-none-linux-gnueabi-gcc,我们使用 #include <sys/epoll.h> ,编译器会去路径:/opt/arm-2014.05/arm-none-linux-gnueabi/libc/usr/include/ 找到指定文件

1
2
3
4
5
6
7
8
9
/* Creates an epoll instance. Returns an fd for the new instance.
The "size" parameter is a hint specifying the number of file
descriptors to be associated with the new instance. The fd
returned by epoll_create() should be closed with close(). */
extern int epoll_create (int __size) __THROW;
/* Same as epoll_create but with an FLAGS parameter. The unused SIZE
parameter has been dropped. */
extern int epoll_create1 (int __flags) __THROW;

明明已经包含进去,却提示未声明,只可能拼写错误。

修改源文件:

1
int epfd = epoll_create1(0);

再次编译 OK 了,错误的原因是把:1 写成了 l,妈蛋,找了两个晚上原因。

烧录

使用 NFS 方式进入 Linux

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@TINY4412:~]# cd tmp/
[root@TINY4412:/tmp]# ls
beep gdbserver key
[root@TINY4412:/tmp]# ./key
thread run.
start epoll...
[KEY1] Press.
[KEY1] Release.
[KEY2] Press.
[KEY2] Release.
[KEY3] Press.
[KEY3] Release.
[KEY4] Press.
[KEY4] Release.
^C< Ctrl+C > Press.
thread exit.
done!

代码分析

APP 中使用了 2 个线程,本来只使用主线程也可以实现,但是在按下 Ctrl + C 时,进程会被操作系统终结,导致不能清理占用的资源,造成内存泄漏。

增加 Ctrl + C 重写

默认在按下 Ctrl + C 时,Linux 向我们的进程发送 SIGINT 信号。

下面是 Linux 的标准信号,是不可靠的,没有放进队列,因此有可能丢失信号。后来增加 32~63 信号为扩展信号,也是实时信号,支持队列,为可靠信号。当标准信号和扩展信号同时挂起时,优先响应标准信号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
#define SIGSTKFLT 16
#define SIGCHLD 17
#define SIGCONT 18
#define SIGSTOP 19
#define SIGTSTP 20
#define SIGTTIN 21
#define SIGTTOU 22
#define SIGURG 23
#define SIGXCPU 24
#define SIGXFSZ 25
#define SIGVTALRM 26
#define SIGPROF 27
#define SIGWINCH 28
#define SIGIO 29
#define SIGPOLL SIGIO
/*
#define SIGLOST 29
*/
#define SIGPWR 30
#define SIGSYS 31
#define SIGUNUSED 31

SIGINT :程序终止信号,按下 Ctrl + C 时发出

SIGHUP:终止终端连接信号,当用户退出终端时发出

上述的信号都有默认的行为,例如:SIGINT 信号默认就是关闭当前进程。当然,我们可以重写方法,改变信号发生后,程序的行为。其实类似于修改函数指针,让其指向我们的自定义方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Register signal */
signal(SIGINT, sigint_handler);
static void sigint_handler(int dunno)
{
switch (dunno) {
case SIGINT:
esc = true;
printf("< Ctrl+C > Press.\n");
break;
default:
break;
}
}

添加上述语句可以捕获 SIGINT 信号,执行 sigint_handler 函数,虽然我设置 esc == false 退出 main 方法,但是测试按下 Ctrl + C 并没有任何反应,只有按下任意一个按键后,才能退出进程。

发现原因是: read() 为阻塞函数,只有按下按键才能退出函数。

顺便说一句,使用 signal() 函数重定向后,在按下 Ctrl + C 后,你的进程不会被杀死了!

多线程

如果使用单线程的话, read() 阻塞后,就不能进行其他操作,因此开辟一个新的线程用来执行 read()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main(int argc, char **argv)
{
pthread_t thread_read;
int ret = pthread_create(&thread_read, NULL, read_handler, NULL);
if (ret) {
perror("[thread_create]");
return 1;
}
pthread_join(thread_read, NULL);
}
static void* read_handler(void* data)
{
...
pthread_exit(NULL);
}

上述就是一个多线程的框架:

pthread_create :用于创建一个新的线程

pthread_join :用于主线程等待子线程执行结束,是一个阻塞函数。

read_handler :为新开的线程,和其他线程共享地址空间,但是有自己的堆栈。

按下 Ctrl + C 后,新开的线程也没有自动结束,这样主线程仍然阻塞。这就强迫你想到使用异步 IO 方式。

异步 IO

查找资料,发现大家用的最多的是:select poll epoll

对于 read() 等类似的阻塞型函数,你不能使用该函数测试是否数据可读写,因为一旦不可读写,将导致当前线程阻塞,那么异步 IO 无非是提供一种方法,帮你做这个工作。

select

1
2
3
4
5
6
7
8
9
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};

对于形参:readfds writefds exceptfds 表示三种监事类型,一旦执行该函数,要么有相应事件就绪函数返回,要么超时返回,要么参数错误返回。可以根据返回值做相应操作。

nfds :表示监视的设备描述符的个数

参考:Linux Programmer’s Manual SELECT(2)

select 当然也有缺点:

  • 监视的 fd 数量有限
  • 对于 fd 采用线性扫描的方式,当监控的 fd 较多时,占用的资源会很多,而且遍历速度会变慢

poll

1
2
3
4
5
6
7
8
9
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};

缺点:

  • poll 返回后,需要遍历并比对所有 fdrevents ,才能判断哪些 fd 可以操作,耗时

epoll

看名字就知道是 poll 的改良版。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <sys/epoll.h>
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event
{
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
} __EPOLL_PACKED;
extern int epoll_create1 (int __flags) __THROW;
extern int epoll_ctl (int __epfd, int __op, int __fd,
struct epoll_event *__event) __THROW;
extern int epoll_wait (int __epfd, struct epoll_event *__events,
int __maxevents, int __timeout);

优点:

当收到可读写事件时,只返回有效的事件的个数,这样可以节省大量遍历的时间。

总结

在监视的描述符较少时,select 比较合适,因为简单。

在监视的描述符较多时,epoll 比较合适,效率比较高。

参考

聊聊IO多路复用之select、poll、epoll详解

当执行kill -9 PID时系统发生了什么

聊聊Linux 五种IO模型

SY wechat
扫一扫,用手机访问本站