1、什么是输入子系统?
输入子系统是Linux专门做的一套框架来处理输入事件的,像鼠标,键盘,触摸屏这些都是输入设备,但是这邪恶输入设备的类型又都不是一样的,所以为了统一这些输入设备驱动标准应运而生的。
统一了以后,在节点/dev/input下面则是我们输入设备的节点,如下图所示:
这些节点对应的则是我们当前系统的输入设备,我们要怎么查看当前系统都有哪些输入设备呢?我们可以使用命令来查看:
cat /proc/bus/input/devices
如下图所示:
2、如何确定哪个设备对应那个节点呢?
可以使用hexdump确定,hexdump命令是Linux下查看二进制文本的工具。
举例:如果想确定键盘对应是哪个节点,可以使用命令:
hexdump /dev/input/event0 或者
hexdump /dev/input/event1 或者
hexdump /dev/input/event2 或者
...
输入完一条命令之后,按键盘上的按键,如果有数据打印出来,则证明当前查看的这个节点就是键盘这个设备对应 的节点。
比如,我现在在Ubuntu上输入命令:
hexdump /dev/input/event1
然后按键盘的按键,这时候有打印信息的出现,则证明/dev/input/event1为键盘对应的节点,如下图所示:
3、hedump出来的打印信息都是什么意思呢?
上报的数据要按照具体的格式上百给输入子系统的核心层,我们的应用就可以通过设备节点来获得按照具体格式上报来的数据了。
封装数据是输入子系统的核心层来帮我们完成的,我们只需要按照指定的类型和这个类型对应的数据来上报给输入子系统的核心层即可。
那怎么指定类型呢?这个就需要了解struct input_event这个结构体,这个结构体在include/uapi/linux/input.h,如下所示:
struct input_event {
struct timeval time;//上报事件的事件
__u16 type;//类型
__u16 code;//编码
__s32 value;//值
};
这里值得注意的是,当type不同的时候,code和value所代表的意义也是不一样的。include/uapi/linux/input-event-codes.h:这个头文件里面找到type的定义,每一个定义都是一个类型,如下所示:
#define EV_SYN 0x00 //同步事件
#define EV_KEY 0x01 //按键事件
#define EV_REL 0x02 //相对坐标事件
#define EV_ABS 0x03 //相对坐标事件
#define EV_MSC 0x04 //杂项(其他)事件
#define EV_SW 0x05 //开关事件
按下按键1:
4、实验:在应用层读取键盘按下的键值
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/input.h>
int main(int argc, char *argv[])
{
int fd;
struct input_event test_event;
fd = open("dev/input/event1",O_RDWE\R);
if (fd < 0) {
perror("open error");
return fd;
}
while (1) {
read(fd, &test_event, sizeof(test_event));
if (test_event.type == EV_KEY) {
printf("type is %#x\n", test_event.type);
printf("value is %#x\n", test_event.value);
}
}
return 0;
}
在ubuntu上编译运行:
按下回车以及长按回车:
5、使用输入子系统设计按键驱动
5.1 申请和释放input_dev结构体的函数
将开发板上的按键值设置为input.h文件中宏定义的任意一个,比如这次实验将开发板上的KEY按键设置为KEY_0。
在编写input设备驱动的时候我们需要先申请一个ibput_dev结构体变量,使用input_allocate_device函数来申请一个input_dev。此函数原型如下所示:
struct input_dev *input_allocate_device(void);
返回值:申请到的input_dev。
如果要注销niput设备的话需要使用input_free_device函数来释放掉前面申请到的input_dev,input_free_device函数原型如下:
void input_free_device(struct input_dev *dev);
dev:需要释放的input_dev。
5.2注册及注销input_dev的函数
申请好一个input_dev以后就需要初始化这个input_dev,需要初始化的内容主要为事件类型(evbit)和事件值(keybit)这两种。初始化完成之后就需要向Linux内核注册input_dev,需要用到input_register_device函数,原型如下:
int input_register_device(struct input_dev *dev);
dev:要注册的input_dev。
返回值:0,input_dev注册成功;负值,input_dev注册失败。
注销input驱动的时候也需要使用input_unregister_device,函数原型如下:
void input_unregister_device(struct input_dev *dev);
dev:要注销的input_dev。
返回值:无
5.3 事件上报函数
最终我们需要把事件上报上去,上报事件我们使用的函数要针对具体的时间来上报。比如,按键我们使用input_report_key函数。同样的含有一些其他的事件上报函数,函数如下所示:
void input_report_key(struct input_dev *dev, unsigned int code, int value);
void input_report_rel(struct input_dev *dev, unsigned int code, int value);
void input_report_abs(struct input_dev *dev, unsigned int code, int value);
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value);
void input_report_switch(struct input_dev *dev, unsigned int code, int value);
void input_sync(struct input_dev *dev);
void input_mt_sync(struct input_dev *dev);
当上报事件以后还需要使用input_sync函数来告诉Linux内核input子系统上报结束,input_sync函数本质上是上报一个同步事件,函数原型如下:
void input_sync(struct input_dev *dev);
dev:需要上报同步事件的input_dev。
5.4 实验驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
#include <linux/input.h>
struct device_node *test_device_node;
struct property *test_node_property;
int gpio_num;
int irq = 0;
struct input_dev *test_dev;
static void timer_funtion(ubsigned long data);
DEFINE_TIMER(test_timer, timer_funtion, 0, 0);
static void timer_funtion(ubsigned long data)
{
int value;
value = !gpio_get_value(gpio_num);
input_repo(rt_key(test_dev, KEY_1, value);
input_sync(test_dev);
}
static const of_device_id of_match_table_test[] = {//匹配表
{.compatible = "keys"},
};
static const platform_device_id beep_id_table ={
.name = "beep_test",
};
irqreturn_t test_key(int irq, void *args)
{
printk("test_key\n");
test_timer.expires = jiffies + msec_to_jiffies(20);
add_timer(&test_timer);
return IRQ_HANDLED;
}
/*设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入该函数,pdev是匹配成功后传入的设备树节点*/
int beep_probe(struct platform_device *pdev)
{
int ret = 0;
printk("beep_probe\n");
//查找要查找的节点
test_device_node = of_find_node_by_path("/test_key");
if (test_device_node == NULL) {
printk("test_device_node find error\n");
return -1;
}
printk("test_device_node name is %s\n",test_device_node->name);//test_key
gpio_num = of_get_named_gpio(test_device_node, "gpios", 0);
if (gpio_num < 0) {
printk("of_get_named_gpio error\n");
return -1;
}
printk("gpio_num name is %d\n",gpio_num);
gpio_direction_input(gpio_num);
//获取中断号
//irq = gpio_to_irq(gpio_num);
irq = irq_of_parse_and_map(test_device_node, 0);
printk("irq is %d\n",irq);
//申请中断
ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "test_key", NULL);//
if (ret < 0) {
printk("request_irq error\n");
return -1;
}
test_dev = input_allocate_device();
test_dev->name = "test_key";
__set_bit(EV_KEY, test_dev->ebit);
__set_bit(KEY_1, test_dev->keybit);
ret = input_register_device(test_dev);
if(ret < 0) {
printk("input_register_device is error");
goto error_input_register;
}
return 0;
error_input_register:
input_unregister_device(test_dev);
}
int beep_remove(struct platform_device *pdev)
{
pritnk("beep_remove \n");
return 0;
}
strcut platform_driver beep_device = {
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.owner = THIS_MODULE,
.name = "123",
.of_match_table = of_match_table_test,//匹配表
},
.id_table = &beep_id_table,
};
static int beep_driver_init(void)
{
int ret = -1;
ret = platform_driver_register(&beep_device);
if(ret < 0) {
printk("platform_driver_register error \n");
}
printk("platform_driver_register ok\n");
return 0;
}
static void beep_driver_exit(void)
{
free_irq(irq, NULL);
input_unregister_device(test_dev);
platform_driver_unregister(&beep_device);
printk("beep_driver_exit \n");
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");
编译加载驱动:
查看当前系统输入设备有哪些:
ls可以看到出现新的event3:
再敲这个命令,每按一次按键会打印相应信息:
5.5 应用层程序
#incldue <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/input.h>
int main(int argc, char *argv[])
{
int fd;
struct input_event test_event;
fd = open("/dev/input/event3", O_RDWR);
if (fd < 0) {
perror("open error");
return fd;
}
while (1) {
read(fd, &test_event, sizeof(test_event));
if (test_event.type == EV_KEY) {
printf("type is %#x\n",test_event.type);
printf("code is %#x\n",test_event.code);
printf("value is %#x\n",test_event.value);
}
}
return 0;
}
编译app且拷贝到开发板上:
运行app且按下开发板的按键: