本篇博客里将会对输入子系统进行比较深入的分析,第一部分将基于内核2.6.2版本,第二部分将基于内核的3.14版本,同时我会为两个版本都提供一个示例代码
我会尽可能深入的分析代码,如果你看完了这篇博客,相信你会有所收获。

input输入子系统分为三层(为了让篇幅不会太过长,我会把源码中不需要关心的代码删掉)
设备层,驱动层以及核心层
先看下核心层 /driver/input/input.c
首先从模块入口开始分析

static int __init input_init(void)
{int err;err = class_register(&input_class);err = input_proc_init();err = register_chrdev(INPUT_MAJOR, "input", &input_fops);return 0;}

向内核注册了一个&input_fops

static const struct file_operations input_fops = {.owner = THIS_MODULE,.open = input_open_file,
};

看下这个fops的open函数

static int input_open_file(struct inode *inode, struct file *file)
{/**从input_table中根据次设备号获取一个元素,然后将这个元素赋值给handler* 所以等下我们要看下input_table是谁给它初始化的*/struct input_handler *handler = input_table[iminor(inode) >> 5];const struct file_operations *old_fops, *new_fops = NULL;int err;//把handler中的fops赋值给news_fops/* No load-on-demand here? */if (!handler || !(new_fops = fops_get(handler->fops)))return -ENODEV;/** That's _really_ odd. Usually NULL ->open means "nothing special",* not "no device". Oh, well...*/old_fops = file->f_op;file->f_op = new_fops;err = new_fops->open(inode, file);if (err) {fops_put(file->f_op);file->f_op = fops_get(old_fops);}fops_put(old_fops);return err;
}

这个函数主要工作是根据次设备号从input_table中获取一个handler,将这个handler赋值给file->f_op,如是我们就可以直接使用这个fops来对当前文件操作。
所以很明显需要知道谁向input_table中注册handler

int input_register_handler(struct input_handler *handler)
{struct input_dev *dev;INIT_LIST_HEAD(&handler->h_list);if (handler->fops != NULL) {if (input_table[handler->minor >> 5])return -EBUSY;input_table[handler->minor >> 5] = handler;}list_add_tail(&handler->node, &input_handler_list);list_for_each_entry(dev, &input_dev_list, node)input_attach_handler(dev, handler);input_wakeup_procfs_readers();return 0;
}
EXPORT_SYMBOL(input_register_handler);

所以我们知道是调用int input_register_handler(struct input_handler *handler); 将handler注册到input_table里。
并将handler尾部插入到input_handler_list中,然后对这个链表input_handler_list中每一个node进行遍历,调用input_attach_handler。
input_handler_list这个可能目前还不太懂,先放一边,来看下到底是谁调用了input_register_handler
查询后发现有evdev.c joydev.c keyboard.c mousedev.c tsdev.c有调用,我们选择最为通用的evdev.c来分析

如是我们进入了设备层
先看下evdev.c里到底注册了一个什么样的handler

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,
};

刚才说过,应用层将会调用的是handler的fops,所以在这里,实际上是调用了evdev_handler的fops。我们看下它的open函数

static int evdev_open(struct inode *inode, struct file *file)
{struct evdev_client *client;struct evdev *evdev;int i = iminor(inode) - EVDEV_MINOR_BASE;int error;evdev = evdev_table[i];client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);client->evdev = evdev;list_add_tail(&client->node, &evdev->client_list);if (!evdev->open++ && evdev->exist) {error = input_open_device(&evdev->handle);if (error) {list_del(&client->node);kfree(client);return error;}}file->private_data = client;return 0;
}

