输入子系统由驱动层、输入子系统核心、事件处理层三部分组成。一个输入事件,如鼠标移动、键盘按下等通过Driver->Inputcore->Event handler->userspace的顺序到达用户控件的应用程序。

                系统框图

假设打开一个字符设备驱动程序/dev/event0,event代表的是输入子系统的设备文件,当应用程序调用C库的open函数后,open函数会进入系统调用,最后定位到drivers\input\input.c文件下(这个文件就是核心层)的。这个函数的功能主要是根据设备的次设备号找到新的fops结构,然后切换到新的fops结构,然后调用它的打开函数。输入子系统的主设备号恒为#define INPUT_MAJOR 13,定义在include\linux\major.h中。

static int input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler = input_table[iminor(inode) >> ];//根据次设备号找到在input_table表中找到handler结构体
const struct file_operations *old_fops, *new_fops = NULL;
int err; /* No load-on-demand here? */
if (!handler || !(new_fops = fops_get(handler->fops)))//判断handler结构体是否存在,存在的话将里面的fops变量赋给new_fops
return -ENODEV; /*
* That's _really_ odd. Usually NULL ->open means "nothing special",
* not "no device". Oh, well...
*/
if (!new_fops->open) {
fops_put(new_fops);
return -ENODEV;
}
old_fops = file->f_op;
file->f_op = new_fops;//切换f_op变量,以后调用诸如read、write等系统调用时会进入到new_fops的read、write函数 err = new_fops->open(inode, file);//调用new_fops的open函数 if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);//释放掉老的fops结构
return err;
}

接着先来看到input_table表的建立,可以看到它是一个静态变量,在本文件(drivers\input\input.c)中搜索它,可以看到它位于input_register_handler函数,这是一个全局的函数,可以供外部的文件调用,这个函数的主要功能是注册一个handler结构体,这个结构体中存在minor这个设备的次设备号,这个结构所在的函数对应的其实就是上述的事件层

int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev; INIT_LIST_HEAD(&handler->h_list);//初始化handler的h_list结构体,这是一个双向链表 if (handler->fops != NULL) {
if (input_table[handler->minor >> ])//检查是否已经存在这个次设备号的handler结构
return -EBUSY; input_table[handler->minor >> ] = handler;//将handler结构次设备号放入input_table表
} list_add_tail(&handler->node, &input_handler_list);//将handler结构根据node成员放入input_handler_list链表 list_for_each_entry(dev, &input_dev_list, node)//根据node这个成员在input_dev_list链表中循环查找dev结构
input_attach_handler(dev, handler);//对于每一个dev结构调用input_attach_handler函数 input_wakeup_procfs_readers();//将这个设备信息写入proc文件系统
return ;
}

接着搜索input_register_handler,抽取drivers\input\evdev.c这个文件,可以看到在这个模块的入口函数调用了注册函数

static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}

接着看到evdev_handler这个结构体,在这个结构体里面找到了evdev_fops这个结构

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

接着看到evdev_fops结构体,可以看到应用层调用的read、write等函数在这里被定义

static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush
};

知道了事件层对应的位置,那么设备驱动层在哪里呢?接着往下看,回到input_register_handler函数,在里面看到如下语句,这句语句的作用是将事件层与驱动层联系起来。

list_for_each_entry(dev, &input_dev_list, node)//根据node这个成员在input_dev_list链表中循环查找dev结构
input_attach_handler(dev, handler);//对于每一个dev结构调用input_attach_handler函数

这里可以看到一个新的结构体dev,先看一下dev结构体,它的原型为input_dev,跟抽取drivers\input\evdev.c这个文件一样,搜索input_dev这个结构体,先列出input_dev这个结构体

struct input_dev {

    void *private;

    const char *name;
const char *phys;
const char *uniq;
struct input_id id; unsigned long evbit[NBITS(EV_MAX)];
unsigned long keybit[NBITS(KEY_MAX)];
unsigned long relbit[NBITS(REL_MAX)];
unsigned long absbit[NBITS(ABS_MAX)];
unsigned long mscbit[NBITS(MSC_MAX)];
unsigned long ledbit[NBITS(LED_MAX)];
unsigned long sndbit[NBITS(SND_MAX)];
unsigned long ffbit[NBITS(FF_MAX)];
unsigned long swbit[NBITS(SW_MAX)]; unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;
int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);
int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode); struct ff_device *ff; unsigned int repeat_key;
struct timer_list timer; int state; int sync; int abs[ABS_MAX + ];
int rep[REP_MAX + ]; unsigned long key[NBITS(KEY_MAX)];
unsigned long led[NBITS(LED_MAX)];
unsigned long snd[NBITS(SND_MAX)];
unsigned long sw[NBITS(SW_MAX)]; int absmax[ABS_MAX + ];
int absmin[ABS_MAX + ];
int absfuzz[ABS_MAX + ];
int absflat[ABS_MAX + ]; 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 *grab; struct mutex mutex; /* serializes open and close operations */
unsigned int users; struct class_device cdev;
union { /* temporarily so while we switching to struct device */
struct device *parent;
} dev; struct list_head h_list;
struct list_head node;
};

