【Linux】输入系统应用

# 前置知识

(1)输入子系统分为三层,分别是事件处理层、核心层、设备驱动层;
(2)鼠标移动、键盘按键按下等输入事件都需要通过设备驱动层→核心层→事件处理层→用户空间,层层上报,直到应用程序;

 事件处理层

(1)事情处理层主要是负责将输入事件上报到应用程序;对于向内核输入子系统注册的输入设备,在sysfs中创建设备节点,应用程序通过操作设备节点来获取输入事件;
(2)事件处理层将输入事件划分为几大类,比如:通用事件(event)、鼠标事件(mouse)、摇杆事件(js)等等,每个输入类设备在注册时需要指定自己属于哪个类;
(3)通用事件是能和任何输入设备匹配上的,意味着只要注册一个输入类设备就会sysfs就会创建对应的/dev/input/eventn设备节点;

核心层 

(1)核心层是起到承上启下的作用,负责协调输入事件在事件处理层和设备驱动层之间的传递;
(2)核心层负责管理事件处理层和设备驱动层,核心层提供相关的接口函数,事件处理层和设备驱动层都必须先向核心层注册,然后才能工作;
(3)核心层负责设备驱动层和事件处理层的匹配问题,设备驱动根据硬件特性是各种各样的,事件处理层也是分为好几种类型,具体硬件驱动和哪一类或者哪几类事件处理类型匹配,需要核心层去做判断;

设备驱动层 

(1)设备驱动层分为两大部分:硬件特性部分 + 核心层注册接口;
(2)设备驱动层的硬件特性部分是具体操作硬件的,不同的硬件差异很大,且不属于内核,这也是我们移植驱动的重点;
(3)核心层注册接口:输入子系统提供的输入设备向内核注册的接口,属于内核代码部分,我们需要理解和会使用这些接口,接口的使用都是模式化的,降低了编写驱动的难度;

 示例

假设用户程序直接访问/dev/input/event0 设备节点,或者使用 tslib 访问设备节点,数据的流程如下:

  1. APP 发起读操作,若无数据则休眠;
  2. 用户操作设备,硬件上产生中断;
  3. 输入系统驱动层对应的驱动程序处理中断:读取到数据,转换为标准的输入事件,向核心层汇报。所谓输入事件就是一个“ struct input_event”结构体。
  4. 核心层可以决定把输入事件转发给上面哪个 handler 来处理:从 handler 的名字来看,它就是用来处输入操作的。有多种 handler,比如: evdev_handler、 kbd_handler、 joydev_handler 等等。最常用的是 evdev_handler:它只是把 input_event 结构体保存在内核buffer 等, APP 来读取时就原原本本地返回。它支持多个 APP 同时访问输入设备,每个 APP 都可以获得同一份输入事件。当 APP 正在等待数据时, evdev_handler 会把它唤醒,这样 APP 就可以返回数据。

APP 对输入事件的处理:

  1. APP 获 得 数 据 的 方 法 有 2 种 : 直 接 访 问 设 备 节 点 ( 比 如/dev/input/event0,1,2,...),或者通过 tslib、 libinput 这类库来间接访问设备节点。这些库简化了对数据的处理

 # input_dev结构体详解 

struct input_dev {
	const char *name;	//设备名称
	const char *phys;	//设备在系统中的物理路径
	const char *uniq;	//设备唯一识别符
	struct input_id id;	//设备工D,包含总线ID(PCI 、 USB)、厂商工D,与 input handler 匹配的时会用到

