好记性不如烂笔头,整理一下笔记~ Linux驱动之输入子系统框架
输入子系统将该类驱动划分为3部分
1、核心层 input.c
2、设备层 Gpio_keys.c ...
3、事件处理层 Evdev.c
事件处理层为纯软件的东西,设备层涉及底层硬件,它们通过核心层建立联系,对外提供open write等接口。
1、我们首先来看,核心层 input.c如何向外界提供接口
在 input_init 中注册了字符设备驱动
register_chrdev(INPUT_MAJOR, "input", &input_fops);
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
在注册的fops中,仅仅只有1个open函数,下面我们来看这个open函数
static int input_open_file(struct inode *inode, struct file *file)
{
// 根据次设备号,从 input_table 数组中取出对应的 handler
struct input_handler *handler = input_table[iminor(inode) >> 5];
const struct file_operations *old_fops, *new_fops = NULL;
// 将 handler 的fops赋值给file->f_op,并用调用新的open函数
old_fops = file->f_op;
file->f_op = fops_get(handler->fops);
err = file->f_op->open(inode, file);
}
那么,我们应该可以猜到,必定有个地方创建了handler并对它进行一定的设置,提供fops函数,将它放入input_table。
就这样,Input.c 实现了一个通用对外接口。
2、事件处理层,注册input_handler
2.1 放入链表、数组(input_register_handler)
input.c input_register_handler 函数中 创建了handler并对它进行一定的设置,提供fops函数,将它放入input_table,
int input_register_handler(struct input_handler *handler)
{
// 将 handler 放入 input_table
input_table[handler->minor >> 5] = handler;
// 将 handler 放入 input_handler_list 链表
list_add_tail(&handler->node, &input_handler_list);
// 取出 input_dev_list 链表中的每一个 dev 与 该 handler 进行 比对
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
}
以 evdev.c 为例
static struct input_handler evdev_handler = {
.event = evdev_event,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.fops = &evdev_fops,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table= evdev_ids,
};
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
// 读函数中 如果没有事件上报休眠,等待上报事件 唤醒休眠,将事件传送到用户空间
static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
//如果无数据可读,且为非阻塞方式 立刻返回
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
//否则,进入休眠
retval = wait_event_interruptible(evdev->wait,
client->head != client->tail || !evdev->exist);
//将内核空间数据拷贝到用户空间,略
return retval;
}
2.2 匹配 (input_attach_handler)
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
// 看 dev.id 是否存在于 handler->id_table 中
id = input_match_device(handler->id_table, dev);
if (!id)
return -ENODEV;
// 在的话,调用 handler->connect
error = handler->connect(handler, dev, id);
}
2.3 建立连接
我们以 Evdev.c 为例,看一下connect函数
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
// 不要关心 evdev ,只看 evdev->handle 即可,这里构建了一个 handle ,注意不是handler
// handle 就是个 中间件,可以理解成胶带,它把 hander 与 dev 连在一起
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
// 第一次建立联系,在 handle 中记录 dev 与 handle 的信息,这样通过handle就可以找到dev与handler
// 即是 实现 handle -> dev handle -> hander 的联系
evdev->handle.dev = dev;
evdev->handle.handler = handler;
// 申请设备号,创建设备节点
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
dev_set_name(&evdev->dev, "event%d", minor);
// 在input 类下面创建设备,文件夹的名字是 evdev->name ->inputn ,设备名是 dev->cdev.dev.name -> eventn
cdev = class_device_create(&input_class, &dev->cdev, devt,
dev->cdev.dev, evdev->name);
// 注册 handle
error = input_register_handle(&evdev->handle);
}
2.4 注册handle,第二次建立联系
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
// 将handle 记录在 dev->h_list 中
list_add_tail(&handle->d_node, &handle->dev->h_list);
// 将handle 记录在 handler->h_list 中
list_add_tail(&handle->h_node, &handler->h_list);
// 至此,dev 与 hander 也可以找到handle了,dev <-> handle <-> handler 之间畅通无阻
}
简单梳理一下:
事件处理层,构建 handler , 通过 input_register_handler 进行注册,注册时
1、将 handler 放入 input_handler_list 链表
2、将 handler 放入 input_table
3、取出 input_dev_list链表中的每一个dev 调用 input_attach_handler 进行id匹配
4、如果匹配成功,则调用 handler->connect 第一次建立连接
5、创建 handle 在 handle 中记录 dev 与 handler 的信息,这样通过handle就可以找到dev与handler
6、在dev hander 中记录 handle的信息,实现 dev <-> handle <-> handler
3、设备层,注册input_dev
int input_register_device(struct input_dev *dev)
{
// 将 dev 放入 input_dev_list
list_add_tail(&dev->node, &input_dev_list);
// 设置 设备名?所谓的input0 input1 由此而来吧
snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),"input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);
error = device_add(&dev->cdev);
// 匹配 handler ,参考 2.2-2.4
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
}
4、辛辛苦苦建立联系,是干嘛的
在设备层,我们写驱动的时候,比如鼠标按了一下,我们要上报event 到Handler层进行处理,然后提交给用户程序。
例如:Gpio_keys.c 中断处理函数中
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
input_event(input, type, button->code, !!state);
input_sync(input);
return IRQ_HANDLED;
}
又得回到input.c void input_event函数
void input_event
input_handle_event(dev, type, code, value);
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
if (disposition & INPUT_PASS_TO_HANDLERS)
input_pass_event(dev, type, code, value);
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle, type, code, value);
例如LED类事件,dev中定义了event 会先调用dev->event
最终调用 handler->event(handle, type, code, value);
好吧,Evdev.c 中的 event 函数看不懂。
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
......看不懂
// 唤醒 休眠
wake_up_interruptible(&evdev->wait);
}
5、写一个Input子系统 设备驱动
事件处理层不用我们管了,- -是暂时能力有限管不了。写写设备层的程序就好了。
软件设计流程:
/* 1. 分配一个Input_dev结构体 */
/* 2. 设置 支持哪一类事件,该类事件里的那些事件*/
/* 3.注册 */
/* 4.硬件相关操作 */
事件类型:
参考程序:基于mini2440 linux2.6.32内核