input子系统
前言
本文主要介绍在linux下input子系统框架,以及如何运作的,自己编写的驱动是怎样的方式嵌入到input子系统中去的。
背景
在linux中每出现一个子系统都是有原因的,为什么会有这个东西,以及这个东西有什么好处,针对一个复杂的系统,他一定会有各式各样的外设,比如鼠标键盘、遥感等等,这些都属于输入设备,这些设备的功能都很单一,获取位置数据,获取键盘键值等。当然可以给每个设备都写一个驱动,那么管理就会变得混乱,应用层使用起来就得一一对应比较麻烦,从这些设备反馈的也就是一个数值,可以进行抽象处理。
输入设备(键盘、鼠标等)的驱动都是采用字符设备、混杂设备处理的
可以对分散的、不同类别的输入设备进行统一的驱动,所以才出现了输入子系统
好处:
- 统一硬件差异但功能相近的设备管理
- 提供了用于分发输入报告给用户应用程序的简单的事件(event)接口
- 驱动不必创建、管理/dev节点以及相关的访问方法,不用在驱动中特定的创建设备节点及class
简化驱动流程
重要的类型
//自己管理的链表
static LIST_HEAD(input_dev_list); //所有struct input_dev 的node都挂着
static LIST_HEAD(input_handler_list);//所有struct input_handler的node都挂这
struct input_dev {
const char *name;
struct input_id id;
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
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);
struct input_handle __rcu *grab;
struct device dev;
struct list_head h_list; //input_handle的node挂这里
struct list_head node; //挂到全局的input_dev_list下
}
struct input_handler {//提供消息的处理方法 及用来创建设备节点
void *private;
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
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);
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);//connect会去new一个struct evdev对象
void (*disconnect)(struct input_handle *handle);
void (*start)(struct input_handle *handle);
bool legacy_minors;
int minor;
const char *name;
const struct input_device_id *id_table;
struct list_head h_list;//input_handle的node挂这里
struct list_head node;//挂在全局的input_handler_list下
}
struct input_handle { //主要是记录input_dev和input_handler,作为两个的桥梁
void *private;
int open;
const char *name;
struct input_dev *dev;
struct input_handler *handler;
struct list_head d_node;//挂到input_dev的内置list里面
struct list_head h_node;//挂到handler的内置list里面
};
struct evdev {//主要用户控件的open和read就是靠这个设备
int open;
struct input_handle handle;
wait_queue_head_t wait;
struct evdev_client __rcu *grab;
struct list_head client_list; //将来evdev_client都挂在这
spinlock_t client_lock; /* protects client_list */
struct mutex mutex;
struct device dev;
struct cdev cdev;
bool exist;
};
evdev_client 和evdev联系比较大
struct evdev_client {//主要是存储,事件发生的时候键的类型,code以及value
unsigned int head;
unsigned int tail;
...
struct evdev *evdev;
struct list_head node;
unsigned int clk_type;
...
struct input_event buffer[];
};
驱动注册
handle有点像设备驱动模型里面的bus他左边的input_dev,右边是input_handler,
而eventdev 又是和handle关系比较密切。
框架init
z:\myworkspeace\kernel\myOrgPiPc2PRO\drivers\input\input.c
subsys_initcall(input_init);
intput初始化proc的内容以及注册了一个class下面的register_chardev
只是占用了一些设备号,并没有生成dev下面的设备。
一些注册的api
input_register_device
这个接口一般是用户写一个输入设备的驱动会用到这个接口,一般使用
gpstInputDev = input_allocate_device(); 申请一个设备
然后赋值指定这个输入设备有哪些能力等驱动相关的这个具体在OrangePIPC2—红外模块(一)这里详细分析。
整体流程大概是这样之,用户的驱动调用input_register_device,input子系统拿这个dev去全局的input_handler_list中去match(//匹配的方式:先由匹配dev的id和handler->id_table(这里还会根据id_table的flag进行是否有额外的匹配),然后在handler的match方法匹配),match成功后,执行这个handler的connect方法,在这里就会创建一个evdev 设备,
int input_register_device(struct input_dev *dev){
...
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
error = device_add(&dev->dev);
if (error)
goto err_free_vals;
list_add_tail(&dev->node, &input_dev_list);
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);//拿这个dev去handler中去match
}
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
id = input_match_device(handler, dev);//匹配的方式:先由匹配id和handler->id_table,然后在handler的match方法匹配
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id);//匹配成功后执行connect函数
if (error && error != -ENODEV)
pr_err("failed to attach handler %s to device %s, error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error);
return error;
}
input_register_handler
X:\myProjects\pipc2Pro\kernel
myOrgPiPc2PRO\drivers\input\evdev.c
这个有点像具体的处理方式比如有鼠标的handler,键盘的handler以及摇杆的handler
input_register_handler(&joydev_handler);
input_register_handler(&mousedev_handler);
input_register_handler(&input_leds_handler);
input_register_handler(&evdev_handler); //如果没有特别的匹配就选择这个匹配
connect:大致做了啥
- 创建一个struct evdev *evdev;
- 初始化client_list这个链表用来管理evdev_client
- 注册handle input_register_handle(&evdev->handle);
- 注册设备 节点 /dev/event%d
- 为设备节点注册fops evdev_fops, 设备节点的read就会调到这个fops的read
static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.legacy_minors = true,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
int input_register_handler(struct input_handler *handler)
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id){
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
INIT_LIST_HEAD(&evdev->client_list);
dev_set_name(&evdev->dev, "event%d", dev_no);
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev;
error = input_register_handle(&evdev->handle);
cdev_init(&evdev->cdev, &evdev_fops);
error = cdev_device_add(&evdev->cdev, &evdev->dev);
}
input_register_handle
int input_register_handle(struct input_handle *handle){
...
if (handler->filter)
list_add_rcu(&handle->d_node, &dev->h_list);
else
list_add_tail_rcu(&handle->d_node, &dev->h_list);
...
list_add_tail_rcu(&handle->h_node, &handler->h_list);
if (handler->start)
handler->start(handle);
}
事件的上报
上面的一些接口的描述基本是在设备的建立初期,一些成员变量的初始化以及匹配过程,
但还缺少一个主动触发的接口,才能将需要的数据上报出去。上面fops中有read接口获取内部数据,而时间上报的接口就是网这个buffer空间里写东西,一旦有东西了,read就不阻塞返回了。
input_event(gpstInputDev, EV_KEY, KEY_POWER, 0);
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
主要将event type code 和value装到buf里面,同样应用成拿到的也是一个大结构体,里面包含了type code 和value
}