    /*
    *EV_SYN	同步事件
    *EV_KEY	按键事件
    *EV_REL	相对坐标事件:比如说鼠标
    *EV_ABS	绝对坐标事件:比如触摸屏
    *EV_MSC	杂项事件
    *EV_SW	开关事件
    *EV_LED	指示灯事件
    *EV_SND	音效事件
    *EV_REP	重复按键事件
    *EV_FF	力反馈事件
    *EV_PWR	电源事件
    *EV_FF_STATUS	力反馈状态事件
    */
	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)];	//设备支持的具体的LED指示灯事件
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];	//户设备支持的具体的音效事件
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];	//设备支持的具体的力反馈事件
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];	//设备支持的具体的开关事件

	unsigned int keycodemax;	//键盘码表的大小
	unsigned int keycodesize;	//键盘码表中的元素个数
	void *keycode;	//设备的键盘码表
	
	//下面两个是可选方法,用于配置和获取键盘码表
	int (*setkeycode)(struct input_dev *dev,
			  unsigned int scancode, unsigned int keycode);
	int (*getkeycode)(struct input_dev *dev,
			  unsigned int scancode, unsigned int *keycode);

	struct ff_device *ff;	//如果设备支持力反馈,则该成员将指向力反馈设备描述结构
	unsigned int repeat_key;	//保存上一个键值,用于实现软件自动重复按键(用户按住某个键不放)
	struct timer_list timer;	//用于软件自动重复按键的定时器

	int sync;		//在上次同步事件(EV_SYNC)发生后没有新事件产生,则被设置为 1 

	int abs[ABS_CNT];	//用于上报的绝对坐标当前值
	int rep[REP_MAX + 1];	//记录自动重复按键参数的当前值

	unsigned long key[BITS_TO_LONGS(KEY_CNT)];		//舍反映设备按键、 按钮的当前状态
	unsigned long led[BITS_TO_LONGS(LED_CNT)];	//反映设备LED指示灯的当前状态时
	unsigned long snd[BITS_TO_LONGS(SND_CNT)];	//反映设备音效的当前状态会
	unsigned long sw[BITS_TO_LONGS(SW_CNT)];	//反映设备开关的当前状态

	int absmax[ABS_CNT];	//绝对坐标的最大值
	int absmin[ABS_CNT];	//绝对坐标的最小值
	int absfuzz[ABS_CNT];	//绝对坐标的噪音值,变化小于该值的一半可忽略该事件
	int absflat[ABS_CNT];	//摇杆中心位置大小
	int absres[ABS_CNT];

	//提供以下4个设备驱动层的操作接口,根据具体的设备需求实现它们
	int (*open)(struct input_dev *dev);
	void (*close)(struct input_dev *dev);
	int (*flush)(struct input_dev *dev, struct file *file);
	//用于处理送到设备驱动层来的事件,很多事件在事件处理层被处理,但有的事件仍需送到设备驱动中.
	//如LED指示灯事件和音效事件,因为这些操作通常需要设备驱动执行(如点亮某个键盘指示灯) 
	int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
	
	//指向独占该设备的输入句柄( input handle ),通常设备驱动上报的事件会被分发到与设备
	//关联的所有事件处理程序( input handler )中处理,但如果通过ioctl 的EVIOCGRAB命令
	//设置了独占句柄,则上报事件只能被所设置的输入句柄对应的事件处理程序处理
	struct input_handle *grab;

	
	spinlock_t event_lock;	//调用 event () 时需要使用该自旋锁来实现互斥
	struct mutex mutex;	//用于串行化的访问 open()、 close()和flush()等设备方法

	//记录输入事件处理程序(input handlers)调用设备open()方法的次数.保证设备open()方法是在
	//第一次调用 input_open_device()中被调用,设备close()方法在最后一次调用 input_close_device()中被调用
	unsigned int users;
	bool going_away;

	struct device dev;  //内嵌device结构

	struct list_head	h_list;	//与该设备相关的输入句柄列表(struct input handle)
	struct list_head	node;	//挂接到input_dev_list链表上
};

# 输入事件

输入事件结构体

        APP 可以通过read函数得到一系列的输入事件,就是一个一个“ struct input_event”:


/*
 * The event structure itself
 * Note that __USE_TIME_BITS64 is defined by libc based on
 * application's request to use 64 bit time_t.
 */