首先从evdev_table数组中取出一个evdev,然后把这个evdev赋值给client->evdev,然后把client加入到evdev->client_list,这个操作说实话我完全不知道什么意思,但是我知道open实际上是对evdev进行操作的,而evdev是从evdev_table这个全局数组中获取的,所以我们需要知道到底是谁初始化了这个数组,如是我们找到了evdev_connect函数

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)
{struct evdev *evdev;struct class_device *cdev;dev_t devt;int minor;int error;//遍历evdev_table数组,找到第一个符合minor < EVDEV_MINORS的元素,什么意思呢?我也不懂,所以先不管//我知道了,evdev的次设备号最多只有EVDEV_MINORS个,不能超过这个数量,所以它是要找一个小于EVDEV_MINORS,同时又没有使用过的次设备号for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);if (minor == EVDEV_MINORS) {printk(KERN_ERR "evdev: no more free evdev devices\n");return -ENFILE;}//申请一个evdevevdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);if (!evdev)return -ENOMEM;INIT_LIST_HEAD(&evdev->client_list);//这个初始化了一个等待队列头部,所以后面是不是会有睡眠和唤醒的操作呢?当然有,这是实现阻塞的基本方法init_waitqueue_head(&evdev->wait);//初始化evdev,重点关注的是evdev->handle.dev = dev;和evdev->handle.handler = handler;evdev->exist = 1;evdev->minor = minor;evdev->handle.dev = dev;evdev->handle.name = evdev->name;evdev->handle.handler = handler;evdev->handle.private = evdev;sprintf(evdev->name, "event%d", minor);/*将evdev赋值给evdev_table*/evdev_table[minor] = evdev;devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name);if (IS_ERR(cdev)) {error = PTR_ERR(cdev);goto err_free_evdev;}/* temporary symlink to keep userspace happy */error = sysfs_create_link(&input_class.subsys.kobj,&cdev->kobj, evdev->name);if (error)goto err_cdev_destroy;error = input_register_handle(&evdev->handle);if (error)goto err_remove_link;return 0;}

首先是获得minor,申请一个evdev,接着对evdev进行初始化
evdev->handle.dev = dev; evdev->handle.handler = handler; 前者代表着device,后者代表着driver(可能是这样,等下看下是谁在调用这个函数时再做验证)
然后evdev_table[minor] = evdev;
接下来就是字符设备创建的基本框架,获得devt,然后基于input_class创建一个字符设备,这个我们就不深入探讨。
我们只知道在这个函数中,将传进来的dev和handler都赋值给了一个evdev,而这个evdev将会在后面的open等函数使用到。
最后对evdev->handle调用了input_register_handle,而handle里正好包含了connect传进来的参数的dev和handler。
所以我先看input_register_handle,在看到底是谁调用了connect函数。
在查询谁调用connect时,不能直接查询是谁调用evdev_connect,因为这个函数根本就没有导出去,而且是当前文件的最底层函数之一。

int input_register_handle(struct input_handle *handle)
{struct input_handler *handler = handle->handler;list_add_tail(&handle->d_node, &handle->dev->h_list);list_add_tail(&handle->h_node, &handler->h_list);if (handler->start)handler->start(handle);return 0;
}

将handle->d_node加入到handle->dev->h_list链表中,将handle->h_node加入到handler->h_list链表中,这是干啥呢?我也不知道,不过我可以看出来我们可以根据handle来找handler,因为handler是它的字段,而如果调用了input_register_handle,我们也可以从handler来找handle,内核就喜欢搞这事。
我在网上找到了一个很不错的图,如下

我们来看到底是谁调用了connect函数
如是我们重新进入了通用的核心层

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{const struct input_device_id *id;int error;if (handler->blacklist && input_match_device(handler->blacklist, dev))return -ENODEV;id = input_match_device(handler->id_table, dev);if (!id)return -ENODEV;error = handler->connect(handler, dev, id);...
}

在这个函数的最后一行调用了connect函数,这个函数其他的内容比较少,不需要关注
我们来看下谁调用了input_attach_handler函数,发现了一个有趣的结果

int input_register_device(struct input_dev *dev)
{static atomic_t input_no = ATOMIC_INIT(0);struct input_handler *handler;const char *path;int error;set_bit(EV_SYN, dev->evbit);/** If delay and period are pre-set by the driver, then autorepeating* is handled by the driver itself and we don't do it in input.c.*/init_timer(&dev->timer);if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {dev->timer.data = (long) dev;dev->timer.function = input_repeat_key;dev->rep[REP_DELAY] = 250;dev->rep[REP_PERIOD] = 33;}if (!dev->getkeycode)dev->getkeycode = input_default_getkeycode;if (!dev->setkeycode)dev->setkeycode = input_default_setkeycode;list_add_tail(&dev->node, &input_dev_list);snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),"input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);if (!dev->cdev.dev)dev->cdev.dev = dev->dev.parent;error = class_device_add(&dev->cdev);if (error)return error;path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);printk(KERN_INFO "input: %s as %s\n",dev->name ? dev->name : "Unspecified device", path ? path : "N/A");kfree(path);list_for_each_entry(handler, &input_handler_list, node)input_attach_handler(dev, handler);input_wakeup_procfs_readers();return 0;
}int input_register_handler(struct input_handler *handler)
{struct input_dev *dev;INIT_LIST_HEAD(&handler->h_list);if (handler->fops != NULL) {if (input_table[handler->minor >> 5])return -EBUSY;input_table[handler->minor >> 5] = handler;}list_add_tail(&handler->node, &input_handler_list);list_for_each_entry(dev, &input_dev_list, node)input_attach_handler(dev, handler);input_wakeup_procfs_readers();return 0;
}

所以我们知道了,当我们调用input_register_device向内核注册一个input_dev,或者是当我们调用input_register_handler向内核注册一个handler时,在注册的过程中内核分别会遍历input_handler_list和input_dev_list链表(就是注册handler,内核会遍历dev所在的链表,注册dev,则会遍历handler所在的链表),遍历的过程中都会调用一个input_attach_handler函数

分两种情况讨论
1、如果是注册handler
在这个函数会在遍历input_dev_list上的dev时尝试匹配注册进来的handler,如果匹配成功则会调用handler的connect函数。如是我们就从核心层进入了handler层(可以称为事件层或者驱动层),我们假设上述的handler是evdev_handler,那么将会调用evdev_handler的connect函数,在这个函数里首先会创建一个evdev,(对,我们向内核注册一个handler,connect会创建和初始化一个dev,并将注册的进来的handler赋值给evdev->handle.handler,把匹配到的dev赋值给evdev->handle.dev.) 然后基于&input_class这个在核心层的input_init已经初始化好的class创建一个evdev->name设备文件,最后调用input_register_handle集合有dev和handler的evdev->handle注册到内核中
input_register_handle这个函数里
我们来看下fops,当上层用户空间调用系统调用时,最终会调用到handler的fops,结合我们自己所了解的字符设备驱动框架来分析一个evdev的fops中的open/read/write函数

static int evdev_open(struct inode *inode, struct file *file)
{struct evdev_client *client;struct evdev *evdev;int i = iminor(inode) - EVDEV_MINOR_BASE;int error;if (i >= EVDEV_MINORS)return -ENODEV;evdev = evdev_table[i];client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);if (!client)return -ENOMEM;client->evdev = evdev;list_add_tail(&client->node, &evdev->client_list);if (!evdev->open++ && evdev->exist) {error = input_open_device(&evdev->handle);if (error) {list_del(&client->node);kfree(client);return error;}}file->private_data = client;return 0;
}

open函数代码前面已经列过,抱歉,在这里重复了一下。
我想尝试弄清楚这里的evdev->open,但是只是感觉它有点像是一个计数标志。在open函数中会创建一个client,然后根据次设备号从evdev_table获取一个合适的evdev赋值给client->evdev,最后把client保存在file->private_data,后面其他的操作将会基于这个client