接着看到drivers\input\tablet\kbtab.c这个文件,这个文件代表的就是设备驱动层,简单分析一下,可以看到它也是一个内核的模块,可以动态加载,一旦加载后,它会调用kbtab_init函数,最终会调用到kbtab_probe这个函数,可以看到最终又定位到了input_register_device这个注册设备的函数,它位于核心层,即drivers\input\input.c文件下。

static int kbtab_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
...
...
input_dev = input_allocate_device();//分配一个input_dev 结构体
if (!kbtab || !input_dev)
goto fail1; ...
... input_dev->name = "KB Gear Tablet";//初始化input_dev 结构体
input_dev->phys = kbtab->phys;
usb_to_input_id(dev, &input_dev->id);
input_dev->dev.parent = &intf->dev; input_set_drvdata(input_dev, kbtab); input_dev->open = kbtab_open;
input_dev->close = kbtab_close; input_dev->evbit[] |= BIT(EV_KEY) | BIT(EV_ABS) | BIT(EV_MSC);
input_dev->keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE);
input_dev->keybit[LONG(BTN_DIGI)] |= BIT(BTN_TOOL_PEN) | BIT(BTN_TOUCH);
input_dev->mscbit[] |= BIT(MSC_SERIAL);
input_set_abs_params(input_dev, ABS_X, , 0x2000, , );
input_set_abs_params(input_dev, ABS_Y, , 0x1750, , );
input_set_abs_params(input_dev, ABS_PRESSURE, , 0xff, , ); ...
... error = input_register_device(kbtab->dev);//注册input_dev结构体
...
...
}

接着看到input_register_device这个函数,它根input_register_handler相对应,前一个注册设备驱动层,后一个注册事件层。列出input_register_device函数,它同样位于drivers\input\input.c文件中。

int input_register_device(struct input_dev *dev)
{
static atomic_t input_no = ATOMIC_INIT();
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] = ;
dev->rep[REP_PERIOD] = ;
} if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;//获得按键值默认函数 if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;//设置按键值默认函数 list_add_tail(&dev->node, &input_dev_list);//将dev->node放入input_dev_list链表 snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),
"input%ld", (unsigned long) atomic_inc_return(&input_no) - ); 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)//根据node这个成员在input_handler_list链表中循环查找handler结构
input_attach_handler(dev, handler);//对于每一个handler结构调用input_attach_handler函数 input_wakeup_procfs_readers();//将这个设备信息写入proc文件系统 return ;
}

可以看到它同样也调用了input_attach_handler函数,将设备驱动层事件层联系起来。这个函数也位于drivers\input\input.c文件中。它的主要功能是

1、根据handler->id_table的值匹配dev,找到id

2、调用调用handler->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);//根据handler->id_table的值匹配dev,找到id
if (!id)
return -ENODEV; error = handler->connect(handler, dev, id);//调用handler->connect进行匹配
if (error && error != -ENODEV)
printk(KERN_ERR
"input: failed to attach handler %s to device %s, "
"error: %d\n",
handler->name, kobject_name(&dev->cdev.kobj), error); return error;
}

接着看到input_match_device函数