struct input_event {
#if (__BITS_PER_LONG != 32 || !defined(__USE_TIME_BITS64)) && !defined(__KERNEL__)
	struct timeval time;
#define input_event_sec time.tv_sec      /* 秒 */
#define input_event_usec time.tv_usec    /* 微妙 */
#else
	__kernel_ulong_t __sec;
#if defined(__sparc__) && defined(__arch64__)
	unsigned int __usec;
	unsigned int __pad;
#else
	__kernel_ulong_t __usec;
#endif
#define input_event_sec  __sec
#define input_event_usec __usec
#endif
	__u16 type;                        /* 哪类事件 */
	__u16 code;                        /* 哪个事件 */
	__s32 value;                       /* 事件值 */
};
  • type: 表示哪类事件
EV_SYN用于事件间的分割标志。事件可能按时间或空间进行分割,就像在多点触摸协议中的例子
EV_KEY用来描述键盘,按键或者类似键盘设备的状态变化
EV_REL用来描述相对坐标轴上数值的变化,例如:鼠标向左方移动了5个单位
 EV_ABS用来描述相对坐标轴上数值的变化,例如:描述触摸屏上坐标的值
EV_MSC当不能匹配现有的类型时,使用该类型进行描述
EV_SW用来描述具备两种状态的输入开关
EV_LED用于控制设备上的LED灯的开和关
EV_SND用来给设备输出提示声音
EV_REP用于可以自动重复的设备(autorepeating)
EV_FF用来给输入设备发送强制回馈命令。(震动?)
EV_PWR特别用于电源开关的输入
EV_FF_STATUS用于接收设备的强制反馈状态
  • code: 表示该类事件下的哪一个事件

比如对于 EV_KEY(按键)类事件,它表示键盘。键盘上有很多按键,比如数字键 1、 2、 3,字母键 A、 B、 C 里等

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

对于触摸屏,它提供的是绝对位置信息,有 X 方向、 Y 方向,还有压力值

/*
 * Absolute axes
 */

#define ABS_X			0x00
#define ABS_Y			0x01
#define ABS_Z			0x02
#define ABS_RX			0x03
#define ABS_RY			0x04
#define ABS_RZ			0x05
#define ABS_THROTTLE		0x06
#define ABS_RUDDER		0x07
#define ABS_WHEEL		0x08
#define ABS_GAS			0x09
#define ABS_BRAKE		0x0a
#define ABS_HAT0X		0x10
#define ABS_HAT0Y		0x11
#define ABS_HAT1X		0x12
#define ABS_HAT1Y		0x13
#define ABS_HAT2X		0x14
#define ABS_HAT2Y		0x15
#define ABS_HAT3X		0x16
#define ABS_HAT3Y		0x17
#define ABS_PRESSURE		0x18
#define ABS_DISTANCE		0x19
#define ABS_TILT_X		0x1a
#define ABS_TILT_Y		0x1b
#define ABS_TOOL_WIDTH		0x1c

#define ABS_VOLUME		0x20
#define ABS_PROFILE		0x21

#define ABS_MISC		0x28
  • value:表示事件值

对于按键,它的 value 可以是 0(表示按键被按下)、 1(表示按键被松开)、2(表示长按);

对于触摸屏,它的 value 就是坐标值、压力值

  • 事件之间的界线

APP 读取数据时,可以得到一个或多个数据,比如一个触摸屏的一个触点会上报 X、 Y 位置信息,也可能会上报压力值。
APP 怎么知道它已经读到了完整的数据?

驱动程序上报完一系列的数据后,会上报一个“同步事件”,表示数据上报完毕。 APP 读到“同步事件”时,就知道已经读完了当前的数据。

同步事件也是一个 input_event 结构体,它的 type、 code、 value 三项都是 0

 使用命令读取数据

调试输入系统时,直接执行类似下面的命令,然后操作对应的输入设备即可读出数据:

hexdump /dev/input/event1