static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{struct evdev_client *client = file->private_data;struct evdev *evdev = client->evdev;int retval;if (count < evdev_event_size())return -EINVAL;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);if (retval)return retval;if (!evdev->exist)return -ENODEV;while (client->head != client->tail && retval + evdev_event_size() <= count) {struct input_event *event = (struct input_event *) client->buffer + client->tail;if (evdev_event_to_user(buffer + retval, event))return -EFAULT;client->tail = (client->tail + 1) & (EVDEV_BUFFER_SIZE - 1);retval += evdev_event_size();}return retval;
}

在connect函数中,我记得有初始化一个等待队列头init_waitqueue_head(&evdev->wait);而我们知道,evdev的connect函数一定要比evdev的fops先调用,所以这个等待队列头很有可能是为read函数做准备的
结合我们自己写过的字符设备驱动框架,可以大胆猜测open函数首先会取出file->private_data,然后在取出client->evdev
首先需要处理的是判断当前flags是否为非阻塞,如果是,同时也没有数据,则立即返回。如果不是非阻塞,则开始调用wait_event_interruptible,对于这个函数的condition有点意思,client->head != client->tail || !evdev->exist,限于篇幅,我不做深入探讨这个condition了,下面一行便是唤醒后将被执行的代码,大概就是将数据提取出来,然后将数据发送给用户空间。在这里采用了一个非常重要的结构体input_event负责装载信息给usr,在介绍这个input_event之前,需要说明两点,第一,在这个read中,它是通过循环的方式把client中从head到tail(反正是缓冲区所有的数据)都读完,这个写代码的方式值得学习,第二,我们有提到唤醒的动作,那谁来执行这个唤醒呢?先把这个问题放在心里。

struct input_event {struct timeval time;__u16 type;__u16 code;__s32 value;
};

这就是evdev上报给用户空间的数据类型,包含time,type,code和value。有趣的是,这个结构体是在核心层被定义的,所以所有的输入子系统上挂载的字符设备驱动都是以这种方式上报数据给用户的。
现在我们来回答之前提出的问题,谁来执行唤醒操作呢?在以前接触到的字符设备驱动中,比如按键驱动在按键触发的中断处理程序里会执行唤醒操作,因为当产生中断了,说明有信息产生,让wait_event_interruptible中的condition不再成立。
在这里执行唤醒操作的是注册进去的evdev_handler的event函数

static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{struct evdev *evdev = handle->private;struct evdev_client *client;if (evdev->grab) {client = evdev->grab;do_gettimeofday(&client->buffer[client->head].time);client->buffer[client->head].type = type;client->buffer[client->head].code = code;client->buffer[client->head].value = value;client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);kill_fasync(&client->fasync, SIGIO, POLL_IN);} elselist_for_each_entry(client, &evdev->client_list, node) {do_gettimeofday(&client->buffer[client->head].time);client->buffer[client->head].type = type;client->buffer[client->head].code = code;client->buffer[client->head].value = value;client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);kill_fasync(&client->fasync, SIGIO, POLL_IN);}wake_up_interruptible(&evdev->wait);
}

这个函数就是非常重要的上报函数,首先event函数将获取到的信息进行打包,打包的方式也很巧妙。当信息打包完毕后,调用kill_fasync(&client->fasync, SIGIO, POLL_IN);向client->fasync发送一个SIGIO信号,关于内核的异步通知,我以后会详细的讲解,但是在这里我们知道如果调用了这个函数,说明信息已经硬件设备的信息已经获取完毕,如果应用程序设置了异步通知模式,那么应用程序就可以接收到内核发出的异步fasync信号。最后调用wake_up_interruptible(&evdev->wait);唤醒等待队列上的evdev->wait
所以现在我们需要看到底谁在调用这个event函数,由于evdev_event是其所在文件的内部函数,调用它的函数也不是直接调用(好像没有直接调用这个概念,你懂意思就好),我们知道它是在设备层被调用,所以我们通过input_register_device找到设备层就可以了

2、如果注册的是设备层
可以发现调用input_register_device的文件比调用input_register_handler的文件多了很多,这也不奇怪,因为每一类设备驱动肯定会对应多个不同的具体设备。我们在这里找的是最为简单的gpio_keys,有趣的是这个dev它是基于平台总线建立的,当它们内部匹配成功后,调用的probe函数中调用了input_register_device

int input_register_device(struct input_dev *dev)
{static atomic_t input_no = ATOMIC_INIT(0);struct input_handler *handler;const char *path;int error;set_bit(EV_SYN, dev->evbit);...list_for_each_entry(handler, &input_handler_list, node)input_attach_handler(dev, handler);input_wakeup_procfs_readers();return 0;
}

可以看到,当向内核注册一个input_dev时,内核会遍历input_handler_list上的每一个handler,尝试着和注册进来的input_dev进行匹配,如果匹配成功,将会调用connect函数,然后就是我们所熟知的套路了。现在我们来看下实际的例子。
/driver/input/keyboard/gpio_keys.c

static int __devinit gpio_keys_probe(struct platform_device *pdev)
{struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;struct input_dev *input;int i, error;input = input_allocate_device();if (!input)return -ENOMEM;platform_set_drvdata(pdev, input);input->evbit[0] = BIT(EV_KEY);input->name = pdev->name;input->phys = "gpio-keys/input0";input->dev.parent = &pdev->dev;input->id.bustype = BUS_HOST;input->id.vendor = 0x0001;input->id.product = 0x0001;input->id.version = 0x0100;for (i = 0; i < pdata->nbuttons; i++) {struct gpio_keys_button *button = &pdata->buttons[i];int irq = gpio_to_irq(button->gpio);unsigned int type = button->type ?: EV_KEY;set_irq_type(irq, IRQ_TYPE_EDGE_BOTH);error = request_irq(irq, gpio_keys_isr, IRQF_SAMPLE_RANDOM,button->desc ? button->desc : "gpio_keys",pdev);}input_set_capability(input, type, button->code);}error = input_register_device(input);...return 0;...
}

