鼠标、键盘、按键、触摸屏等提供输入支持的设备都属于输入设备,在Linux也提供了一套驱动框架“input 子系统”与之对应,用于抽象输入设备,并提供管理输入设备驱动和输入事件处理程序的功能
input 子系统用于管理各种输入设备的驱动程序和各种输入事件的事件处理程序,input 子系统分为3层:
input_dev 对象表示一个 input 设备,它的核心成员如下:
//输入设备的名字,通过命令 cat /proc/bus/input/devices 可以查看
const char *name;
//在系统层次结构中设备的物理路径,通过命令 cat /proc/bus/input/devices 可以查看
const char *phys;
//设备的唯一识别码,通过命令 cat /proc/bus/input/devices 可以查看
const char *uniq;
//设备ID,包含总线类型、作者、产品和版本相关消息,它用于输入设备和事件处理程序间的匹配,此外它通过命令 cat /proc/bus/input/devices 可以查看
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)];
//led事件
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)];
//触摸屏相关,是一个动态分配的数组,用于存储触摸点的状态
struct input_mt *mt;
//操作函数,一般由事件处理程序调用,用于控制输入设备,如 evdev 事件处理程序则是通过字符设备接口开放到应用层,供应用层进行调用
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
//使用 input_handle 中的 h_node 成员构成的链表, Linux 通过 input_handle 对象关联输入设备和输入事件处理程序
struct list_head h_list;
事件码、事件类型、设备属性定义参考 input_event_codes.h 文件,可以通过函数 void __set_bit (int nr, volatile void *addr) 直接设置位图中的某一位,也可以通过函数 void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code) 函数完成事件码位图和事件类型位图的设置。
当设备支持重复事件后内核会为其开启一个定时器生成重复事件,无需设备驱动层重复上报。
注册输入设备:
input_handler 对象表示一个输入事件处理程序,其核心成员如下:
//事件处理函数,用于处理输入设备上报的事件
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
//事件处理函数,用于处理输入设备上报的事件,可处理多个事件,未定义时系统默认使用event实现
void (*events)(struct input_handle *handle, const struct input_value *vals, unsigned int count);
//事件过滤函数,用于处理输入设备上报的事件
bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
//自定义匹配函数,当系统匹配函数执行成功后才会调用此函数
bool (*match)(struct input_handler *handler, struct input_dev *dev);
//用于建立事件处理程序和输入设备的联系,通过调用 input_register_handle 实现
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
//用于断开事件处理程序和输入设备的联系,通过调用 input_unregister_handle 实现
void (*disconnect)(struct input_handle *handle);
//事件处理程序和输入设备建立联系后执行
void (*start)(struct input_handle *handle);
//名字
const char *name;
//设备匹配表,用于与输入设备匹配
const struct input_device_id *id_table;
//使用 input_handle 中的 d_node 成员构成的链表, Linux 通过 input_handle 对象关联输入设备和输入事件处理程序
struct list_head h_list;
注册输入事件处理程序
input_handle 用于关联输入设备和事件处理程序,其核心成员如下:
//关联的输入设备
struct input_dev *dev;
//关联的事件处理程序
struct input_handler *handler;
//用于构建输入设备驱动程序中的 h_list 链表
struct list_head d_node;
//用于构建输入事件处理程序中的 h_list 链表
struct list_head h_node;
建立输入设备驱动程序和输入事件处理程序的联系:
通过函数 input_event 可以上报输入设备产生的输入事件:
/**
* dev 需要上报的 input_dev。
* type 上报的事件类型,比如 EV_KEY。
* code 事件码,如 KEY_0、 KEY_1 等等。
* value 事件值,如按键事件 1 表示按键按下, 0 表示按键松开。
*/
void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value)
另外针对常见类型的事件还有专门的事件上报函数,这些函数都是通过 input_event 函数进行了二次封装实现,如 input_report_key 可用于上报按键事件
输入设备事件上报完成后需要调用 input_sync 告诉内核此次上报结束:
/**
* 需要上报同步事件的 input_dev。
*/
void input_sync(struct input_dev *dev)
输入事件处理程序可以通过 input_inject_event 下发事件给输入设备,它最终会调用到输入设备驱动的 event 函数,如 evdev 驱动在 connect 中注册了一个字符设备,并在字符设备的 write中调用了此函数,使得应用层可以向输入设备驱动程序发送事件。
/**
* handle 需要下发事件的 input_handle (在 input_handle 关联了事件处理程序和输入设备)
* type 下发的事件类型
* code 事件码
* value 事件值
*/
void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
evdev 是一个通用的输入事件处理程序,它能与所有输入设备进行匹配,在匹配成功后会创建一个字符设备,自然应用层也可以通过它创建的字符设备访问到对应的输入设备,其步骤如下:
struct timeval {
__kernel_time_t tv_sec;
__kernel_suseconds_ tv_usec;
};
struct input_event {
//事件上报时间
struct timeval time;
//表示哪类事件,比如 EV_KEY 表示按键类、EV_REL 表示相对位移,EV_ABS 表示绝对位置
__u16 type;
//表示该类事件下的事件码,比如对于 EV_KEY 类事件它表示按键值,对于触摸屏它表示是 X 还是Y ,或是压力值
__u16 code;
//表示事件值,对于按键它表示按键状态,对于触摸屏,它表示坐标值 x 值或 y 值或压力值
__s32 value;
};
在读取据时,可以得到一个或多个数据(比如一个触摸屏的一个触点会上报 X 、Y 位置信息,还可能会上报压力值), 应用层可通过同步事件确定此次上报是否结束,对于同步事件其 type 、 code 、 value 三项都是 0
通过命令 cat /proc/bus/input/devices 可以查看输入设备的详细信息,其内容格式如下:
//设备 ID
I: Bus=0019 Vendor=0000 Product=0001 Version=0000
//设备名称
N: Name="Power Button"
//系统层次结构中设备的物理路径
P: Phys=LNXPWRBN/button/input0
//位于 sys 文件系统的路径
S: Sysfs=/devices/LNXSYSTM:00/LNXPWRBN:00/input/input0
//设备的唯一标识码
U: Uniq=
//与输入设备关联的输入事件处理程序
H: Handlers=kbd event0
//设备属性
B: PROP=0
//设备支持的事件类型
B: EV=3
//可上报的按键位图
B: KEY=10000000000000 0
I: Bus=0011 Vendor=0001 Product=0001 Version=ab41
N: Name="AT Translated Set 2 keyboard"
P: Phys=isa0060/serio0/input0
S: Sysfs=/devices/platform/i8042/serio0/input/input1
U: Uniq=
H: Handlers=sysrq kbd event1 leds
B: PROP=0
B: EV=120013
B: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe
//设备支持的其他事件
B: MSC=10
//设备上的指示灯
B: LED=7
......
调试输入设备时,可使用 hexdump 读取输入设备上报的数据,其数据格式如下:
|序号 | |秒 | |微秒 | |type| |code| |value |
0000340 0000 0000 0000 0000 508b 62dd 0000 0000
0000350 a54d 0009 0000 0000 0003 0000 9d3f 0000
0000360 508b 62dd 0000 0000 a54d 0009 0000 0000
硬件原理图如下所示,在引脚PG3上接了一个按键KEY0,按下按键时PG3为低电平,松开按键时PG3为高电平
设备树如下所示:
intr_key {
compatible = "intr_key";
status = "okay";
key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpiog>;
interrupts = <3 IRQ_TYPE_EDGE_BOTH>;
};
驱动代码主要包括以下部分:
//分配输入设备
key->input_dev = devm_input_allocate_device(&pkey_dev->dev);
if(!key->input_dev)
{
printk("alloc input device failed");
return -ENOMEM;
}
//设置输入设备名称
key->input_dev->name = "virtual_input_Device";
//设置事件类型,按键事件、重复事件
__set_bit(EV_KEY, key->input_dev->evbit);
__set_bit(EV_REP, key->input_dev->evbit);
//设置事件码
__set_bit(KEY_0, key->input_dev->keybit);
//注册输入设备
result = input_register_device(key->input_dev);
if(result)
{
printk("register input device failed");
return result;
}
//初始化消抖定時器
timer_setup(&key->timer, timer_func, 0);
//获取中断号
key->irq = of_irq_get(pkey_dev->dev.of_node, 0);
if(key->irq <= 0)
{
input_unregister_device(key->input_dev);
printk("irq get failed");
return key->irq;
}
//获取中断触发方式
irq_flags = irq_get_trigger_type(key->irq);
if(irq_flags == IRQF_TRIGGER_NONE)
irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
irq_flags |= IRQF_SHARED;
//注册中断
result = devm_request_irq(&pkey_dev->dev, key->irq, key_handler, irq_flags, "key", (void*)key);
if(result != 0)
{
input_unregister_device(key->input_dev);
printk("request irq failed\r\n");
return result;
}
static irqreturn_t key_handler(int irq, void *dev)
{
struct key_handle *key = (struct key_handle *)dev;
//设置定时器到期时间
mod_timer(&key->timer, jiffies + msecs_to_jiffies(10));
//返回IRQ_HANDLED,表示中断被成功处理
return IRQ_HANDLED;
}
static void timer_func(struct timer_list *tm)
{
struct key_handle *key = container_of(tm, struct key_handle, timer);
//上报GPIO状态
input_report_key(key->input_dev, KEY_0, gpiod_get_value(key->gpio));
//上报完成
input_sync(key->input_dev);
}
完整的按键输入驱动程序如下所示:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct key_handle {
//GPIO描述符
struct gpio_desc *gpio;
//GPIO中断号
unsigned int irq;
//软件定时器,用于消抖
struct timer_list timer;
//输入设备
struct input_dev *input_dev;
};
static irqreturn_t key_handler(int irq, void *dev)
{
struct key_handle *key = (struct key_handle *)dev;
//设置定时器到期时间
mod_timer(&key->timer, jiffies + msecs_to_jiffies(10));
//返回IRQ_HANDLED,表示中断被成功处理
return IRQ_HANDLED;
}
static void timer_func(struct timer_list *tm)
{
struct key_handle *key = container_of(tm, struct key_handle, timer);
//上报GPIO状态
input_report_key(key->input_dev, KEY_0, gpiod_get_value(key->gpio));
//上报完成
input_sync(key->input_dev);
}
static int key_probe(struct platform_device *pkey_dev)
{
int result;
uint32_t irq_flags;
struct key_handle *key;
printk("%s\r\n", __FUNCTION__);
//分配设备句柄,devm表示模块卸载时自动释放
key = devm_kzalloc(&pkey_dev->dev, sizeof(struct key_handle), GFP_KERNEL);
if(!key)
{
printk("alloc memory failed\r\n");
return -ENOMEM;
}
//复位key设备句柄
memset(key, 0, sizeof(struct key_handle));
//分配输入设备
key->input_dev = devm_input_allocate_device(&pkey_dev->dev);
if(!key->input_dev)
{
printk("alloc input device failed");
return -ENOMEM;
}
//设置输入设备名称
key->input_dev->name = "virtual_input_Device";
//设置事件类型,按键事件、重复事件
__set_bit(EV_KEY, key->input_dev->evbit);
__set_bit(EV_REP, key->input_dev->evbit);
//设置事件码
__set_bit(KEY_0, key->input_dev->keybit);
//注册输入设备
result = input_register_device(key->input_dev);
if(result)
{
printk("register input device failed");
return result;
}
//初始化消抖定時器
timer_setup(&key->timer, timer_func, 0);
//获取GPIO,并设置为输入(devm表示模块卸载时自动释放)
key->gpio = devm_gpiod_get_index(&pkey_dev->dev, "key", 0, GPIOD_IN);
if(IS_ERR(key->gpio))
{
input_unregister_device(key->input_dev);
printk("get gpio failed\r\n");
return PTR_ERR(key->gpio);
}
//获取中断号
key->irq = of_irq_get(pkey_dev->dev.of_node, 0);
if(key->irq <= 0)
{
input_unregister_device(key->input_dev);
printk("irq get failed");
return key->irq;
}
//获取中断触发方式
irq_flags = irq_get_trigger_type(key->irq);
if(irq_flags == IRQF_TRIGGER_NONE)
irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
irq_flags |= IRQF_SHARED;
//注册中断
result = devm_request_irq(&pkey_dev->dev, key->irq, key_handler, irq_flags, "key", (void*)key);
if(result != 0)
{
input_unregister_device(key->input_dev);
printk("request irq failed\r\n");
return result;
}
//设置平台设备的驱动私有数据
pkey_dev->dev.driver_data = (void*)key;
return 0;
}
static int key_remove(struct platform_device *pkey_dev)
{
struct key_handle *key;
printk("%s\r\n", __FUNCTION__);
//提取平台设备的驱动私有数据
key = (struct key_handle*)pkey_dev->dev.driver_data;
//注销输入设备
input_unregister_device(key->input_dev);
return 0;
}
/* 匹配列表,用于设备树和驱动进行匹配 */
static struct of_device_id key_of_match[] = {
{.compatible = "intr_key"},
{},
};
static struct platform_driver key_drv = {
.driver = {
.name = "intr_key",
.of_match_table = key_of_match,
},
.probe = key_probe,
.remove = key_remove,
};
static int __init mykey_init(void)
{
return platform_driver_register(&key_drv);
}
static void __exit mykey_exit(void)
{
platform_driver_unregister(&key_drv);
}
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("key test");
MODULE_ALIAS("key_input");
按键驱动加载成功后会与event事件处理程序匹配,event事件处理程序会在/dev/input/目录中创建一个event*的设备文件,通过此设备文件可以读取到按键驱动上报的数据,其测试代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd, ret;
struct input_event ev;
if(argc < 2)
{
printf("Usage:\r\n\t./keyinputApp /dev/input/eventX @ Open Key\r\n");
return -1;
}
//打开输入设备
fd = open(argv[1], O_RDWR);
if(0 > fd)
{
printf("Error: file %s open failed!\r\n", argv[1]);
return -1;
}
while(1)
{
//读取输入设备
ret = read(fd, &ev, sizeof(struct input_event));
if (ret)
{
//处理输入事件
switch (ev.type)
{
/* 按键事件 */
case EV_KEY:
if (KEY_0 == ev.code)
{
/* 判断是不是 KEY_0 按键 */
if (ev.value)
{
/* 按键按下 */
printf("Key0 Press\n");
}
else
{
/* 按键松开 */
printf("Key0 Release\n");
}
}
break;
/* 重复事件 */
case EV_REL:
if (KEY_0 == ev.code)
{
/* 按键一直按下 */
printf("Key0 repetition\n");
}
break;
/* 其他类型的事件,自行处理 */
case EV_ABS:
break;
case EV_MSC:
break;
case EV_SW:
break;
};
}
else
{
printf("Error: file %s read failed!\r\n", argv[1]);
break;
}
}
/* 关闭设备 */
close(fd);
return 0;
}