Chinaunix首页 | 论坛 | 博客
  • 博客访问: 869155
  • 博文数量: 189
  • 博客积分: 4310
  • 博客等级: 上校
  • 技术积分: 1925
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-27 08:56
文章分类

全部博文(189)

文章存档

2015年(1)

2013年(2)

2012年(1)

2011年(39)

2010年(98)

2009年(48)

分类: LINUX

2009-11-27 10:29:11

输入子系统架构Overview

        输入子系统(Input Subsystem)的架构如下图所示

 

        输入子系统由 输入子系统核心层( Input Core ),驱动层和事件处理层(Event Handler)三部份组成。一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过 Driver -> InputCore -> Eventhandler -> userspace 的顺序到达用户空间传给应用程序。
       
其中Input Core Input Layer driver/input/input.c及相关头文件实现。对下提供了设备驱动的接口,对上提供了Event Handler层的编程接口。

1.1 主要数据结构

                                                                                                                          1     Input Subsystem main data structure

数据结构

用途

定义位置

具体数据结构的分配和初始化

Struct input_dev

驱动层物理Input设备的基本数据结构

Input.h

通常在具体的设备驱动中分配和填充具体的设备结构

Struct Evdev

Struct Mousedev

Struct Keybdev…

Event Handler层逻辑Input设备的数据结构

Evdev.c

Mousedev.c

Keybdev.c

Evdev.c/Mouedev.c …中分配

 

Struct Input_handler

Event Handler的结构

Input.h

Event Handler层,定义一个具体的Event Handler

Struct Input_handle

用来创建驱动层DevHandler链表的链表项结构

Input.h

Event Handler层中分配,包含在Evdev/Mousedev…中。

 

1.2 输入子系统架构示例图

2  输入子系统架构示例图


输入链路的创建过程

        由于input子系统通过分层将一个输入设备的输入过程分隔为独立的两部份:驱动到Input CoreInput CoreEvent Handler。所以整个链路的这两部分的接口的创建是独立的。

2.1 硬件设备的注册

        驱动层负责和底层的硬件设备打交道,将底层硬件对用户输入的响应转换为标准的输入事件以后再向上发送给Input Core
       
驱动层通过调用Input_register_device函数和Input_unregister_device函数来向输入子系统中注册和注销输入设备。
这两个函数调用的参数是一个Input_dev结构,这个结构在driver/input/input.h中定义。驱动层在调用Input_register_device之前需要填充该结构中的部分字段

#include
#include
#include

MODULE_LICENSE("GPL");

struct input_dev ex1_dev;

static int __init ex1_init(void)
{
    /* extra safe initialization */
    memset(&ex1_dev, 0, sizeof(struct input_dev));
    init_input_dev(&ex1_dev);

    /* set up descriptive labels */
    ex1_dev.name = "Example 1 device";
    /* phys is unique on a running system */
    ex1_dev.phys = "A/Fake/Path";
    ex1_dev.id.bustype = BUS_HOST;
    ex1_dev.id.vendor = 0x0001;
    ex1_dev.id.product = 0x0001;
    ex1_dev.id.version = 0x0100;
   
    /* this device has two keys (A and B) */
    set_bit(EV_KEY, ex1_dev.evbit);
    set_bit(KEY_B, ex1_dev.keybit);
    set_bit(KEY_A, ex1_dev.keybit);
   
    /* and finally register with the input core */
    input_register_device(&ex1_dev);
   
    return 0;
}

        其中比较重要的是evbit字段用来定义该输入设备可以支持的(产生和响应)的事件的类型。
包括:

Ø EV_RST   0x00  Reset
Ø EV_KEY   0x01  
按键
Ø EV_REL   0x02  
相对坐标
Ø EV_ABS   0x03  
绝对坐标
Ø EV_MSC   0x04  
其它
Ø EV_LED   0x11   LED
Ø EV_SND   0x12  
声音
Ø EV_REP   0x14  Repeat
Ø EV_FF   0x15  
力反馈