简单了解一下probe函数也可以重新认识一下平台总线,在probe函数里构建一个input_dev,对这个input_dev进行初始化,然后把input_dev注册到内核的输入子系统中去。但是在注册到输入子系统之前,它for循环里注册了中断,我们要干什么?我要找和event相关的函数,event函数是上报信息的,所以我们需要在使用event之前要获得信息,仔细研究一下中断处理函数,可能会有所发现

static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{int i;struct platform_device *pdev = dev_id;struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;struct input_dev *input = platform_get_drvdata(pdev);for (i = 0; i < pdata->nbuttons; i++) {struct gpio_keys_button *button = &pdata->buttons[i];int gpio = button->gpio;if (irq == gpio_to_irq(gpio)) {unsigned int type = button->type ?: EV_KEY;int state = (gpio_get_value(gpio) ? 1 : 0) ^ button->active_low;input_event(input, type, button->code, !!state);input_sync(input);}}return IRQ_HANDLED;
}

果然在获取信息以后,调用了input_event和input_sync,所以我们可以大胆猜测在核心层里的input_event函数很有可能会调用驱动层handler的event函数来实现信息上报,而且必定是在input_event函数的底部,当真正调用了handler的event函数时,说明信息已经获取完毕(在设备层的中断处理程序中)以及信息打包到input_event结构体中(在input_event的函数中)现在只剩下上报信息了,如是event函数里发送一个异步信号,同时唤醒等待队列上的相关进程,最终真正实现信息上报还是read函数中间接调用的copy_to_user.

例子:按键事件输入子系统

#include <linux/module.h>
#include <linux/version.h>#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <linux/gpio_keys.h>#include <asm/gpio.h>struct input_dev *botton_dev;
struct timer_list botton_timer;struct pin_desc{int irq;char *name;unsigned int pin;unsigned int key_val;
};struct pin_desc pin_desc[4] = {{IRQ_EINT0, "S2", S3C2410_GPF0, KEY_L},{IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S},{IRQ_EINT11, "S4", S3C2410_GPG3, KEY_ENTER},{IRQ_EINT19, "S5", S3C2410_GPG11, KEY_LEFTSHIFT},
};static void buttons_timer_function(unsigned long data)
{struct pin_desc * pindesc = irq_pd;unsigned int pinval;if (!pindesc)return;pinval = s3c2410_gpio_getpin(pindesc->pin);if (pinval){/* 松开 */input_event(botton_dev, EV_KEY, pindesc->key_val, 0);input_sync(botton_dev);}else{/* 按下 */input_event(botton_dev, EV_KEY, pindesc->key_val, 1);input_sync(botton_dev);}}static irqreturn_t buttons_irq(int irq, void *dev_id)
{irq_pd = (struct pin_desc *)dev_id;mod_timer(&botton_timer, jiffies + HZ/100);return IRQ_RETVAL(IRQ_HANDLED);
}static int __init botton_init(void)
{if((botton_dev = input_allocate_device()) == NULL){input_free_device(botton_dev);printk("input_allocate_device failed\n");return -1;}/*设置按键能够产生哪一类事件,在这里设置为EV_KEY*/set_bit(EV_KEY, botton_dev->evbit);/*按键产生的是这类事件中的哪中类型的,*/set_bit(KEY_L, botton_dev->keybit);set_bit(KEY_S, botton_dev->keybit);set_bit(KEY_ENTER, botton_dev->keybit);set_bit(KEY_LEFTSHIFT, botton_dev->keybit);int err;if(!(err = input_register_device(botton_dev))){printk("input_register_device error\n");input_unregister_device(botton_dev);input_free_device(botton_dev);return -1;}init_timer(&botton_timer);botton_timer.function = buttons_timer_function;if((err = add_timer(&botton_timer)) == 0){del_timer(&botton_timer);input_unregister_device(botton_dev);input_free_device(botton_dev);}int i = 0;for(i = 0; i < 4; i++){request_irq(pin_desc[i].irq,  buttons_irq, IRQT_BOTHEDGE, pin_desc[i].name, &pin_desc[i]);}return 0;}static void __exit botton_exit(void)
{int i;for(i = 0 ; i < 4; i++){free_irq(pin_desc[i].irq, &pin_desc[i]);}del_timer(&botton_timer);input_unregister_device(botton_dev);input_free_device(botton_dev);
}module_init(botton_init);
module_exit(botton_exit);
MODULE_LICENSE("GPL");

基于3.14版本内核代码的分析
输入子系统

核心层的代码主要是在input.c中,我们从入口函数开始分析
subsys_initcall(input_init);
module_exit(input_exit);
核心层的初始化函数input_init
其调用了如下几个函数:
创建类,传进去的参数是input_class,作用类似于class_create()
因为class_register和class_create这两个函数最终都是调用_class_register实现的
class_register(&input_class);

在/proc创建bus/input/devices handlers
input_proc_init();

//申请设备号
register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES, “input”);

然后我们来看下input_handler层做了什么?
模块初始化调用如下函数
return input_register_handler(&evdev_handler)
而input_register_handler函数则是input.c文件中,所以相当于又重新回到了核心层

