您的位置:首页 > 运维架构 > Linux

学习笔记 --- LINUX输入子系统分析

2014-03-10 18:34 447 查看
输入子系统入口:

static int __init input_init(void)
{
int err;

input_init_abs_bypass();

err = class_register(&input_class);
if (err) {
printk(KERN_ERR "input: unable to register input_dev class\n");
return err;
}

err = input_proc_init();
if (err)
goto fail1;

err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
if (err) {
printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
goto fail2;
}

return 0;

fail2:	input_proc_exit();
fail1:	class_unregister(&input_class);
return err;
}


这里注册输入子系统为字符设备,操作函数体:

static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
可以看出,只有一个open函数,再看这个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;
int err;

lock_kernel();
/* No load-on-demand here? */
handler = input_table[iminor(inode) >> 5];  //这里获取到了一个包含新的fops结构的handler
if (!handler || !(new_fops = fops_get(handler->fops))) {   //获取新的fops结构
err = -ENODEV;
goto out;
}

/*
* That's _really_ odd. Usually NULL ->open means "nothing special",
* not "no device". Oh, well...
*/
if (!new_fops->open) {
fops_put(new_fops);
err = -ENODEV;
goto out;
}
old_fops = file->f_op;
file->f_op = new_fops;   //这里用新的fops结构替代了以前的只有open的那个结构

err = new_fops->open(inode, file); //调用新的结构里面的open函数

if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
out:
unlock_kernel();
return err;
}


所以子系统的实际处理函数是在这个新拿到的fops结构体里面的函数实现的;

那么handler_table里面放的什么?通过下面这个函数放进去的:

/**
* input_register_handler - register a new input handler
* @handler: handler to be registered
*
* This function registers a new input handler (interface) for input
* devices in the system and attaches it to all input devices that
* are compatible with the handler.
*/
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int retval;

retval = mutex_lock_interruptible(&input_mutex);
if (retval)
return retval;

INIT_LIST_HEAD(&handler->h_list);

if (handler->fops != NULL) {
if (input_table[handler->minor >> 5]) {
retval = -EBUSY;
goto out;
}
input_table[handler->minor >> 5] = handler;  //注册的handler放入
}

list_add_tail(&handler->node, &input_handler_list);  //新注册的handler入链表input_handler_list
list_for_each_entry(dev, &input_dev_list, node)   //所有input_dev_list里面已注册的的dev读拿出来和这个handler匹配一次,看是否匹配上
input_attach_handler(dev, handler);  //此函数里面判断匹配成功,成功则调用connect

input_wakeup_procfs_readers();

out:
mutex_unlock(&input_mutex);
return retval;
}


搜索这个input_register_handler函数,可以知道有鼠标驱动,触摸屏驱动,按键驱动,evdev,Joy都调用了,所以不同的驱动对应自己的handler;
看下evdev驱动:

input_register_handler(&kbd_handler)

所以之前新的fops就是evdev_handler里面的fops,evdev_handler是什么?

static struct input_handler evdev_handler = {
.event		= evdev_event,
.connect	= evdev_connect,    //如果有支持的device调用这个建立连接
.disconnect	= evdev_disconnect,
.fops		= &evdev_fops,     //新的fops
.minor		= EVDEV_MINOR_BASE,
.name		= "evdev",
.id_table	= evdev_ids,    //该驱动能支持的device
};


上面是注册handler,那么设备和handler是怎么匹配的?再来看设备的注册:

/**
* input_register_device - register device with input core
* @dev: device to be registered
*
* This function registers device with input core. The device must be
* allocated with input_allocate_device() and all it's capabilities
* set up before registering.
* If function fails the device must be freed with input_free_device().
* Once device has been successfully registered it can be unregistered
* with input_unregister_device(); input_free_device() should not be
* called in this case.
*/
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;

dev_set_name(&dev->dev, "input%ld",
(unsigned long) atomic_inc_return(&input_no) - 1);

error = device_add(&dev->dev);
if (error)
return error;