在开发板上执行上述命令之后,点击按键或是点击触摸屏,就会打印如下信息:

        比如第一行 type 为3,即 EV_ABS ;code 为 0x39 对应的 ABS_MT_TRACKING_ID,表示手指ID。手指 ID 才能唯一代表一个手指,槽的 ID 并不能代表一个手指。因为假如一个手指抬起,另外一个手指按下,这两个手指的事件可能由同一个槽进行上报,但是手指 ID 肯定是不一样的。

        比如 type 为3,即 EV_ABS ;code 为 0x35对应的 ABS_MT_POSITION_X,code 为 0x36对应的 ABS_MT_POSITION_Y

        上图中还发现有 2 个同步事件:它的 type、 code、 value 都为 0。表示电容屏上报了 2 次完整的数据

注意:

当第一个手指移动时,会有如下事件

EV_ABS       ABS_MT_POSITION_X    000002ec            
EV_ABS       ABS_MT_POSITION_Y    00000526    
​
EV_SYN       SYN_REPORT           00000000 

此时没有指定 ABS_MT_SLOT 事件和 ABS_MT_TRACKING_ID 事件,默认使用前面的值,因为此时只有一个手指。

当第二个手指按下时,会有如下事件

EV_ABS       ABS_MT_SLOT          00000001            
EV_ABS       ABS_MT_TRACKING_ID   00000001            
EV_ABS       ABS_MT_POSITION_X    00000470            
EV_ABS       ABS_MT_POSITION_Y    00000475       
​
EV_SYN       SYN_REPORT           00000000 
​

 

很简单,第二个手指的事件,由另外一个槽进行上报。

当两个手指同时移动时,会有如下事件

EV_ABS       ABS_MT_SLOT          00000000            
EV_ABS       ABS_MT_POSITION_Y    000004e0            
EV_ABS       ABS_MT_SLOT          00000001            
EV_ABS       ABS_MT_POSITION_X    0000046f            
EV_ABS       ABS_MT_POSITION_Y    00000414   
​
EV_SYN       SYN_REPORT           00000000 
​

通过指定槽,就可以清晰看到事件由哪个槽进行上报,从而就可以区分出两个手指产生的事件。

当其中一个手指抬起时,会有如下事件

EV_ABS       ABS_MT_SLOT          00000000  
// 注意,ABS_MT_TRACKING_ID 的值为 -1
EV_ABS       ABS_MT_TRACKING_ID   ffffffff            
EV_ABS       ABS_MT_SLOT          00000001            
EV_ABS       ABS_MT_POSITION_Y    000003ee  
​
EV_SYN       SYN_REPORT           00000000  
​

当一个手指抬起时,ABS_MT_TRACKING_ID 事件的值为 -1,也就是十六进制的 ffffffff。通过槽事件,可以知道是第一个手指抬起了。

如果最后一个手指也抬起了,会有如下事件

EV_ABS       ABS_MT_TRACKING_ID   ffffffff     

// 同步事件,不属于触摸事件 
EV_SYN       SYN_REPORT           00000000    

# 查看输入设备节点

使用如下指令查看输入设备节点:

cat /proc/bus/input/devices

  • I:id of the device(设备 ID)

        该参数由结构体 struct input_id 来进行描述,驱动程序中会定义这样的结构体:

/*
 * IOCTLs (0x00 - 0x7f)
 */

struct input_id {
	__u16 bustype;
	__u16 vendor;
	__u16 product;
	__u16 version;
};
  •  N:name of the device

        设备名称

  • P:physical path to the device in the system hierarchy

        系统层次结构中设备的物理路径

  • S:sysfs path

        位于 sys 文件系统的路径

  • U:unique identification code for the device(if device has it)

        设备的唯一标识码

  • H:list of input handles associated with the device

        与设备关联的输入句柄列表

  • B:bitmaps(位图)

        PROP:device properties and quirks(设备属性)

        EV:types of events supported by the device(设备支持的事件类型)

        KEY:keys/buttons this device has(此设备具有的键/按钮)

        MSC:miscellaneous events supported by the device(设备支持的其他事件)

        LED:leds present on the device(设备上的指示灯)

