嵌入式Linux—输入子系统

输入系统

常见的输入设备有键盘、鼠标、遥控杆、书写板、触摸屏等等,用户通过这些输入设备与Linux系统进行数据交换。

内核中怎样表示一个输入设备

// include/linux/input.h
struct input_dev {
    const char *name;  //设备名称
    const char *phys;  //设备物理路径
    const char *uniq;  //设备唯一标识码
    struct input_id id;

    unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];   //支持什么类型的输入事件
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //支持按键输入事件的话,支持哪些按键(键盘)
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //支持相对位移事件的话,支持哪些
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
    unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
    unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
    unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
    unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

    .......

    };

查看所有的输入设备:

ls /dev/input/* -l

查看输入设备的信息:

cat /proc/bus/input/devices

得到如下信息:

[root@imx6ull:~]# cat /proc/bus/input/devices
I: Bus=0019 Vendor=0000 Product=0000 Version=0000
N: Name="20cc000.snvs:snvs-powerkey"
P: Phys=snvs-pwrkey/input0
S: Sysfs=/devices/soc0/soc/2000000.aips-bus/20cc000.snvs/20cc000.snvs:snvs-powerkey/input/input0
U: Uniq=
H: Handlers=kbd event0 evbug
B: PROP=0
B: EV=3
B: KEY=100000 0 0 0

I: Bus=0018 Vendor=dead Product=beef Version=28bb  //设备ID(定义在input.h的struct input_id结构体)
N: Name="goodix-ts"      //名称
P: Phys=input/ts         //物理地址
S: Sysfs=/devices/virtual/input/input1  //sys系统地址
U: Uniq=          //标识号(无)
H: Handlers=event1 evbug
B: PROP=2        //设备属性
B: EV=b          //支持何种输入事件 
B: KEY=1c00 0 0 0 0 0 0 0 0 0 0   //设备具有的键
B: ABS=6e18000 0

I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio-keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/soc0/gpio-keys/input/input2
U: Uniq=
H: Handlers=kbd event2 evbug
B: PROP=0
B: EV=3
B: KEY=c

APP可以获得什么数据

// include/linux/input.h
struct input_value {
    __u16 type;   //当前数据的事件类型
    __u16 code;   //当前事件类型下的哪一个事件
    __s32 value;  //
};

Type的内容:

// include/uapi/linux/input-event-codes.h
/*
 * Event types
 */

#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
#define EV_SND          0x12
#define EV_REP          0x14
#define EV_FF           0x15
#define EV_PWR          0x16
#define EV_FF_STATUS        0x17
#define EV_MAX          0x1f
#define EV_CNT          (EV_MAX+1)

code的内容(以EV_KEY举例)

// include/uapi/linux/input-event-codes.h
#define KEY_RESERVED        0
#define KEY_ESC         1
#define KEY_1           2
#define KEY_2           3
#define KEY_3           4
#define KEY_4           5
#define KEY_5           6
#define KEY_6           7
#define KEY_7           8
#define KEY_8           9
#define KEY_9           10
#define KEY_0           11

获取输入设备信息实例

两个ioctl的request参数说明(input.h)
request 说明
EVIOCGID 返回输入设备ID
EVIOCGBIT(ev,len) 获取输入设备支持的事件类型列表

ev值的说明:ev参数表示要获取的事件类型,它是一个整数值

  • 当ev=0,表示要获取输入设备支持的所有事件类型列表,包括键盘事件、鼠标事件、相对事件、绝对事件、事件同步、杂项事件等。
  • 当ev=1,表示要获取输入设备支持的键盘事件类型列表。
  • 当ev=2,表示要获取输入设备支持的相对事件类型列表。

EVIOCGBIT的iotcl调用说明:必须使用

len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit);    
//len是evbit的实际读取大小,如果单独使用sizeof(evbit)得到len,将发生段错误
源码:
#include 
#include 
#include 
#include 
#include 
#include 

