/*输入子系统分析 input.c*/
/*1. 为什么需要输入子系统?
由于我们平时用的输入设备比较杂乱,比较多。 比如: 鼠标,键盘, 触摸屏等。
当我们写驱动的时候都需要注册字符设备文件或者混杂设备文件。所以出现了一种机制。
这种机制就是把各种输入设备定义为input_device。 把处理这种设备的函数定义为input_handler。
这个样以来,程序员只需要操作input_device,而input_handler则是已经为各种设备做好的处理函数了。
这样的好处是对所有的设备进行一个统一和抽象处理。形成一个统一的接口。方便驱动的编写等。
这种机制就是输入子系统。
*/
/*分析: drivers/input/input.c*/
static int __init input_init(void)
{
int err;
/*注册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;
/*注册字符设备。主设备号13。 file_operations定义为input_fops*/
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
if (err) {
pr_err("unable to register char major %d", INPUT_MAJOR);
goto fail2;
}
return 0;
}
/*而input_fops中只有一个open函数, 那如何read, write设备呢?*/
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
/*先进入open函数看看,都干了什么? */
static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler;
const struct file_operations *old_fops, *new_fops = NULL;
/*从input_table中根据次设备号找到handler, 获得handler中的fops, 赋值给new_fops*/
handler = input_table[iminor(inode) >> 5];
if (handler)
new_fops = fops_get(handler->fops);
/*将new_fops给file->f_op*/
old_fops = file->f_op;
file->f_op = new_fops;
/*调用new_fops中的open函数。 也就是说input_open_file只是一个中转的过程。到头来还是调用设备注册时的fops中的open函数*/
err = new_fops->open(inode, file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
}
/* 有一个问题? 那input_table是从那里赋值的?发现input_table是从 input_register_handle中得到的 */
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int retval;
INIT_LIST_HEAD(&handler->h_list);
/*将传进来的hanler放入input_table中*/
input_table[handler->minor >> 5] = handler;
/*放入input_handler_list链表*/
list_add_tail(&handler->node, &input_handler_list);
/*遍历input_dev_list链表, 对于每个Input_dev结构, 调用input_attach_handler根据input_handler的id_table判断是否支持input_dev*/
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
return retval;
}
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
/*根据input_handler中的id_table判断是否支持input_dev。 如果支持则调用input_handler中的connect函数*/
id = input_match_device(handler, dev);
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id);
return error;
}
/*还有一个问题?那是谁调用input_register_handler函数的?*/
/*搜索工程,发现是各式各样的输入设备。 比如键盘, 鼠标, 游戏句柄等。 也就是说当调用input_register_handler函数时,
参数handler就是处理这类设备的操作集合。
*/
/*那么问题来了? 当我想实现一个按键操作LED灯的驱动程序时, 我应该如何实现? */
int input_register_device(struct input_dev *dev)
{
/*将input_dev放入input_dev_list链表中*/
list_add_tail(&dev->node, &input_dev_list);
/*遍历input_handler_list链表,对于每个input_handler结构,调用input_attach_handler看是否匹配。
如果匹配则调用input_handler中的connect函数建立"连接"
*/
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
}
/*
那么, 如何建立连接?
*/
/*分析: evdev.c 一个特殊的handler例子*/
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)
{
//1. 分配一个evdev结构
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
//2. 初始化evdev->handle中的dev
// 初始化evdev->handle中的hanler。 这样以来,input_handle就成handler与dev之间连接的桥梁了。
evdev->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev;
}
//3. 注册input_handle
int input_register_handle(struct input_handle *handle)
{
//input_handler->h_list = input_handle
//input_dev->h_list = input_handle
list_add_tail_rcu(&handle->d_node, &dev->h_list);
list_add_tail_rcu(&handle->h_node, &handler->h_list);
}
/*
小结:当注册Input_dev时,会去input_handler的链表中, 从前到后,依次的根据input_handler中的id_table判断是否支持这个刚注册的input_dev。
如果支持,则调用input_handler中的connect函数来建立连接。 所谓的建立连接,也就是建立一个input_handle结构。 这样的话handle中的dev指向input_dev
handle中的handler指向Input_handler。 然后将input_handle放入input_dev的handle链表中,input_handle放如input_dev的链表中。
当需要操作input_dev支持的Input_handler时, 就会去input_dev的handle链表中找到input_handle,然后同过input_handle中的handler找到input_handler。
同理: 当注册input_handler时,会去input_dev的链表中, (省略, 和上面的操作几乎一样)
*/
/*那还有几个问题? 当应用程序read时, 我们的驱动程序应该干些什么?*/
//我们上面分析了,new_fops是从input_register_handler传进来的handler中的fops。 行,我们假设我们用的evdev这个事件处理handler
/*分析evdev_read函数 */
static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
//如果循环缓冲区的头等于循环缓冲区的尾部,或者是不阻塞的。 则立刻返回
if (client->packet_head == client->tail && evdev->exist &&
(file->f_flags & O_NONBLOCK))
return -EAGAIN;
/*不过不是上述的情况,则立马休眠*/
retval = wait_event_interruptible(evdev->wait,
client->packet_head != client->tail || !evdev->exist);
}
/*很明显,当read的时候,没有数据就立马休眠, 那当有数据时, 谁来唤醒呢? */
/*毫无疑问,当数据来时,硬件先知道的。 当数据到达时, 就会触发中断。*/
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
//当事件发生时, 则调用input_event上报事件,则会调用input_handler中的event函数
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle, type, code, value);
}
/*
总结:如何写一个符合输入子系统的驱动程序?
1. 分配一个input_dev结构
2. 设置input_dev
3. 注册input_dev
4. 硬件相关的代码,一般都在中断子程序中上报事件。
*/