注意:

        值得注意的是 B 位图,比如上图中“ B: EV=b”用来表示该设备支持哪类输入事件。 b 的二进制是 1011, bit0、 1、 3 为 1,表示该设备支持 0、 1、 3 这三类事件,即 EV_SYN、 EV_KEY、 EV_ABS

        再举一个例子,“ B: ABS=2658000 3”这是 2 个 32 位的数 字: 0x2658000、 0x3, 高位在 前低 位在 后, 组成一 个 64 位 的数字 : “ 0x2658000,00000003”,数值为 1 的位有: 0、 1、 47、 48、 50、 53、 54,即: 0、 1、 0x2f、 0x30、 0x32、 0x35、 0x36,对应以下这些宏

        即 这 款 输 入 设 备 支 持 上 述 的 ABS_X 、 ABS_Y 、 ABS_MT_SLOT 、ABS_MT_TOUCH_MAJOR 、 ABS_MT_WIDTH_MAJOR 、 ABS_MT_POSITION_X 、ABS_MT_POSITION_Y 这些绝对位置事件
 

# APP访问硬件

阻塞、非阻塞

#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <poll.h>
#include <signal.h>

int fd = 0; 
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 ",};

static void print_ev_info(void)
{
    int len;
    int err;
    struct input_id id;
    unsigned char byte;
	int bit;
    unsigned int evbit[5];

    err = ioctl(fd, EVIOCGID, &id);
    if(err < 0)
    {
        perror("ioctl:");
        return;
    }

    printf("bustype = 0x%x\n", id.bustype );
    printf("vendor	= 0x%x\n", id.vendor  );
    printf("product = 0x%x\n", id.product );
    printf("version = 0x%x\n", id.version );

    len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit);
	if (len > 0 && len <= sizeof(evbit))
	{
		printf("support ev type: ");
		for (int i = 0; i < len; i++)
		{
			byte = ((unsigned char *)evbit)[i];
			for (bit = 0; bit < 8; bit++)
			{
				if (byte & (1<<bit)) {
					printf("%s ", ev_names[i*8 + bit]);
				}
			}
		}
		printf("\n");
	}
}

static void read_block_nblock(void)
{
    int len = 0;
    struct input_event ev;

    while(true)
    {
        len = read(fd, &ev, sizeof(struct input_event));
        if(len == sizeof(struct input_event))
        {
            printf("**********************************\r\n");
            printf("time.tv_sec = %ld\r\n", ev.time.tv_sec);
            printf("time.tv_usec = %ld\r\n", ev.time.tv_usec);
            printf("type = %d\r\n", ev.type);
            printf("code = %d\r\n", ev.code);
            printf("value = %d\r\n", ev.value);
        }
        else
        {
            printf("read error %d\r\n", len);
        }
    }
}