int input_register_handler(struct input_handler *handler)
{struct input_dev *dev;int error;error = mutex_lock_interruptible(&input_mutex); 和同步相关,获得互斥锁if (error)return error;INIT_LIST_HEAD(&handler->h_list); 为handler构建一个链表list_add_tail(&handler->node, &input_handler_list); 将handler->node加入到input_handler_list链表中list_for_each_entry(dev, &input_dev_list, node) 遍历input_dev_list这个链表,得到我们需要的devinput_attach_handler(dev, handler); 遍历完成后,将dev和handler进行匹配。dev是怎么来的?是我们遍历input_dev_list这个链表而得来input_wakeup_procfs_readers();  而handler则是我们在handler层中所获得,这是内核在启动时就已经初始化好了的  mutex_unlock(&input_mutex); 为什么要用互斥锁来对这段代码进行保护,因为这段代码中涉及到了对链表的操作,同时input_dev_listreturn 0;                    和input_handler_list属于全局链表,因为我们根本就没有在这个函数里定义这两个链表
}

所以我们需要看下input_attach_handler(dev, handler)这个函数到底做了什么

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{const struct input_device_id *id;  int error;将handler和dev进行匹配,至于如何匹配我们可以不用管,我们只需要知道,内核能确保event handler最终一定可以和dev匹配成功id = input_match_device(handler, dev); if (!id)return -ENODEV;当代码到这里时候,说明匹配成功了,程序将会调用handler的connect的方法,我们重新思考一下,这个handler到底是哪个?它正是在handler层中input_register_handler(&evdev_handler)这个函数中所传进来的handlererror = handler->connect(handler, dev, id);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;
}

所以目前为止我们看到的是,从handler层中我们传入一个input_handler,
然后进入到核心层中为将这个handler找到对应的dev,如果成功,就直接执行handler中的
connect函数

稍后再来分析connect函数
我们来看下input_device这一层
input_handler层和核心层都是内核在启动时就已经初始化好了,而device这一层是我们需要创建的
这一层我们之前已经写过了,主要做两件事情
硬件的初始化
初始化input_dev,然后用input_register_device(inputdev)将input_dev注册到核心层中

int input_register_device(struct input_dev *dev)
{......error = mutex_lock_interruptible(&input_mutex);if (error)goto err_device_del;list_add_tail(&dev->node, &input_dev_list);list_for_each_entry(handler, &input_handler_list, node)input_attach_handler(dev, handler);input_wakeup_procfs_readers();mutex_unlock(&input_mutex);
}

我们发现这其中的操作和input_register_handler非常相似
将dev加入到input_dev_list中去,然后遍历input_handler_list
找到handler以后,依然是调用connect函数

所以现在都指向了connect函数

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)
{struct evdev *evdev;int minor;int dev_no;int error;找一个没有被使用的次设备号minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);创建并初始化一个evdev对象evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);INIT_LIST_HEAD(&evdev->client_list);spin_lock_init(&evdev->client_lock);mutex_init(&evdev->mutex);init_waitqueue_head(&evdev->wait);evdev->exist = true;dev_no = minor;/* Normalize device number if it falls into legacy range */if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)dev_no -= EVDEV_MINOR_BASE;dev_set_name(&evdev->dev, "event%d", dev_no);evdev->handle.dev = input_get_device(dev);记录保存传进来的参数devevdev->handle.name = dev_name(&evdev->dev);   evdev->handle.handler = handler;    记录保存传进来的handler函数这就是非常有趣的一点,我们把evdev中的一个成员重新指向evdevevdev->handle.private = evdev;如下代码的作用是创建文件节点,类似于device_create( );我们把传进来的dev赋值给evdev->dev,然后对其进行初始化,创建文件节点evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);evdev->dev.class = &input_class;evdev->dev.parent = &dev->dev;evdev->dev.release = evdev_free;device_initialize(&evdev->dev);调用input_register_handle函数,将&evdev->handle作为传递参数,而这个handle包含着我们传给connect函数的handler和device跳转到下方来看这个函数error = input_register_handle(&evdev->handle);总结一下input_register_handle这个函数就是将handle中的handler和dev加入handle中的两个不同的链表中这样如此复杂的操作是为了什么呢?做一个很形象的比喻,handler和dev可以分别视为父亲和母亲,而evdev则可以看作是儿子,我们可以把handler和dev重新加入到evdev中的handle字段下的两个链表中所以我们就可以通过儿子来找到父亲和母亲,而父亲和母亲也可以然后初始化cdev,并创建fopscdev_init(&evdev->cdev, &evdev_fops);evdev->cdev.kobj.parent = &evdev->dev.kobj;error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);error = device_add(&evdev->dev);
}

我们可以加入device_create进行对比 device_create_groups_vargs(struct class *class, struct device *parent,dev_t devt, void *drvdata,const struct attribute_group **groups,const char *fmt, va_list args)
{struct device *dev = NULL;int retval = -ENODEV;dev = kzalloc(sizeof(*dev), GFP_KERNEL);device_initialize(dev);dev->devt = devt;dev->class = class;dev->parent = parent;dev->groups = groups;dev->release = device_create_release;dev_set_drvdata(dev, drvdata);retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
int input_register_handle(struct input_handle *handle)
{   首先我们将传递进来的handle中的handler赋值给input_handler,感觉就像是之前的操作是将handler和dev封装在一个结构体,如今又把他们拿出来struct input_handler *handler = handle->handler;struct input_dev *dev = handle->dev;int error;同样由于涉及到全局链表的操作,所以需要获得互斥锁error = mutex_lock_interruptible(&dev->mutex);将dev加入到handle中的d_node链表上if (handler->filter)list_add_rcu(&handle->d_node, &dev->h_list);elselist_add_tail_rcu(&handle->d_node, &dev->h_list);mutex_unlock(&dev->mutex);将handler加入到h_node链表上list_add_tail_rcu(&handle->h_node, &handler->h_list);if (handler->start)

evdev_open

static int evdev_open(struct inode *inode, struct file *file)
{   我们需要根据inode中的i_cdev字段找到同时包含这个cdev的evdev,cdev正是我们在evdev_connect中注册的那个struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);接下来我们要创建一个缓冲区,如下则是在计算缓冲区的大小unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);unsigned int size = sizeof(struct evdev_client) +bufsize * sizeof(struct input_event);struct evdev_client *client;int error;为缓冲区client申请内存空间client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);client->bufsize = bufsize;spin_lock_init(&client->buffer_lock);client->evdev = evdev;将evdev和client进行双向联系起来evdev_attach_client(evdev, client);error = evdev_open_device(evdev);if (error)goto err_free_client;将client保存在file->private_data中file->private_data = client;nonseekable_open(inode, file);return 0;}