一个设备可以支持一个或多个事件类型。每个事件类型下面还需要设置具体的触发事件,比如EV_KEY事件,支持哪些按键等。

2.2 Event Handler

2.2.1 注册Input Handler

        驱动层只是把输入设备注册到输入子系统中,在驱动层的代码中本身并不创建设备结点。应用程序用来与设备打交道的设备结点的创建由Event Handler层调用Input core中的函数来实现。而在创建具体的设备节点之前,Event Handler层需要先注册一类设备的输入事件处理函数及相关接口
       
MouseDev Handler为例:

static struct input_handler mousedev_handler = {
 event:  mousedev_event,
 connect:  mousedev_connect,
 disconnect: mousedev_disconnect,
 fops:  &mousedev_fops,
 minor:  MOUSEDEV_MINOR_BASE,
};

static int __init mousedev_init(void)
{
 input_register_handler(&mousedev_handler);

 memset(&mousedev_mix, 0, sizeof(struct mousedev));z
 init_waitqueue_head(&mousedev_mix.wait);
 mousedev_table[MOUSEDEV_MIX] = &mousedev_mix;
 mousedev_mix.exist = 1;
 mousedev_mix.minor = MOUSEDEV_MIX;
 mousedev_mix.devfs = input_register_minor("mice", MOUSEDEV_MIX, MOUSEDEV_MINOR_BASE);

 printk(KERN_INFO "mice: PS/2 mouse device common for all mice\n");

 return 0;
}

        Mousedev_init中调用input.c中定义的input_register_handler来注册一个鼠标类型的Handler. 这里的Handler不是具体的用户可以操作的设备,而是鼠标类设备的统一的处理函数接口。

2.2.2 设备节点的创建

        接下来,mousedev_init函数调用input_register_minor注册一个通用mice设备,这才是与用户相关联的具体的设备接口。然而这里在init函数中创建一个通用的Mice设备只是鼠标类Event Handler层的特例。在其它类型的EventHandler层中,并不一定会创建一个通用的设备。
       
标准的流程见是硬件驱动向Input子系统注册一个硬件设备后,在input_register_device中调用已经注册的所有类型的Input Handlerconnect函数,每一个具体的Connect函数会根据注册设备所支持的事件类型判断是否与自己相关,如果相关就调用 input_register_minor创建一个具体的设备节点。

void input_register_device(struct input_dev *dev)
{
 ……
 while (handler) {
  if ((handle = handler->connect(handler, dev)))
   input_link_handle(handle);
  handler = handler->next;
 }
}

        此外如果已经注册了一些硬件设备,此后再注册一类新的Input Handler,则同样会对所有已注册的Device调用新的Input HandlerConnect函数已确定是否需要创建新的设备节点:

void input_register_handler(struct input_handler *handler)
{
……
 while (dev) {
  if ((handle = handler->connect(handler, dev)))
   input_link_handle(handle);
  dev = dev->next;
 }
}

        从上面的分析中可以看到一类Input Handler可以和多个硬件设备相关联,创建多个设备节点。而一个设备也可能与多个Input Handler相关联,创建多个设备节点。
       
直观起见,物理设备,Input Handler,逻辑设备之间的多对多关系可见下图:

3  物理设备,Input Handler,逻辑设备关系图

设备的打开和读写

    用户程序通过Input Handler层创建的设备节点的Openreadwrite等函数打开和读写输入设备。

3.1 Open

    设备节点的Open函数,首先会调用一类具体的Input HandlerOpen函数,处理一些和该类型设备相关的通用事务,比如初始化事件缓冲区等。然后通过Input.c中的 input_open_device函数调用驱动层中具体硬件设备的Open函数。

3.2 Read

    大多数Input HandlerRead函数等待在Event Layer层逻辑设备的wait队列上。当设备驱动程序通过调用Input_event函数将输入以事件的形式通知给输入子系统的时候,相关的Input Handlerevent函数被调用,该event函数填充事件缓冲区后将等待队列唤醒。
   
