Linux input 子系统
介绍
按键,鼠标,键盘,触摸屏等都属于(input)设备,linux内核为此专门做了一个叫做input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上用了input框架,用户只需要负责上报输入事件,比如按键值,坐标等信息,input核心层负责处理这些事件。
框架
硬件输入设备 | 内核空间 | 驱动层/核心层/事件层 | 用户空间 |
---|---|---|---|
按键 | GPIO | input core | 设备访问节点 |
USB键盘/鼠标 | USB HID | input core | 设备访问节点 |
触摸屏等等 | touch | input core | 设备访问节点 |
可以看出input子系统用到了驱动分层模型,我们编写驱动的时候只需要关注中间的驱动层,核心层和事件层。
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理
事件层:主要和用户空间进行交互
input驱动编写流程
input核心层会向linux内核注册一个字符设备
/* input核心层重点代码,可以在drivers/input/input.c这个文件阅读核心层代码 */
struct class input_class = {
.name = "input",
.devnode = "input_devnode",
};
static int __init input_init(void){
int err;
/* 注册了一个input类 系统启动后可以在/sys/class 目录下找到一个input子目录 */
err = class_register(&input_class);
if(err){
pr_err("unable to register input_dev class\n");
return err;
}
err = input_proc_init();
if(err){
goto fail1;
}
/* 注册一个字符设备,主设备号为INPUT_MAJOR(13)
* 也就是说所有input子系统处理输入设备的时候就不用去注册字符设备,只需要向系统注册一个input_device即可
*/
err = register_chrdev_region(MKDEV(INPUT_MAJOR,0),INPUT_MAX_CHAR_DEVICES,"input");
if(err){
pr_err("unable to register char major %d",INPUT_MAJOR);
goto fail2;
}
return 0;
fail2:input_proc_exit();
fail1:class_unregister(&input_class);
return err;
}
注册 input_dev
在使用input子系统的时候只需要注册一个input设备即可
/* input_dev结构体表示input设备,定义在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)];/* 事件类型的位图 */
/* 如果要用到按键,就需要注册EV_KEY事件,如果需要使用连按功能还需要注册EN_REP事件 */
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)]; /* sount有关的位图 */
unsigned long ffbit[BITS_TO_LONGS9(FF_CNT)]; /* 压力反馈的位图 */
unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /* 开关状态的位图 */
bool dervers_managed;
};
/* 这里简单说明一个demo 我们将某个开发板上的按键作为USB键盘按键上的某一个按键
* 那么编写input设备驱动的时候需要先申请一个Input_dev结构体变量,使用以下函数
*/
struct input_dev *input_allocate_device(void);
/* 注销设备需要使用以下函数 */
void input_free_device(struct input_dev *dev);
/* 除此之外,我们还可以通过devm_input_allocate_device 函数来申请input_dev 变量 但是这个函数在设备卸载的时候(加载失败)会自动释放,不需要手动释放 */
/* 申请完后就要初始化这个input_dev 初始化内容为事件类型(evbit)和事件值(keybit),然后使用下列
* 函数向input子系统核心层注册
*/
int input_register_device(struct input_dev *dev);
/* 注销input驱动 */
void input_unregister_device(struct input_dev *dev);
/* 总结 */
struct input_dev *inputdev;
static int __init xxx_init(void){
inputdev = devm_input_allocate_device();
inputdev->name = "xxx_inputdev";
/* 第一种设置事件和事件值的方法 */
__set_bit(EV_KEY,inputdev->evbit);
__set_bit(EV_REP,inputdev->evbit);
__set_bit(KEY_0,inputdev->keybit);
/* 第二种设置事件和事件值的方法 */
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
/* 第三种设置事件和事件值的方法 */
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
input_set_capability(keyinputdev.inputdev,EN_KEY,KEY_0);
input_register_device(inputdev);
return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void){
input_unregister_device(input_dev);
input_free_device(inputdev);
}
上报输入事件
注册好input_dev以后还需要上报输入事件,比如按键,我们需要在按键中断处理函数,或者消抖定时器中断函数中将按键值上报给input子系统核心层,不同事件的的上报API函数不同
/* 常用的事件上报API函数 针对所有事件
* dev: 需要上报的input设备
* type: 上报的事件类型 比如EV_KEY
* code: 事件码,我们注册的按键值 KEY_0
* value:事件值,比如1表示按下 0表示按键松开
*/
void input_event(struct input_dev *dev,
unsigned int type,
unsigned int code,
int value
);
/* 针对按键上报的函数 */
static inline void input_report_key(struct input_dev *dev,
unsigned int code,
int value){
input_event(dev,EV_KEY,code,!!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_mt_sync(struct input_dev *dev);
/* 当我们上报事件后还需要使用input_sync来告知input子系统上报结束,进行同步操作*/
void input_sync(struct input_dev *dev);
/* 总结 */
void timer_function(unsigned long arg){
unsigned char value;
value = gpio_get_value(keydesc->gpio);
if(0 == value){
input_reprot_key(inputdev,KEY_0,1);
input_sync(inputdev);
}
else{
input_report_key(inputdev,KEY_0,0);
input_sync(inputdev);
}
}
input_event 结构体
/* linux内核使用input_event结构体表示所有的输入事件 include/uapi/linux/input.h */
/*
*time: 事件发生的时间
*type: 事件类型
*code: 事件码
*value:值
*/
struct input_event{
struct timeval time;
__u16 type;
__u16 code;
__u16 value;
};
platform 框架下需要添加的内容
struct mykey_dev{
struct input_dev *idev;
struct timer_list timer;
int gpio;
int irq;
}
static int mykey_probe(struct platform_device *pdev){
struct mykey_dev *key;
struct input_dev *idev;
int ret;
dev_info(&pdev->dev,"Key device and driver matched successfully!\n");
/* key指针分配内存 */
key = devm_kazlloc(&pdev->dev,sizeof(struct mykey_dev),GFP_KERNEL);
if(!key)
return -ENOMEM;
platform_set_drvdate(pdev,key); //设置私有数据
/* 初始化按键
* 这个函数主要是从设备树获取按键硬件信息
* 申请GPIO devm_gpio_request(); 设置GPIO模式 获取中断号 获取中断触发类型
* 申请中断
*/
ret = mykey_init(pdev);
if(ret)
return -ENOMEM;
/* 初始化定时器 */
init_timer(&key->timer);
key->timer.function = key_timer_function;
key->timer.data = (unsigned long)key;
/* input_dev 初始化 */
idev = devm_input_allocate_device(&pdev->dev);
if(idev){
return -ENOMEM;
}
key->idev = idev;
idev->name = "mykey";
__set_bit(EV_KEY,idev->evbit);
__set_bit(EV_REP,idev->evbit);
__set_bit(PS_KEY0_CODE,idev->keybit);
return input_register_device(idev);
}
static int mykey_remove(struct platform_device *pdev){
struct mykey_dev *key = platform_get_drvdata(pdev);
/* 删除定时器 */
del_timer_sync(&key->timer);
/* 卸载按键设备 */
input_unregister_device(key->idev);
return 0;
}
/* 匹配列表 */
static const struct of_device_id key_of_match[] = {
{.compatible = "mizar,key"},
{/* Sentinel */}
};
/* platform 驱动结构体 */
static struct platform_driver mykey_driver = {
.driver = {
.name = "zynq-key";
.of_match_table = key_of_match,
},
.probe = mykey_probe;
.remove = mykey_remove,
};
module_platform_driver(mykey_driver);