我们先思考一下client中有什么?我们直接从整个代码的逻辑来分析
client中有一个缓冲区,应该是和后面的文件读写相关的
client中还有一个evdev,而这个evdev是我们根据evdev_connect函数中注册的cdev通过container_of出来的
为什么要这样做呢?因为inode和evdev共用一个cdev(猜测)
所以这个evdev包含了有device信息、handler信息还有cdev中的fops信息
evdev中的handle字段包含device和handler信息
而dev字段则是包含设备节点的信息(device_create)
然后我们把client保存在file->private_data中,这可以对该文件指针进行操作的函数可以共用client

evdev_readstatic ssize_t evdev_read(struct file *file, char __user *buffer,size_t count, loff_t *ppos)
{从file->private_data中取出我们在open中保存的数据struct evdev_client *client = file->private_data;struct evdev *evdev = client->evdev;而input_event是我们将要上传给用户的数据包struct input_event event;read在这里作用类似于file positionsize_t read = 0;int error;if (count != 0 && count < input_event_size())return -EINVAL;for (;;) {if (!evdev->exist || client->revoked)return -ENODEV;如果没有数据,同时是以非阻塞的方式打开,那么我们就直接返回错误为了更好读代码,我们在这里假设没有数据if (client->packet_head == client->tail &&(file->f_flags & O_NONBLOCK))return -EAGAIN;if (count == 0)break;如果我们要传给用户的数据小于count且我们可以从client的buffer中得到数据给event,那我们将执行input_event_to_userinput_event_to_user即为把event中的数据复制给用户空间的buffer有趣的是,这里是复制给buffer + read为起始地址的空间同时对read的值进行更新但是我们已经在这里假设没有数据,所以这个循环的测试条件不能成立,程序将执行下一个部分while (read + input_event_size() <= count &&evdev_fetch_next_event(client, &event)) {if (input_event_to_user(buffer + read, &event))return -EFAULT;read += input_event_size();}因为我们假设的条件下,read依然为0,所以循环不会break出去if (read)break;当我们假设我们打开文件的方式不是非阻塞,那就是以阻塞的方式打开那 !(file->flags & O_NONBLOCK)为真,打开方式为阻塞我们走到这里,就已经证明当前client中的buffer没有数据,我们可以看到程序将会调用 wait_event_interruptible进行阻塞等待等待队列的测试条件是client->packet_head != client->tail !! !evdev->exist || client->revokedif (!(file->f_flags & O_NONBLOCK)) {error = wait_event_interruptible(evdev->wait,client->packet_head != client->tail ||!evdev->exist || client->revoked);if (error)return error;}}return read;
}
static int evdev_fetch_next_event(struct evdev_client *client,struct input_event *event)
{int have_event;spin_lock_irq(&client->buffer_lock);have_event = client->packet_head != client->tail;if (have_event) {我们把client中缓冲区的数据传递给event*event = client->buffer[client->tail++];client->tail &= client->bufsize - 1;}spin_unlock_irq(&client->buffer_lock);return have_event;
}

从evdev_read函数中我们可以看到它是如何把client中的buffer数据传递给用户
复制给input_event,然后input_event_to_user给用户空间的缓冲区,我们可以从中理解了为何在之前的驱动中,当我们执行input_report_key(inputdev, pdesc->key_code, 0)
后为什么要执行input_sync(inputdev),因为我们需要唤醒等待队列
而input_report_key和input_sync都需要调用input_event

mpu6050驱动