static const struct input_device_id *input_match_device(const struct input_device_id *id,
struct input_dev *dev)
{
int i; for (; id->flags || id->driver_info; id++) {//循环查找支持的id if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
continue; if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
continue; if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
continue; MATCH_BIT(evbit, EV_MAX);//
MATCH_BIT(keybit, KEY_MAX);
MATCH_BIT(relbit, REL_MAX);
MATCH_BIT(absbit, ABS_MAX);
MATCH_BIT(mscbit, MSC_MAX);
MATCH_BIT(ledbit, LED_MAX);
MATCH_BIT(sndbit, SND_MAX);
MATCH_BIT(ffbit, FF_MAX);
MATCH_BIT(swbit, SW_MAX); return id;
} return NULL;

再看到handler->connect函数,这里选取的是evdev_handler ->evdev_connect函数,这个函数的主要作用就是将handle、handler、evdev三者相互匹配起来

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; for (minor = ; minor < EVDEV_MINORS && evdev_table[minor]; minor++);//取得次设备号,如果还没有利用则evdev_table为空
if (minor == EVDEV_MINORS) {
printk(KERN_ERR "evdev: no more free evdev devices\n");//没有剩余的空间可以用了
return -ENFILE;
} evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);//分配一个evdev
if (!evdev)
return -ENOMEM; INIT_LIST_HEAD(&evdev->client_list);
init_waitqueue_head(&evdev->wait); evdev->exist = ; //evdev初始化
evdev->minor = minor;
evdev->handle.dev = dev; //初始化evdev->handle.dev
evdev->handle.name = evdev->name;
evdev->handle.handler = handler; //初始化evdev->handle.handler
evdev->handle.private = evdev;
sprintf(evdev->name, "event%d", minor);//打印次设备号,每次注册新的设备驱动都会打印 evdev_table[minor] = evdev;//将分配的evdev放入evdev_table[minor] 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; /*
llist_add_tail(&handle->d_node, &handle->dev->h_list);//将&handle->d_node 放入&handle->dev->h_list链表
list_add_tail(&handle->h_node, &handler->h_list); //将&handle->h_node 放入 &handler->h_list链表?
*/
error = input_register_handle(&evdev->handle);//注册evdev->handle
if (error)
goto err_remove_link; return ; err_remove_link:
sysfs_remove_link(&input_class.subsys.kobj, evdev->name);
err_cdev_destroy:
class_device_destroy(&input_class, devt);
err_free_evdev:
kfree(evdev);
evdev_table[minor] = NULL;
return error;
}

再回过头来看一下应用层是怎么读取按键值得:应用层调用C库的read函数,通过前面的分析可以知道,最终会通过系统调用会定位到内核的evdev_handler ->fops ->evdev_read,下面看到evdev_read函数,它位于事件层,这个函数根据读取方式的不同采取不同的方式,如果是阻塞方式打开的话最终会当前进程放入等待队列,一直等到有数据才将进程唤醒。

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 + ) & (EVDEV_BUFFER_SIZE - );
retval += evdev_event_size();
} return retval;
}

接下来的问题就是谁将进程唤醒,我们直接看到设备驱动层,即drivers\input\tablet\kbtab.c,在这个文件中有一个kbtab_irq函数,它是一个中断处理函数,它位于设备驱动层,负责将中断过来的按键数据上报调用的是input_report_key函数,input_report_key函数最终调用的是input_event函数,他们全部都属于核心层。接着看一下input_event的核心代码

list_for_each_entry(handle, &dev->h_list, d_node)//根据dev设备驱动层的h_list找出handle结构体
if (handle->open)
handle->handler->event(handle, type, code, value);//调用事件层的handle->handler->event进行处理

再回过头看事件层的event,即evdev_event函数,可以看到在这个函数里将按键的相关的值取出后,最终进程的唤醒函数在这里调用wake_up_interruptible。

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 + ) & (EVDEV_BUFFER_SIZE - ); kill_fasync(&client->fasync, SIGIO, POLL_IN);
} else
list_for_each_entry(client, &evdev->client_list, node) { do_gettimeofday(&client->buffer[client->head].time);//时间 8字节
client->buffer[client->head].type = type; //按键类型 2字节
client->buffer[client->head].code = code; //按键码 2字节
client->buffer[client->head].value = value; //按键值,按下或松开 4字节
client->head = (client->head + ) & (EVDEV_BUFFER_SIZE - ); kill_fasync(&client->fasync, SIGIO, POLL_IN);//异步通知
} wake_up_interruptible(&evdev->wait);//唤醒进程
}

总结一下整个输入子系统的调用过程:

app_open->input_open_file->evdev_open

应用层         核心层                    事件层

app_read->evdev_read->kbtab_irq->input_report_key->input_event->evdev_event->evdev_read

应用层           事件层          设备层         核心层                   核心层            事件层          事件层

如果要自己添加一个输入子系统的设备,只需要添加设备层的文件即可。

1、在里面添加设备层input_dev结构并初始化

2、编写中断处理程序