在驱动层中,读取设备输入的一种可能的实现机制是扫描输入的函数睡眠在驱动设备的等待队列上,在设备驱动的中断函数中唤醒等待队列,而后扫描输入函数将设备输入包装成事件的形式通知给输入子系统。

3.3 Write

    2.4内核中没有固定的模式,根据具体的Input Handler,可能不实现,也可能通过调用Input_event将写入的数据以事件的形式再次通知给输入子系统,或者调用设备驱动的Write函数等等。
    2.6
内核的代码中,通过调用Input_event将写入的数据以事件的形式再次通知给输入子系统,而后在Input.c中根据事件的类型,将需要反馈给物理设备的事件通过调用物理设备的Event函数传给设备驱动处理,如EV_LED事件:

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
 ......
 case EV_LED:
         if (code > LED_MAX || !test_bit(code, dev->ledbit) || !!test_bit(code, dev->led) == value)
            return;

         change_bit(code, dev->led);
         if (dev->event) dev->event(dev, type, code, value);
         break;
 ......
 }

其它

    本文中对Input子系统架构的分析主要是基于2.4.20内核,在2.6内核中对Input子系统做了很大的扩充增加了对许多设备的支持(如触摸屏,键盘等)。不过整体的框架还是一致的。

    另,参考了linux journal上的两篇文章:

 

 

 

 

 

#####################################################################################################
早前曾研究了一下输入子系统的原理,给人的感觉是输入子系统很复杂.但其实内核开发者在这方面已经做得很完善了
,
输入子系统虽然错综复杂,但是只要我们领会了输入子系统的一些设计思想后,我们要使用它并非难事
.

以下以内核自带的gpio_keys驱动为例,介绍输入子系统的使用
.
主要的原因是gpio_keys驱动比较简单易懂,另外不是没个人都有触摸屏,但键盘的话相信每一块开发板上都配有吧
^_^

按照以前的习惯,先从下到上的研究底层驱动是如何提交输入事件的:

#####################################################################################################

drivers/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();//
申请input_dev结构
    if (!input)
        return -ENOMEM;

    platform_set_drvdata(pdev, input);//
input_dev结构放好(以后方便调用)

    input->evbit[0] = BIT(EV_KEY);//
目前event的类型不操作32,所以你会看到对于evbit数组的操作都是对evbit[0]中的位来进行操作.

    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);

        /*
根据用户所指定的gpio_keys来申请中断和注册中断处理函数*/
        error = request_irq(irq, gpio_keys_isr, IRQF_SAMPLE_RANDOM,
                     button->desc ? button->desc : "gpio_keys",
                     pdev);
        if (error) {
            printk(KERN_ERR "gpio-keys: unable to claim irq %d; error %d\n",
                irq, error);
            goto fail;
        }

        input_set_capability(input, type, button->code);
    }

    error = input_register_device(input);//
注册输入设备,并和对应的handler处理函数挂钩
    if (error) {
        printk(KERN_ERR "Unable to register gpio-keys input device\n");
        goto fail;
    }

    return 0;

 fail:
    for (i = i - 1; i >= 0; i--)
        free_irq(gpio_to_irq(pdata->buttons[i].gpio), pdev);

    input_free_device(input);

    return error;
}


提到input_dev结构,以下谈一下我对于它的理解:
struct input_dev {

    void *private;

    const char *name;
    const char *phys;
    const char *uniq;
    struct input_id id;

    /*
     *
根据各种输入信号的类型来建立类型为unsigned long 的数组,
     *
数组的每1bit代表一种信号类型
,
     *
内核中会对其进行置位或清位操作来表示时间的发生和被处理.

     */

    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)];

    .........................................
};

/**
 * input_set_capability - mark device as capable of a certain event
 * @dev: device that is capable of emitting or accepting event
 * @type: type of the event (EV_KEY, EV_REL, etc...)
 * @code: event code
 *
 * In addition to setting up corresponding bit in appropriate capability
 * bitmap the function also adjusts dev->evbit.
 */