#include <linux/init.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define SMPLRT_DIV 0x19 //閲囨牱棰戠巼瀵勫瓨鍣紝瀵勫瓨鍣ㄩ泦鍚堥噷鐨勬暟鎹牴鎹噰鏍烽鐜囨洿鏂?鍏稿瀷鍊?x07(125kHz)
#define CONFIG 0x1A //閰嶇疆瀵勫瓨鍣?鍏稿瀷鍊?x06(5Hz)
#define GYRO_CONFIG     0x1B//闄€铻轰华閰嶇疆-27,鍙互閰嶇疆鑷鍜屾弧閲忕▼鑼冨洿
//鍏稿瀷鍊硷細0x18(涓嶈嚜妫€锛?000deg/s)
#define ACCEL_CONFIG        0x1C    //鍔犻€熷害閰嶇疆-28 鍙互閰嶇疆鑷鍜屾弧閲忕▼鑼冨洿鍙婇珮閫氭护娉㈤鐜?
//鍏稿瀷鍊硷細0x01(涓嶈嚜妫€锛?G锛?Hz)
#define ACCEL_XOUT_H    0x3B //59-65,鍔犻€熷害璁℃祴閲忓€?XOUT_H
#define ACCEL_XOUT_L    0x3C  // XOUT_L
#define ACCEL_YOUT_H    0x3D  //YOUT_H
#define ACCEL_YOUT_L    0x3E  //YOUT_L
#define ACCEL_ZOUT_H    0x3F  //ZOUT_H
#define ACCEL_ZOUT_L    0x40 //ZOUT_L---64
#define TEMP_OUT_H      0x41 //娓╁害娴嬮噺鍊?-65
#define TEMP_OUT_L      0x42
#define GYRO_XOUT_H     0x43 //闄€铻轰华鍊?-67锛岄噰鏍烽鐜囷紙鐢卞瘎瀛樺櫒 25 瀹氫箟锛夊啓鍏ュ埌杩欎簺瀵勫瓨鍣?
#define GYRO_XOUT_L     0x44
#define GYRO_YOUT_H     0x45
#define GYRO_YOUT_L     0x46
#define GYRO_ZOUT_H     0x47
#define GYRO_ZOUT_L     0x48 //闄€铻轰华鍊?-72
#define PWR_MGMT_1      0x6B //鐢垫簮绠$悊 鍏稿瀷鍊硷細0x00(姝e父鍚敤)#define INT_ENABLE        0x38 //
#define INT_PIN_CFG     0x37struct mpu_sensor {int dev_major;struct i2c_client *client;
};struct mpu_sensor *mpu_dev;
struct input_dev *inputdev;static struct work_struct mywork; int mpu6050_write_bytes(struct i2c_client *client,char *buf,int count)
{int ret;struct i2c_adapter * adapter=client->adapter;struct i2c_msg  msg;msg.addr=client->addr;msg.flags=0;msg.len=count;msg.buf=buf;ret=i2c_transfer(adapter,&msg,1);return ret==1?count:ret;
}//璇诲彇鏌愪釜鐗瑰畾瀵勫瓨鍣ㄧ殑鍦板潃
int mpu6050_read_reg_byte(struct i2c_client *client,char reg)
{int ret;char rxbuf[1];struct i2c_adapter *adapter=client->adapter;struct i2c_msg msg[2];msg[0].addr=client->addr;msg[0].flags=0;msg[0].len=1;msg[0].buf=&reg;msg[1].addr=client->addr;msg[1].flags=I2C_M_RD;msg[1].len=1;msg[1].buf=rxbuf;ret=i2c_transfer(adapter,msg,2);if(ret<0){printk("i2c_transfer read error\n");return ret;}return rxbuf[0];
}static irqreturn_t mpu6050_irq(int irq, void *dev_instance)
{schedule_work(&mywork);return IRQ_HANDLED;
}void work_mpu6050_half(struct work_struct *work)
{short x = mpu6050_read_reg_byte(mpu_dev->client, ACCEL_XOUT_H) << 8 | \mpu6050_read_reg_byte(mpu_dev->client, ACCEL_XOUT_L);short y = mpu6050_read_reg_byte(mpu_dev->client, ACCEL_YOUT_H) << 8 | \mpu6050_read_reg_byte(mpu_dev->client, ACCEL_YOUT_L);short z = mpu6050_read_reg_byte(mpu_dev->client, ACCEL_ZOUT_H) << 8 | \mpu6050_read_reg_byte(mpu_dev->client, ACCEL_ZOUT_L);input_report_abs(inputdev, ABS_RX, x);input_report_abs(inputdev, ABS_RY, y);input_report_abs(inputdev, ABS_RZ, z);input_sync(inputdev);}int mpu6050_drv_probe(struct i2c_client *client, const struct i2c_device_id *id)
{char buf1[2]={PWR_MGMT_1,0x0};char buf2[2]={SMPLRT_DIV,0x07};char buf3[2]={CONFIG,0x06};char buf4[2]={GYRO_CONFIG,0x18};char buf5[2]={ACCEL_CONFIG,0x01};char buf6[2]={INT_ENABLE,0x01};char buf7[2]={INT_PIN_CFG,0x9c};int retval = 0;memset(&mywork, 0, sizeof(&mywork));INIT_WORK(&mywork, work_mpu6050_half);mpu_dev = kzalloc(sizeof(struct mpu_sensor),GFP_KERNEL);mpu_dev->client = client;mpu6050_write_bytes(mpu_dev->client, buf1, 2);mpu6050_write_bytes(mpu_dev->client, buf2, 2);mpu6050_write_bytes(mpu_dev->client, buf3, 2);mpu6050_write_bytes(mpu_dev->client, buf4, 2);mpu6050_write_bytes(mpu_dev->client, buf5, 2);mpu6050_write_bytes(mpu_dev->client, buf6, 2);mpu6050_write_bytes(mpu_dev->client, buf7, 2);inputdev = input_allocate_device();if(inputdev == NULL){printk(KERN_ERR "input_allocatre_device error\n");kfree(mpu_dev);return -ENOMEM;}// /sys/class/input/event*/device/inputdev->name="simple input key";inputdev->phys="key/input/input0";inputdev->uniq="simple key0 for 4412";inputdev->id.bustype=BUS_HOST;inputdev->id.vendor=0x1234;inputdev->id.product=0x8888;inputdev->id.version=0x0001;__set_bit(EV_ABS, inputdev->evbit);input_set_abs_params(inputdev, ABS_RX, 0, 100, 0, 0);input_set_abs_params(inputdev, ABS_RY, 0, 100, 0, 0);input_set_abs_params(inputdev, ABS_RZ, 0, 100, 0, 0);retval = input_register_device(inputdev);if(retval != 0) {printk(KERN_ERR "input_allocatre_device error\n");input_free_device(inputdev);kfree(mpu_dev);return retval;}retval = request_irq(client->irq, mpu6050_irq,IRQF_TRIGGER_RISING,client->dev.driver->name, NULL);printk("---%d ----\n", client->irq);if(retval) {input_unregister_device(inputdev);input_free_device(inputdev);kfree(mpu_dev);return retval;}return 0;
}int mpu6050_drv_remove(struct i2c_client *client)
{printk("%s %d \n", __func__, __LINE__);free_irq(client->irq, NULL);printk("%s %d \n", __func__, __LINE__);input_unregister_device(inputdev);printk("%s %d \n", __func__, __LINE__);kfree(mpu_dev);return 0;
}const struct of_device_id of_mpu6050_id[]={{ .compatible="invensense,mpu6050", },{/*northing to be done*/},
};const struct i2c_device_id mpu_id_table[]={{"mpu6050_drv",0x1100},{/*northing to be done*/},
};struct i2c_driver mpu6050_drv={.probe=mpu6050_drv_probe,.remove=mpu6050_drv_remove,.driver={.name="mpu6050_drv",.of_match_table=of_match_ptr(of_mpu6050_id),},.id_table=mpu_id_table,
};static int __init mpu6050_drv_init(void)
{return i2c_add_driver(&mpu6050_drv);;
}static void __exit mpu6050_drv_exit(void)
{i2c_del_driver(&mpu6050_drv);
}module_init(mpu6050_drv_init);
module_exit(mpu6050_drv_exit);
MODULE_LICENSE("GPL");