/* 用法:./get_input_info /dev/input/event0 */
int main(int argc, char const **argv)
{
    int fd;
    struct input_id id;
    int err;
    unsigned char byte;
    unsigned int evbit[2];
    int i;
    int bit;
    unsigned int len;
    char *ev_names[] = {
        "EV_SYN ",
        "EV_KEY ",
        "EV_REL ",
        "EV_ABS ",
        "EV_MSC ",
        "EV_SW  ",
        "NULL ",
        "NULL ",
        "NULL ",
        "NULL ",
        "NULL ",
        "NULL ",
        "NULL ",
        "NULL ",
        "NULL ",
        "NULL ",
        "NULL ",
        "EV_LED ",
        "EV_SND ",
        "NULL ",
        "EV_REP ",
        "EV_FF  ",
        "EV_PWR ",
    };

    if(argc != 2) 
    {
        printf("Usage: %s n", argv[0]);
        return -1;
    }
    fd = open(argv[1], O_RDWR);
    if(fd == -1) 
    {
        printf("can not open %sn", argv[1]);
        return -1;
    }
    err = ioctl(fd, EVIOCGID, &id);      //返回输入设备ID
    if(err == 0)
    {
        printf("bustype = 0x%xn", id.bustype );
        printf("vendor  = 0x%xn", id.vendor  );
        printf("product = 0x%xn", id.product );
        printf("version = 0x%xn", id.version );
    }
    len = ioctl(fd, EVIOCGBIT(0,sizeof(evbit)), evbit);   //返回输入事件类型
    printf("support ev type:n");
    for(i = 0;i 
实验结果:
[root@imx6ull:/mnt]# ./get_input_info /dev/input/event0
bustype = 0x19
vendor  = 0x0
product = 0x0
version = 0x0
support ev type:
EV_SYN
EV_KEY
[root@imx6ull:/mnt]# ./get_input_info /dev/input/event1
bustype = 0x18
vendor  = 0xdead
product = 0xbeef
version = 0x28bb
support ev type:
EV_SYN
EV_KEY
EV_ABS
[root@imx6ull:~]# cat /proc/bus/input/devices
I: Bus=0019 Vendor=0000 Product=0000 Version=0000
N: Name="20cc000.snvs:snvs-powerkey"
P: Phys=snvs-pwrkey/input0
S: Sysfs=/devices/soc0/soc/2000000.aips-bus/20cc000.snvs/20cc000.snvs:snvs-powerkey/input/input0
U: Uniq=
H: Handlers=kbd event0 evbug
B: PROP=0
B: EV=3
B: KEY=100000 0 0 0

I: Bus=0018 Vendor=dead Product=beef Version=28bb
N: Name="goodix-ts"
P: Phys=input/ts
S: Sysfs=/devices/virtual/input/input1
U: Uniq=
H: Handlers=event1 evbug
B: PROP=2
B: EV=b
B: KEY=1c00 0 0 0 0 0 0 0 0 0 0
B: ABS=6e18000 0

I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio-keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/soc0/gpio-keys/input/input2
U: Uniq=
H: Handlers=kbd event2 evbug
B: PROP=0
B: EV=3
B: KEY=c

结论:EV值与程序输出的type结果一致

查询和休眠唤醒方式读输入事件

所谓的阻塞与非阻塞,是在open处声明。当设置为阻塞方式,如果没有输入事件,整个进程都在阻塞态

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* 用法:./get_input_info /dev/input/event0 */
int main(int argc, char const **argv)
{
    int fd;
    unsigned int len;
    struct input_event event;   //read读到的是input_event类型的结构体

    if(argc  [noblock]n", argv[0]);
        return -1;
    }
    if(argc == 3 && !strcmp(argv[2], "noblock"))
    {
        fd = open(argv[1], O_RDWR | O_NONBLOCK);  //非阻塞(查询)
    }
    else
    {
        fd = open(argv[1], O_RDWR);
    }
    if(fd == -1) 
    {
        printf("can not open %sn", argv[1]);
        return -1;
    }

    while(1)
    {
        len = read(fd, &event, sizeof(event));     //阻塞方式下,进程阻塞在此
        if(len == sizeof(event))
        {
            printf("type = 0x%x, code = 0x%x, value = 0x%x", event.type, event.code, event.value);
        }
        else
        {
            printf("read err %d", len);
        }
    }
    return 0;

}

实验现象:
  • 查询方式(非阻塞):反复查询,输出"read err",直到操作输入设备时,输出内容更改为输入事件内容
  • 休眠-唤醒方式(阻塞):只有操作屏幕,才会输出事件内容

POLL方式读输入事件

poll会在设定的时间内进行监听,当改时间内有输入事件返回或超过设定时间没有事件返回,poll都将唤醒。poll/select函数可以监测多个文件,可以监测多种事件。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* 用法:./get_input_info /dev/input/event0 */
int main(int argc, char const **argv)
{
    int fd;
    struct input_event event;   //read读到的是input_event类型的结构体
    struct pollfd pollfd;
    nfds_t nfds = 1;      //同时打开一个文件

    if(argc != 2) 
    {
        printf("Usage: %s n", argv[0]);
        return -1;
    }
    fd = open(argv[1], O_RDWR | O_NONBLOCK);  //非阻塞(查询)
    if(fd == -1) 
    {
        printf("can not open %sn", argv[1]);
        return -1;
    }
    while(1)
    {
        pollfd.fd = fd;
        pollfd.events = POLLIN;
        pollfd.revents = 0;     //revents初始化为0,当有输入事件传入,内核改写revents
        poll(&pollfd, nfds, 3000);    //poll等待时间为3s
        if(pollfd.revents == POLLIN)      //只有poll函数返回了数据,才调用read
        {
            while(read(fd, &event, sizeof(event)) == sizeof(event))    //把一次获取到的数据读完再退出
            {
                printf("type = 0x%x, code = 0x%x, value = 0x%xn", event.type, event.code, event.value);
            }
        }
        else if(pollfd.revents == 0)
        {
            printf("time outn");
        }
        else
        {
            printf("read errn");
        }
    }
    return 0;

}
关于POLL实现多路复用IO
struct pollfd pollfd[n];    //n为文件个数
nfds_t nfds = n;      //同时打开n个文件

.......

if(pollfd[0].revents == POLLIN){}     //依次访问revents
if(pollfd[1].revents == POLLIN){}

.......

异步通知方式读输入事件

[补充]fcntl的五个功能:

  • 复制一个现有的描述符(cmd=F_DUPFD).
  • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
  • 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
  • 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
  • 获得/设置记录锁(cmd=F_GETLK , F_SETLK或F_SETLKW).
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int fd;

void sig_func(int sig)
{
    struct input_event event;
    while(read(fd, &event, sizeof(event)) == sizeof(event))
    {
        printf("type = 0x%x, code = 0x%x, value = 0x%xn", event.type, event.code, event.value);
    }
}

/* 用法:./get_input_info /dev/input/event0 */
int main(int argc, char const **argv)
{   
    int count = 0;
    unsigned short flag;
    if(argc != 2) 
    {
        printf("Usage: %s n", argv[0]);
        return -1;
    }
    signal(SIGIO, sig_func);   //1.注册信号处理函数(信号类型为IO类型)
    fd = open(argv[1], O_RDWR | O_NONBLOCK);  //2.打开驱动(一定要用非阻塞方式,否则无输入事件进程一直被阻塞)
    if(fd == -1) 
    {
        printf("can not open %sn", argv[1]);
        return -1;
    }
    fcntl(fd ,F_SETOWN, getpid());   //3.告知驱动程序app进程ID
    flag = fcntl(fd, F_GETFL);       //4.获得文件状态标记
    fcntl(fd, F_SETFL, flag | FASYNC); //5.设置文件状态标记(将进程添加到驱动fasync事件等待队列)

    while(1)
    {
        printf("count = %dn", count++);
        sleep(2);
    }
    return 0;

}

实验结果:
[root@imx6ull:/mnt]# ./get_input_info /dev/input/event1
count = 0
count = 1
count = 2               //无输入事件时正常计数
type = 0x3, code = 0x39, value = 0x6
type = 0x3, code = 0x35, value = 0x1a6
type = 0x3, code = 0x36, value = 0x131
type = 0x3, code = 0x30, value = 0x1f
type = 0x3, code = 0x3a, value = 0x1f
type = 0x1, code = 0x14a, value = 0x1
type = 0x0, code = 0x0, value = 0x0
count = 3
type = 0x3, code = 0x35, value = 0x1a7
type = 0x0, code = 0x0, value = 0x0
count = 4
type = 0x3, code = 0x35, value = 0x1a9
type = 0x0, code = 0x0, value = 0x0
count = 5
type = 0x3, code = 0x35, value = 0x1a8
type = 0x0, code = 0x0, value = 0x0
count = 6

文章来源于互联网:嵌入式Linux—输入子系统

THE END
分享
二维码