/*
记录本设备对于哪些事件感兴趣(对其进行处理)*/
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
{
    switch (type) {
    case EV_KEY:
        __set_bit(code, dev->keybit);//
比如按键,应该对哪些键值的按键进行处理(对于其它按键不予理睬)
        break;

    case EV_REL:
        __set_bit(code, dev->relbit);
        break;

    case EV_ABS:
        __set_bit(code, dev->absbit);
        break;

    case EV_MSC:
        __set_bit(code, dev->mscbit);
        break;

    case EV_SW:
        __set_bit(code, dev->swbit);
        break;

    case EV_LED:
        __set_bit(code, dev->ledbit);
        break;

    case EV_SND:
        __set_bit(code, dev->sndbit);
        break;

    case EV_FF:
        __set_bit(code, dev->ffbit);
        break;

    default:
        printk(KERN_ERR
            "input_set_capability: unknown type %u (code %u)\n",
            type, code);
        dump_stack();
        return;
    }

    __set_bit(type, dev->evbit);//
感觉和前面重复了(前面一经配置过一次了)
}
EXPORT_SYMBOL(input_set_capability);


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() - report new input event
 * @dev: device that generated the event
 * @type: type of the event
 * @code: event code
 * @value: value of the event
 *
 * This function should be used by drivers implementing various input devices
 * See also input_inject_event()
 */
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
    struct input_handle *handle;

    if (type > EV_MAX || !test_bit(type, dev->evbit))//
首先判断该事件类型是否有效且为该设备所接受
        return;

    add_input_randomness(type, code, value);

    switch (type) {

        case EV_SYN:
            switch (code) {
                case SYN_CONFIG:
                    if (dev->event)
                        dev->event(dev, type, code, value);
                    break;

                case SYN_REPORT:
                    if (dev->sync)
                        return;
                    dev->sync = 1;
                    break;
            }
            break;

        case EV_KEY:
            /*
             *
这里需要满足几个条件:
             * 1:
键值有效(不超出定义的键值的有效范围
)
             * 2:
键值为设备所能接受(属于该设备所拥有的键值范围
)
             * 3:
按键状态改变了

             */

            if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value)
                return;

            if (value == 2)
                break;

            change_bit(code, dev->key);//
改变对应按键的状态

            /*
如果你希望按键未释放的时候不断汇报按键事件的话需要以下这个(在简单的gpio_keys驱动中不需要这个,暂时不去分析) */
            if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) {
                dev->repeat_key = code;
                mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY]));
            }

            break;
........................................................

    if (type != EV_SYN)
        dev->sync = 0;

    if (dev->grab)
        dev->grab->handler->event(dev->grab, type, code, value);
    else
        /*
         *
循环调用所有处理该设备的handle(event,mouse,ts,joy),
         *
如果有进程打开了这些handle(进行读写),则调用其对应的event接口向气汇报该输入事件.

         */
        list_for_each_entry(handle, &dev->h_list, d_node)
            if (handle->open)
                handle->handler->event(handle, type, code, value);
}
EXPORT_SYMBOL(input_event);


#########################################################################
好了,下面再来研究一下event层对于input层报告的这个键盘输入事件是如何来处理的.
#########################################################################

drivers/input/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 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);
        } else
                  /*
遍厉client_list链表中的client结构(代表些打开evdev的进程(个人理解^_^)) */
                list_for_each_entry(client, &evdev->client_list, node) {
                            /*
填充代表该输入信号的struct input_event结构(事件,类型,键码,键值) */

                        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);//
通知调用input_sync的进程:输入事件经已处理完毕(通知底层).
                }

        wake_up_interruptible(&evdev->wait);//
唤醒睡眠在evdev->wait等待队列等待输入信息的进程(通知上层).
}