输入子系统代码内核代码分析相关推荐

  1. linux input输入子系统分析《一》:初识input输入子系统

    主要讲述本人在学习Linux内核input子系统的全部过程,如有分析不当,多谢指正.以下交流方式,文章欢迎转载,保留联系信息,以便交流. 邮箱:eabi010@gmail.com 主页:www.iel ...

  2. 12.Linux之输入子系统分析(详解)

    在此节之前,我们学的都是简单的字符驱动,涉及的内容有字符驱动的框架.自动创建设备节点.linux中断.poll机制.异步通知.同步互斥/非阻塞.定时器去抖动. 其中驱动框架如下: 1)写file_op ...

  3. 【OS学习笔记】二十八 保护模式八:任务切换对应的汇编代码之内核代码

    本汇编代码对应以下两篇文章对应的内核汇编代码: OS学习笔记]二十六 保护模式八:任务门-任务切换 [OS学习笔记]二十七 保护模式八:任务切换的方法之----jmp与call的区别以及任务的中断嵌套 ...

  4. Linux输入子系统简析

    1. 前言 限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺. 2. 背景 本文基于 Linux 4.14 内核源码进行分析. 3. 简介 Linux 内核输入子系统,负责 ...

  5. Linux内核分析:完成一个简单的时间片轮转多道程序内核代码

    PS.贺邦   原创作品转载请注明出处  <Linux内核分析>MOOC课程    http://mooc.study.163.com/course/USTC-1000029000 1.m ...

  6. linux实验:基于mykernel的一个简单的时间片轮转多道程序内核代码分析

    学号后三位:288 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ 1.mykernel mykernel是由中科大孟宁老师建立的用于开发 ...

  7. Linux内核分析2:一个简单的时间片轮转多道程序内核代码分析

    Lab2:一个简单的时间片轮转多道程序内核代码 席金玉   <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100002900 ...

  8. 7.Linux 输入子系统分析

    为什么要引入输入子系统? 在前面我们写了一些简单的字符设备的驱动程序,我们是怎么样打开一个设备并操作的呢? 一般都是在执行应用程序时,open一个特定的设备文件,如:/dev/buttons 1 .. ...

  9. [arm 驱动]Linux输入子系统分析

    首先说明一下,本文是基于Linux-2.6.38版本内核来分析Linux输入子系统架构和原理的.这阵子本来没有打算花时间来分析Linux input system的,然而当在研究S3C6410触摸屏驱 ...

最新文章

  1. php 接收多图片base64
  2. 动软Model 模板 生成可空类型字段
  3. vue xunidom_vue的虚拟dom(Virtual DOM )
  4. 莫兰迪色rgb颜色表_企业风险评价,(SCL)安全检查表,Excel工作表
  5. 简单说明c语言程序步骤,C语言的入门简介和三个简单的C语言程序详细说明
  6. mac安装win10_mac制作win10启动盘教程
  7. 又一所“国字头”大学要来?屠呦呦也在
  8. 构建之法第三章学习小记
  9. html5 刷子,简单聊聊眼部刷子吧(打底刷、上色刷、晕染刷)
  10. Struts,Hibernate,Spring经典面试题收藏(转)
  11. 排序算法-快速排序(入门)
  12. LoadRunner测试Google Suggest
  13. 微信公众号开发 ----微信获取access_token(2)
  14. 实习成长之路——设计模式实战二:如何使用面向对象的思想设计一个功能需求?接口鉴权怎么用面向对象分析实现?
  15. python实现取出一个列表或者多个列表中的公共前缀
  16. paip.spring 获取bean getBean 没有beanid的情况下
  17. 用户需求管理 - KANO模型
  18. Python - 经典程序示例
  19. 【译】迁移被废弃的Kotlin Android Extensions插件
  20. 设计模式第一篇——简单工厂模式&类图

热门文章

  1. 思科关闭日志_关于思科交换机的日志配置总结
  2. 微服务团队_为什么团队文化对于成功的微服务至关重要
  3. 瑞萨RH850/F1L-片上资源分配(Flash,RAM,外设资源)
  4. 【图像】搜索相同,或者相似照片
  5. 【自动化测试】想了解Selenium吗?看这里!
  6. HTML点击图片实现提交或跳转链接页面
  7. 深入分析零拷贝的原理,彻底掌握Netty、Kafka、RocketMQ高效率读写的秘诀
  8. 淘宝天猫商家运营,宝贝标题如何突出卖点,标题编写方法小技巧
  9. AI视觉传感器作用和应用介绍
  10. WEB页面播放大华摄像头视频解决方案