int main(int argc, char **argv)
{
    if (argc < 2)
	{
		printf("Usage: %s <dev> [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 < 0)
    {
        perror("open:");
        return -1;
    }

    print_ev_info();
    read_block_nblock();
    //read_poll();
    //read_sync();

    close(fd);
}
  • 使用如下指令进行阻塞式询问
 ./input_demo /dev/input/event1

  •  使用如下指令进行非阻塞式询问
./input_demo /dev/input/event1 noblock

POLL/SELECT 方式 

API

  • poll 函数

        poll 是在指定时间内论询一定数量的文件描述符,来测试其中是否有就绪的

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

函数参数: 

其中,struct pollfd 定义为:


struct pollfd {
    /* 文件描述符 */
    int   fd;         /* file descriptor */
    /*
    *监听事件
       POLLIN 有数据可读
       POLLPRI 等同于 POLLIN 
       POLLOUT 可以写数据
       POLLERR 发生了错误
       POLLHUP 挂起
       POLLNVAL 无效的请求,一般是 fd 未 open
       POLLRDNORM 等同于 POLLIN
       POLLRDBAND Priority band data can be read,有优先级较较高的“ band data”可读
Linux 系统中很少使用这个事件
       POLLWRNORM 等同于 POLLOUT
       POLLWRBAND Priority data may be written.
    */
    short events;     /* requested events */
    /*
    *接收到的事件,参数同上
    */
    short revents;    /* returned events */
};

返回值:  

  • 非负值:成功
  • 0:超时
  • -1:错误

程序代码:

#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <poll.h>
#include <signal.h>

int fd = 0; 
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 ",};

static void print_ev_info(void)
{
    int len;
    int err;
    struct input_id id;
    unsigned char byte;
	int bit;
    unsigned int evbit[5];

    err = ioctl(fd, EVIOCGID, &id);
    if(err < 0)
    {
        perror("ioctl:");
        return;
    }

    printf("bustype = 0x%x\n", id.bustype );
    printf("vendor	= 0x%x\n", id.vendor  );
    printf("product = 0x%x\n", id.product );
    printf("version = 0x%x\n", id.version );

    len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit);
	if (len > 0 && len <= sizeof(evbit))
	{
		printf("support ev type: ");
		for (int i = 0; i < len; i++)
		{
			byte = ((unsigned char *)evbit)[i];
			for (bit = 0; bit < 8; bit++)
			{
				if (byte & (1<<bit)) {
					printf("%s ", ev_names[i*8 + bit]);
				}
			}
		}
		printf("\n");
	}
}

static void read_poll(void)
{
    int ret;
    struct input_event ev;
    struct pollfd fds[1];
    nfds_t nfds = 1;

    while (true)
    {
        fds[0].fd = fd;
        fds[0].events = POLLIN;
        fds[0].revents = 0;

        ret = poll(fds, nfds, 5000);
        if(ret > 0)
        {
            if(fds->revents == POLLIN)
            {
                while(read(fd, &ev, sizeof(struct input_event)) == sizeof(struct input_event))
                {
                    printf("**********************************\r\n");
                    printf("time.tv_sec = %ld\r\n", ev.time.tv_sec);
                    printf("time.tv_usec = %ld\r\n", ev.time.tv_usec);
                    printf("type = %d\r\n", ev.type);
                    printf("code = %d\r\n", ev.code);
                    printf("value = %d\r\n", ev.value);
                }
            }
        }
        else if(ret == 0)
        {
            printf("read out %d\r\n", ret);
        }
        else
        {
            perror("read:");
        }
    }
    
}

int main(int argc, char **argv)
{
    if (argc < 2)
	{
		printf("Usage: %s <dev> [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 < 0)
    {
        perror("open:");
        return -1;
    }

    print_ev_info();
    //read_block_nblock();
    read_poll();
    //read_sync();

    close(fd);
}
  •  使用如下指令进行询问
 ./input_demo /dev/input/event1 noblock

异步通知方式 

步骤:

  • 编写信号处理函数:
static void sig_func(int sig)
 {
    int val;
    read(fd, &val, 4);
    printf("get button : 0x%x\n", val); 
}
  • 注册信号处理函数:
signal(SIGIO, sig_func);

 Linux 系统中也有很多信号,在 Linux 内核源文件 include\uapi\asmgeneric\signal.h 中,有很多信号的宏定义:

  • 打开驱动:
fd = open(argv[1], O_RDWR);
  • 把进程 ID 告诉驱动:
fcntl(fd, F_SETOWN, getpid());
  • 使能驱动的 FASYNC 功能:
flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, flags | FASYNC);

API:

  • signal函数

        设置某一信号的对应动作

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

函数参数:

参数描述
signum指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号
handler描述了与信号关联的动作
  • fcntl

        根据文件描述词来操作文件的特性

int fcntl(int fd, int cmd);

int fcntl(int fd, int cmd, long arg);         

int fcntl(int fd, int cmd, struct flock *lock);

fcntl函数有5种功能:

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

 cmd 选项:

  1.  F_GETFD     取得与文件描述符fd联合close-on-exec标志
  2.  F_SETFD     设置close-on-exec旗标。该旗标以参数arg的FD_CLOEXEC位决定
  3.  F_GETFL     取得fd的文件状态标志,如同下面的描述一样(arg被忽略)  
  4.  F_SETFL     设置给arg描述符状态标志,可以更改的几个标志是:O_APPEND, O_NONBLOCK,O_SYNC和O_ASYNC(O_NONBLOCK :非阻塞I/O;如果read(2)调用没有可读取的数据,或者如果write(2)操作将阻塞,read或write调用返回-1和EAGAIN错误 ;  O_APPEND: 强制每次写(write)操作都添加在文件大的末尾,相当于open(2)的O_APPEND标志 ;  O_DIRECT :  最小化或去掉reading和writing的缓存影响.系统将企图避免缓存你的读或写的数据.  如果不能够避免缓存,那么它将最小化已经被缓存了的数 据造成的影响.如果这个标志用的不够好,将大大的降低性能 ; O_ASYNC: 当I/O可用的时候,允许SIGIO信号发送到进程组,例如:当有数据可以读的时候)
  5.  F_GETOWN 取得当前正在接收SIGIO或者SIGURG信号的进程id或进程组id,进程组id返回成负值(arg被忽略) 
  6.  F_SETOWN 设置将接收SIGIO和SIGURG信号的进程id或进程组id,进程组id通过提供负值的arg来说明,否则,arg将被认为是进程id

 程序代码:

#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <poll.h>
#include <signal.h>

int fd = 0; 
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 ",};

static void print_ev_info(void)
{
    int len;
    int err;
    struct input_id id;
    unsigned char byte;
	int bit;
    unsigned int evbit[5];

    err = ioctl(fd, EVIOCGID, &id);
    if(err < 0)
    {
        perror("ioctl:");
        return;
    }

    printf("bustype = 0x%x\n", id.bustype );
    printf("vendor	= 0x%x\n", id.vendor  );
    printf("product = 0x%x\n", id.product );
    printf("version = 0x%x\n", id.version );

    len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit);
	if (len > 0 && len <= sizeof(evbit))
	{
		printf("support ev type: ");
		for (int i = 0; i < len; i++)
		{
			byte = ((unsigned char *)evbit)[i];
			for (bit = 0; bit < 8; bit++)
			{
				if (byte & (1<<bit)) {
					printf("%s ", ev_names[i*8 + bit]);
				}
			}
		}
		printf("\n");
	}
}

static void my_sig_handler(int sig)
{
    struct input_event ev;

    if(sig == SIGIO)
    {
        while(read(fd, &ev, sizeof(struct input_event)) == sizeof(struct input_event))
        {
            printf("**********************************\r\n");
            printf("time.tv_sec = %ld\r\n", ev.time.tv_sec);
            printf("time.tv_usec = %ld\r\n", ev.time.tv_usec);
            printf("type = %d\r\n", ev.type);
            printf("code = %d\r\n", ev.code);
            printf("value = %d\r\n", ev.value);
        }
    }
}

static void read_sync(void)
{
    int flags;
    int count = 0;

    /* 注册信号处理函数 */
	signal(SIGIO, my_sig_handler);

    /* 把APP的进程号告诉驱动程序 */
	fcntl(fd, F_SETOWN, getpid());

    /* 使能"异步通知" */
	flags = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, flags | FASYNC);

    while (true)
    {
        printf("main loop count = %d\n", count++);
		sleep(2);
    }
    
}

int main(int argc, char **argv)
{
    if (argc < 2)
	{
		printf("Usage: %s <dev> [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 < 0)
    {
        perror("open:");
        return -1;
    }

    print_ev_info();
    //read_block_nblock();
    //read_poll();
    read_sync();

    close(fd);
}
  •  使用如下指令进行询问
./input_demo /dev/input/event1

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值