path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
printk(KERN_INFO "input: %s as %s\n",
dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
kfree(path);

error = mutex_lock_interruptible(&input_mutex);
if (error) {
device_del(&dev->dev);
return error;
}

list_add_tail(&dev->node, &input_dev_list);   //新注册的dev加入input_dev_list队列

list_for_each_entry(handler, &input_handler_list, node) //所有input_handler_list队列里面的handler都拿出来和dev匹配一次
input_attach_handler(dev, handler);   //判断是否匹配成功,成功则调用connect

input_wakeup_procfs_readers();

mutex_unlock(&input_mutex);

return 0;
}


可以看出没如果注册设备,则和已经注册的handler遍历一次,看是否匹配;如果注册handler,则和已经注册的设备遍历一次,看是否匹配。所以注册一个设备或一个handler都会调用input_attach_handler函数来和已经注册的对方匹配一次:
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);  //hander与设备匹配
if (!id)
return -ENODEV;

error = handler->connect(handler, dev, id);  //匹配成功后再建立连接
if (error && error != -ENODEV)
printk(KERN_ERR
"input: failed to attach handler %s to device %s, "
"error: %d\n",
handler->name, kobject_name(&dev->dev.kobj), error);

return error;
}
上面可以看出hander通过id_table来匹配支持的device;匹配之后调用handler的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 error;

for (minor = 0; minor < EVDEV_MINORS; minor++)
if (!evdev_table[minor])
break;

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);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait);

dev_set_name(&evdev->dev, "event%d", minor); //这个evdev输入设备的名字,也就是在dev目录下面的设备节点名,evdev所以是event开头后面标号就为evdev输入设备的编号
evdev->exist = 1;
evdev->minor = minor;

evdev->handle.dev = input_get_device(dev);   //把device放进去
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;            //把handler放进去
evdev->handle.private = evdev;

evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
/*所有输入设备的主设备号统一为INPUT_MAJOR,这里的EVDEV类型的输入设备子设备号以EVDEV_MINOR_BASE为起始,显然minor就代表第几个EVDEV输入设备,他和EVDEV_MINOR_BASE组成了子设备号*/
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);

error = input_register_handle(&evdev->handle); //注册这个handle,不是"handler"
if (error)goto err_free_evdev;
error = evdev_install_chrdev(evdev);
if (error)
goto err_unregister_handle;

error = device_add(&evdev->dev);
if (error)
goto err_cleanup_evdev;
return 0;
err_cleanup_evdev:
evdev_cleanup(evdev);
err_unregister_handle:
input_unregister_handle(&evdev->handle);
err_free_evdev:
put_device(&evdev->dev);
return error;
}
由上面可以知道connect时候构造了一个handle,里面有handler和device;再注册这个handle:

/**
* input_register_handle - register a new input handle
* @handle: handle to register
*
* This function puts a new input handle onto device's
* and handler's lists so that events can flow through
* it once it is opened using input_open_device().
*
* This function is supposed to be called from handler's
* connect() method.
*/
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
struct input_dev *dev = handle->dev;
int error;

/*
* We take dev->mutex here to prevent race with
* input_release_device().
*/
error = mutex_lock_interruptible(&dev->mutex);
if (error)
return error;
list_add_tail_rcu(&handle->d_node, &dev->h_list); //把这个handle放到dev链表
mutex_unlock(&dev->mutex);

/*
* Since we are supposed to be called from ->connect()
* which is mutually exclusive with ->disconnect()
* we can't be racing with input_unregister_handle()
* and so separate lock is not needed here.
*/
list_add_tail(&handle->h_node, &handler->h_list);  //把这个handle放到handler链表

if (handler->start)
handler->start(handle);

return 0;
}
看上面的注释就可以知道注册这个handle就是把自己放入dev和handler的链表,让这三者都连起来,这样就可以在硬件(dev)触发event的时候根据这个中介(handle)快速找到对应的软件(handler)处理函数了。

综合上面就知道通过connect函数,建立了下面这样一个关系:

handle 1----handle 2----handle 3-     ----- input_handle队列

                     /              \

                 dev       handler

               (硬件)    (软件框架)

大体结构就是这样,再看应用调用的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;
struct input_event event;
int retval;

if (count < input_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 (retval + input_event_size() <= count &&
evdev_fetch_next_event(client, &event)) {

if (input_event_to_user(buffer + retval, &event))   //数据copyt到buffer
return -EFAULT;

retval += input_event_size();
}

return retval;
}
他返回给应用这个结构:
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};