Linux驱动之输入子系统简析的更多相关文章

  1. Linux驱动之中断处理体系结构简析

    S3C2440中的中断处理最终是通过IRQ实现的,在Linux驱动之异常处理体系结构简析已经介绍了IRQ异常的处理过程,最终分析到了一个C函数asm_do_IRQ,接下来继续分析asm_do_IRQ, ...

  2. linux驱动模型<输入子系统>

    在linux中提供一种输入子系统的驱动模型,其主要是实现在input.c中. 在输入子系统这套模型中,他把驱动分层分类.首先分为上下两层,上层为input.c .下层为驱动的实现,下层分为两部分,一部 ...

  3. Linux驱动之异常处理体系结构简析

    异常的概念在单片机中也接触过,它的意思是让CPU可以暂停当前的事情,跳到异常处理程序去执行.以前写单片机裸机程序属于前后台程序,前台指的就是mian函数里的while(1)大循环,后台指的就是产生异常 ...

  4. Linux网络性能优化方法简析

    Linux网络性能优化方法简析 2010-12-20 10:56 赵军 IBMDW 字号:T | T 性能问题永远是永恒的主题之一,而Linux在网络性能方面的优势则显而易见,这篇文章是对于Linux ...

  5. Linux 目录结构学习与简析 Part2

    linux目录结构学习与简析 by:授客 QQ:1033553122 ---------------接Part 1-------------- #1.查看CPU信息 #cat /proc/cpuinf ...

  6. Linux 目录结构学习与简析 Part1

    linux目录结构学习与简析 by:授客 QQ:1033553122 说明: /             linux系统目录树的起点 =============== /bin      User Bi ...

  7. Linux 驱动框架---input子系统框架

    前面从具体(Linux 驱动框架---input子系统)的工作过程学习了Linux的input子系统相关的架构知识,但是前面的学习比较实际缺少总结,所以今天就来总结一下输入子系统的架构分层,站到远处来 ...

  8. Linux 驱动框架---input子系统

    input 子系统也是作为内核的一个字符设备模块存在的,所以他也是字符设备自然也会有字符设备的文件接口.input子系统的注册过程主要分为两步,先注册了一个input class然后再注册一个字符设备 ...

  9. ARM Linux内核Input输入子系统浅解

    --以触摸屏驱动为例 第一章.了解linux input子系统         Linux输入设备总类繁杂,常见的包括有按键.键盘.触摸屏.鼠标.摇杆等等,他们本身就是字符设备,而linux内核将这些 ...

随机推荐

  1. webpack 中,importloaders 配置项的含义

    importLoaders:用于配置「css-loader 作用于 @import 的资源之前」有多少个 loader. 0 => no loaders (default); 1 => p ...

  2. SVN 撤回已提交的代码

    1. TortoiseSVN -----> Show log 2.右键点击你想撤回的提交 -> Revert changes from this revision ----->Rev ...

  3. 【C++】 多态的实现和原理

    本文转自 https://www.cnblogs.com/cxq0017/p/6074247.html 安利一篇blog,https://blog.csdn.net/u013982161/articl ...

  4. 学习vue容易忽视的细节

    1.对于自定义标签名(组件名称),Vue.js 不强制要求遵循 W3C 规则 (小写,并且包含一个短杠),尽管遵循这个规则比较好.HTML 特性是不区分大小写的.所以,当使用的不是字符串模板,came ...

  5. 小程序客服下发消息禁止后 session from 还有用吗?

    文章概要 1. 小程序下发政策调整分析 2. session from 数据还传到底三方了没?  1. 小程序下发政策调整分析 小程序客服功能下发策略调整                       ...

  6. 洛谷P1605走迷宫

    传送 这是一道dfs,但是...但是....但是它竟然被放在bfs练习题辣!!!! 打了半天bfs,发现路径不会标记了,于是发现好像有什么不对的,似乎dfs要简单一点,于是半路跑去打dfs,结果打了半 ...

  7. centos7-网络连接

    Centos系统在安装完毕后,默认联网状态为no,需要手动开启联网状态. 编辑网卡文件 vim /etc/sysconfig/network-scripts/ifcfg-eno16777736 修改O ...

  8. TypeScript 模块系统

    https://www.cnblogs.com/niklai/p/5808789.html

  9. Java IO浅析

    1.File类 /** * * @author lenovo * * File类 * pathSeparator * separator * * File() * boolean createNewF ...

  10. JAVA使用log4j(另SSM框架中使用log4j)

    1.引入jar包 log4j-1.2.13.jar 2.src下建立配置文件:log4j.properties #不+All,只写后一种LOG log4j.rootLogger =ALL,system ...