
在前面的文章中,我们已经了解了Linux输入子系统的基本框架,以及/dev/input/eventX设备节点的作用。本篇我们进入实战阶段 ------编写一个应用层程序,读取输入设备的设备基本信息(厂商ID、产品ID等) 、设备支持的事件类型(EV_KEY、EV_REL、EV_ABS 等)。通过本篇,你将彻底理解:
ioctl在输入子系统中的作用EVIOCGID的原理EVIOCGBIT的位图机制- 输入设备能力是如何用bit位表示的
1 Linux 输入子系统简介
Linux输入系统的设备节点通常位于:/dev/input/eventX中,每个eventX代表一个输入设备,例如:
- 键盘
- 鼠标
- 触摸屏
- 电源按键
- 陀螺仪
输入设备统一使用struct input_event结构上报事件,但在真正读取事件之前,我们通常需要知道:
这个设备是什么?支持什么类型的事件?
这就需要用到ioctl。
2 获取设备基本信息------EVIOCGID
Linux 内核中定义了:
cpp
struct input_id {
__u16 bustype; // 总线类型
__u16 vendor; // 厂商ID
__u16 product; // 产品ID
__u16 version; // 版本号
};
使用:
cpp
ioctl(fd, EVIOCGID, &id);
即可获取输入设备的基本身份信息。其中,fd是通过open()打开的设备文件描述符(如/dev/input/event0),EVIOCGID是输入子系统定义的控制命令,用于从驱动中读取设备的ID信息,&id是一个 struct input_id结构体指针,用于接收驱动返回的数据。
调用成功后,内核会将设备的总线类型(bustype)、厂商 ID(vendor)、产品 ID(product)以及版本号(version)填充到该结构体中,从而帮助应用层识别设备的硬件来源及类型。这种方式体现了 ioctl 在应用层与驱动之间进行"控制信息交互"的典型用法。
示例输出:
bustype = 0x3
vendor = 0x046d
product = 0xc077
version = 0x111
这些值可以帮助我们:
- 判断是 USB 设备还是 I2C 设备
- 识别具体厂商和产品型号
3 获取设备支持的事件类型------EVIOCGBIT
3.1 什么是事件类型?
Linux输入系统定义了多种事件类型,例如:
| 事件类型 | 说明 |
|---|---|
| EV_SYN | 同步事件 |
| EV_KEY | 按键事件 |
| EV_REL | 相对坐标(鼠标) |
| EV_ABS | 绝对坐标(触摸屏) |
| EV_LED | LED |
| EV_SND | 声音 |
| EV_FF | 力反馈 |
3.2 事件类型如何存储?
Linux使用**位图(bitmask)**表示设备支持的能力。例如:
bit 0 → EV_SYN
bit 1 → EV_KEY
bit 2 → EV_REL
bit 3 → EV_ABS
...
如果某一位为 1,表示支持该类型。
3.3 获取事件类型位图?
cpp
ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
解释:
- 第一个参数0表示查询"事件类型"
- 第二个参数表示位图缓冲区大小
- 返回值表示有效字节数
3.4 如何解析位图?
我们按字节遍历:
cs
for(i=0; i<len; i++)
{
byte = ((unsigned char *)evbit)[i];
for(bit=0 ; bit<8; bit++)
{
if(byte & (1<<bit))
{
printf("%s\n", ev_names[i*8+bit]);
}
}
}
逻辑解析:
- 每个字节8个bit
- 每个bit对应一个事件类型
- i*8+bit就是实际的事件编号
4 完整运行示例
编译:
arm-linux-gcc 01_get_input_info.c -o get_input_info
运行:
./get_input_info /dev/input/event0
输出示例:
bustype = 0x3
vendor = 0x046d
product = 0xc077
version = 0x111
support ev type:
EV_SYN
EV_KEY
EV_REL
说明该设备是一个USB鼠标。
5 程序原理总结
本程序完成了三件事:
- 打开输入设备
- 使用EVIOCGID获取设备身份信息
- 使用EVIOCGBIT获取支持的事件类型位图
- 解析bit位并打印可读名称
6 完整代码
下面给出完整版详细注释代码:
cs
#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>
/*
* 使用方法:
* ./01_get_input_info /dev/input/event0
*
* 功能:
* 1. 获取输入设备基本信息
* 2. 获取设备支持的事件类型
*/
int main(int argc, char **argv)
{
int fd; // 设备文件描述符
int err; // ioctl返回值
int len; // 返回的有效字节数
int i; // 字节循环变量
unsigned char byte; // 当前字节
int bit; // 当前bit
struct input_id id; // 设备ID结构体
/* 用于保存事件类型位图
* 每个bit表示一种事件类型是否支持
*/
unsigned int evbit[2];
/* 事件类型名称数组
* 下标对应事件编号
*/
char *ev_names[] = {
"EV_SYN", // 0
"EV_KEY", // 1
"EV_REL", // 2
"EV_ABS", // 3
"EV_MSC", // 4
"EV_SW", // 5
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"EV_LED", // 17
"EV_SND", // 18
"NULL",
"EV_REP", // 20
"EV_FF", // 21
};
/* 检查参数 */
if(argc != 2)
{
printf("Usage: %s <dev>\n", argv[0]);
return -1;
}
/* 打开输入设备 */
fd = open(argv[1], O_RDWR);
if(fd < 0)
{
printf("open %s error\n", argv[1]);
return -1;
}
/* 获取设备ID信息 */
err = ioctl(fd, EVIOCGID, &id);
if(err == 0)
{
printf("Device ID info:\n");
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);
}
/*
* 获取事件类型位图
* 参数说明:
* 0 表示获取事件类型
* sizeof(evbit) 表示缓冲区大小
*/
len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
if(len > 0 && len <= sizeof(evbit))
{
printf("support ev type:\n");
/* 按字节解析位图 */
for(i=0; i<len; i++)
{
/* 取出当前字节 */
byte = ((unsigned char *)evbit)[i];
/* 遍历该字节的8个bit */
for(bit=0 ; bit<8; bit++)
{
/* 判断当前bit是否为1 */
if(byte & (1<<bit))
{
int type = i*8 + bit; // 计算事件编号
/* 防止数组越界 */
if(type < sizeof(ev_names)/sizeof(ev_names[0])
&& ev_names[type] != NULL)
{
printf(" %s\n", ev_names[type]);
}
}
}
}
}
close(fd);
return 0;
}