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

【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-8-13 09:43:25
作者: 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

/ {
    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

pinctrl_1: pinctrl@11000000 {
    gpx3: gpx3 {
        gpio-controller;
        #gpio-cells = <2>;

        interrupt-controller;
        #interrupt-cells = <2>;
    };
};

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

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

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;
    };
};
Device Drivers  ---> 
    Input device support  --->
        [*]   Keyboards  ---> 
            <*>   GPIO Buttons 

应用程序

/*
 * 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

# 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) 

编译

第一次编译出现警告

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

再次编译

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/ 找到指定文件

/* 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;

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

修改源文件:

int epfd = epoll_create1(0);

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

烧录

使用 NFS 方式进入 Linux

[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 信号为扩展信号,也是实时信号,支持队列,为可靠信号。当标准信号和扩展信号同时挂起时,优先响应标准信号。

#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 信号默认就是关闭当前进程。当然,我们可以重写方法,改变信号发生后,程序的行为。其实类似于修改函数指针,让其指向我们的自定义方法。

/* 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()

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
#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
#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 的改良版。

#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模型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值