###################################################################################
好了,至此一个按键的输入事件处理完毕,现在再来从上到上的来看看用户是如何获取这个输入事件的.
###################################################################################


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


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];

        if (!evdev || !evdev->exist)
                return -ENODEV;

        client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);
        if (!client)
                return -ENOMEM;

        client->evdev = evdev;
         /*
添加evdev_client结构到链表evdev->client_list(好让输入事件到来的时候填写该结构并唤醒进程读取) */
        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;//
存放好evdev_client结构方便以后使用
        return 0;
}


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))//
缓存中没有数据可读且设备是存在的,
                                                     
如果设置为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;
}

呵呵,看到了吧,应用程序就是这样获取输入事件的^_^

######################################################################################################################################
本来对于gpio_keys这样的驱动程序,只要当发生按键事件的时候向上层应用程序汇报键值即可.
不过,对于一些带输出设备(例如led)的输入设备来说(例如键盘),上层应用程序同样可以利用event层来读取或改变其状态
.
请看以下代码:

######################################################################################################################################

static ssize_t evdev_write(struct file *file, const 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 = 0;

        if (!evdev->exist)
                return -ENODEV;

        while (retval < count) {

                if (evdev_event_from_user(buffer + retval, &event))//
从用户处获取事件结构
                        return -EFAULT;
                input_inject_event(&evdev->handle, event.type, event.code, event.value);//
往底层发送事件
                retval += evdev_event_size();
        }

        return retval;
}


/**
 * input_inject_event() - send input event from input handler
 * @handle: input handle to send event through
 * @type: type of the event
 * @code: event code
 * @value: value of the event
 *
 * Similar to input_event() but will ignore event if device is "grabbed" and handle
 * injecting event is not the one that owns the device.
 */
void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
        if (!handle->dev->grab || handle->dev->grab == handle)
                input_event(handle->dev, type, code, value);
}
EXPORT_SYMBOL(input_inject_event);

/*
 * input_event() - report new input event
 * @dev: device that generated the event
 * @type: type of the event
 * @code: event code
 * @value: value of the event
 *
 * This function should be used by drivers implementing various input devices
 * See also input_inject_event()
 */
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
    struct input_handle *handle;

    if (type > EV_MAX || !test_bit(type, dev->evbit))//
首先判断该事件类型是否有效且为该设备所接受
        return;

    add_input_randomness(type, code, value);

    switch (type) {

        case EV_SYN:
            switch (code) {
                case SYN_CONFIG:
                    if (dev->event)
                        dev->event(dev, type, code, value);
                    break;

                case SYN_REPORT:
                    if (dev->sync)
                        return;
                    dev->sync = 1;
                    break;
            }
            break;

.............................................................
        case EV_LED:

            if (code > LED_MAX || !test_bit(code, dev->ledbit) || !!test_bit(code, dev->led) == value)
                return;

            change_bit(code, dev->led);

            if (dev->event)
                dev->event(dev, type, code, value);

            break;


    if (type != EV_SYN)
        dev->sync = 0;

    if (dev->grab)
        dev->grab->handler->event(dev->grab, type, code, value);
    else
        /*
         *
循环调用所有处理该设备的handle(event,mouse,ts,joy),
         *
如果有进程打开了这些handle(进行读写),则调用其对应的event接口向气汇报该输入事件.

         */
        list_for_each_entry(handle, &dev->h_list, d_node)
            if (handle->open)
                handle->handler->event(handle, type, code, value);
}
EXPORT_SYMBOL(input_event);

:
   
鉴于简单的gpio_keys驱动中没有注册自己的event接口,当然也没有对于LED灯的处理,event层只是简单的向上层汇报输入事件(event层也不可能帮你处理你的led设备,对吧),所以这个通过输入子系统控制LED的部分暂时不去研究
.
    (
输出设备LED灯不属于这个输入设备gpio_key的一部分.当然,如果你想通过这个gpio_keys设备来控制led灯的话,可以修改这个gpio_keys驱动,详细可参考driver/input/keyboard目录下的驱动)

阅读(2505) | 评论(1) | 转发(2) |
给主人留下些什么吧!~~

wzfbetterlife2014-11-05 10:54:38

楼主写的好,学习了!