上面read可以看出会休眠,那么谁来唤醒?

/*
* Pass incoming event to all connected clients.
*/
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;
struct input_event event;

do_gettimeofday(&event.time);
event.type = type;
event.code = code;
event.value = value;

rcu_read_lock();

client = rcu_dereference(evdev->grab);
if (client)
evdev_pass_event(client, &event); //把数据放入client缓冲区,然后发信号给应用程序
else
list_for_each_entry_rcu(client, &evdev->client_list, node)
evdev_pass_event(client, &event);

rcu_read_unlock();

wake_up_interruptible(&evdev->wait);   //唤醒
}
而evdev_event就是handler里面的event :

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,
};
那么evenet被谁调用?查看代码是在input_handle_event里面被调用,而input_handle_event被input_event调用;再查看input_event,内核里面很多不同的设备都调用了下面这两个函数:

input_event();    //报告输入信息

input_sync();     //同步输入(确认报告一次输入事件)

这些代码都是在硬件中断里面,所以唤醒是通过硬件触发的。

上面提到很多dev和handler,这两个东西一个硬件有关,一个软件框架。这种机制就是一种软件架构分离分层机制,把需要关心的硬件与稳定的软件处理框架分开来:

而纯软件框架那一块在输入子系统中已经帮我们实现了,就是注册handler部分,有evdev,joydev,keyboard,mousedev;

我们只要实现硬件部分的dev,注册dev,然后实现具体驱动;

来看一个4个按键驱动实例:
#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 <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>

struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};

struct pin_desc pins_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 struct input_dev *buttons_dev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms后启动定时器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies+HZ/100);
return IRQ_RETVAL(IRQ_HANDLED);
}

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)
{
/* 松开 : 最后一个参数: 0-松开, 1-按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_dev);
}
else
{
/* 按下 */
input_event(buttons_dev, EV_KEY, pindesc->key_val, 1);
input_sync(buttons_dev);
}
}

static int buttons_init(void)
{
int i;

/* 1. 分配一个input_dev结构体 */
buttons_dev = input_allocate_device();;

/* 2. 设置 */
/* 2.1 能产生哪类事件 */
set_bit(EV_KEY, buttons_dev->evbit);
set_bit(EV_REP, buttons_dev->evbit);

/* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */
set_bit(KEY_L, buttons_dev->keybit);
set_bit(KEY_S, buttons_dev->keybit);
set_bit(KEY_ENTER, buttons_dev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);

/* 3. 注册 */
input_register_device(buttons_dev);

/* 4. 硬件相关的操作 */
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
add_timer(&buttons_timer);

for (i = 0; i < 4; i++)
{
request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
}

return 0;
}

static void buttons_exit(void)
{
int i;
for (i = 0; i < 4; i++)
{
free_irq(pins_desc[i].irq, &pins_desc[i]);
}

del_timer(&buttons_timer);
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
}

module_init(buttons_init);

module_exit(buttons_exit);

MODULE_LICENSE("GPL");



测试方法:要先关闭qt,可以在etc/init.d/rcS文件里注释掉最后一行,然后重启

方法1:cat /dev/tty1

方法2:exec 0</dev/tty1,将tty1设置为标准输入设备

调试方法:

hexdump /dev/event1

  数据偏移        秒             微秒         类     code    value

0000000 00ac 0000 b539 0007 0001 0026 0001 0000

0000010 00ac 0000 b557 0007 0000 0000 0000 0000

0000020 00ac 0000 3ba9 0009 0001 0026 0000 0000

0000030 00ac 0000 3bc0 0009 0000 0000 0000 0000

上面hexdump 指令就是说文件以hex格式显示,参数为文件路径,这里需要读取输入设备文件里面的数据,也就是read返回的值,这个格式在之前说过了:
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
一一对应 hexdump 读取到的数据,看是否返回正确。

_______________________________________________________________________________________

总结输入子系统的写法步骤:

1 分配一个input_dev结构体

2 设置

3 注册

4 硬件相关设置

5 中断上报事件
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: