嵌入式Linux驱动笔记(六)------浅析input输入子系统框架

你好!这里是风筝的博客,

欢迎和我一起交流。


(文章写得比较早,当时自己也是一知半解,描述的不好,后来我参考了网上的大量资料,重新写了一篇:嵌入式Linux驱动笔记(二十五)------Input子系统框架

基于设备驱动分层的思想,其实理解了platform总线,输入子系统也是可以很好理解的。

以kernel  4.8.17为例:

input.c文件:

static char *input_devnode(struct device *dev, umode_t *mode)  
{  
    return kasprintf(GFP_KERNEL, "input/%s", dev_name(dev));  
}  
  
struct class input_class = {  
    .name       = "input",  
    .devnode    = input_devnode,  
};  
static int __init input_init(void)  
{  
    int err;  
  
    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;  
  
    err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),  
                     INPUT_MAX_CHAR_DEVICES, "input");  
    if (err) {  
        pr_err("unable to register char major %d", INPUT_MAJOR);  
        goto fail2;  
    }  
  
    return 0;  
  
 fail2: input_proc_exit();  
 fail1: class_unregister(&input_class);  
    return err;  
}  

 

 

初始化的工作主要做了:

创建一个input_class类.

在/proc下创建入口项.即/proc/bus/input目录产生设备信息.

注册字符设备input,主设备号为13(#define INPUT_MAJOR13)

 

至于input_class结构体下的input_devnode,我也不知道干什么的......

值得注意的是注册input设备的时候都是在这个input下,即/dev/input/event0等等......

 

好吧,input注册后,基于设备驱动分层的思想,总要有设备文件和驱动文件吧,下面可以看下evdev.c文件,(为什么看这个文件?其实我想看gpio_key.c这个文件的,但是这个驱动文件里的probe涉及到设备树,目前还不会搞,脑阔疼......)

 

evdev.c:

static struct input_handler evdev_handler = {  
    .event      = evdev_event,  
    .events     = evdev_events,  
    .connect    = evdev_connect,  
    .disconnect = evdev_disconnect,  
    .legacy_minors  = true,  
    .minor      = EVDEV_MINOR_BASE,  
    .name       = "evdev",  
    .id_table   = evdev_ids,  
};  
  
static int __init evdev_init(void)  
{  
    return input_register_handler(&evdev_handler);  
}  

向输入子系统注册驱动handler,注意哦,这里是handler,译为:处理者,

之后还会看到一个词:handle,译为:手柄,这两个不要搞混了。

 

接下来进入input_register_handler函数:

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);  
  
    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();  
  
    mutex_unlock(&input_mutex);  
    return 0;  
}  

初始化h_list链表.

14、15行,重头戏,遍历链表,寻找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;  
  
    id = input_match_device(handler, dev);  
    if (!id)  
        return -ENODEV;  
  
    error = 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;  
}  

这里面有一个匹配函数,就像platform总线一样,会把设备和驱动两两匹配起来。

如果匹配成功,这会执行connect函数进行连接(就好比probe函数一样).

 

设备和驱动是怎么匹配的:

static const struct input_device_id *input_match_device(struct input_handler *handler,  
                            struct input_dev *dev)  
{  
    const struct input_device_id *id;  
  
    for (id = handler->id_table; id->flags || id->driver_info; 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;  
        if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))  
            continue;  
        if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))  
            continue;  
        if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))  
            continue;  
        if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))  
            continue;  
        if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))  
            continue;  
        if (!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX))  
            continue;  
        if (!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX))  
            continue;  
        if (!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX))  
            continue;  
        if (!bitmap_subset(id->swbit, dev->swbit, SW_MAX))  
            continue;  
        if (!handler->match || handler->match(handler, dev))  
            return id;  
    }  

很遗憾,这个match函数我也看不懂他们是怎么具体匹配的..........伤心啊 。

 

不过据我了解,evdev.c的handler可以适应任何的dev设备,即分配一个input_dev结构体,即使什么都不做,也能和evdev.c的handler匹配成功。而且这个match的时候,即使匹配成功了也没用break跳出,所以会出现一个dev对应多个handler的情况......

 

当它们匹配成功后,就会调用connect函数.这个connect函数是什么呢,就是之前input_register_handler传入的结构体里的成员,函数如下:

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;  
  
    /*比较长,省略了一部分代码*/  
    dev_set_name(&evdev->dev, "event%d", dev_no);  
  
    evdev->handle.dev = input_get_device(dev);  
    evdev->handle.name = dev_name(&evdev->dev);  
    evdev->handle.handler = handler;  
    evdev->handle.private = evdev;  
  
    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);  
  
    error = input_register_handle(&evdev->handle);  
    if (error)  
        goto err_free_evdev;  
  
    cdev_init(&evdev->cdev, &evdev_fops);  
    evdev->cdev.kobj.parent = &evdev->dev.kobj;  
    error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);  
    /*比较长,省略了一部分代码*/  
    return 0;  
}  

注意第10行,注册input的时候,就是这样会在/dev/input下生成event0、1、2...n.

 

还有注意handle(不是handler哦) ,之前说过,这里会把dev、name、handler、private通过evdev->handle记录下来,并注册handle(23行,即把dev和handler添加到h_list链表中),这样通过handle就可以找到dev与handler,实现设备和驱动的连接了

 

最后,通过device_add就实现设备的注册了("关于正真设备文件的创建(不是指sys下的文件), 最终是由device_add函数里头的kobject_uevent(&dev->kobj, KOBJ_ADD)完成的对hotplug_helper的调用的。

如果想了解uevent的详情,可以阅读 《Linux设备模型浅析之uevent篇》或者他的一系列文章。
")

 

上面讲的是驱动部分,接下来就是设备部分,原理是一样的:

就在input.c文件里的input_register_device函数,方法是一模一样的,

调用:

list_for_each_entry(handler, &input_handler_list, node)  
        input_attach_handler(dev, handler);  

所以说,不管事设备先注册还是驱动先注册,他们都会去寻找、等待自己的另一半(好好的码着代码,突然就虐狗了............)

 

 

驱动和设备建立好连接后,怎么用呢?

当然是通过event上报event(事件)到Handler层进行处理,然后提交给用户了。

input_event->input_handle_event->input_pass_values->input_to_handler->handler->events(handle, vals, count);

 

所以看下evdev.c文件里的evdev_event函数:

static void evdev_event(struct input_handle *handle,  
            unsigned int type, unsigned int code, int value)  
{  
    struct input_value vals[] = { { type, code, value } };  
  
    evdev_events(handle, vals, 1);  
}  

它会调用evdev_events函数,如下:

static void evdev_events(struct input_handle *handle,  
             const struct input_value *vals, unsigned int count)  
{  
    struct evdev *evdev = handle->private;  
    struct evdev_client *client;  
    ktime_t ev_time[EV_CLK_MAX];  
  
    ev_time[EV_CLK_MONO] = ktime_get();  
    ev_time[EV_CLK_REAL] = ktime_mono_to_real(ev_time[EV_CLK_MONO]);  
    ev_time[EV_CLK_BOOT] = ktime_mono_to_any(ev_time[EV_CLK_MONO],  
                         TK_OFFS_BOOT);  
  
    rcu_read_lock();  
  
    client = rcu_dereference(evdev->grab);  
  
    if (client)  
        evdev_pass_values(client, vals, count, ev_time);  
    else  
        list_for_each_entry_rcu(client, &evdev->client_list, node)  
            evdev_pass_values(client, vals, count, ev_time);  
  
    rcu_read_unlock();  
}  

可以看出,刚开始对读操作上锁,防止被干扰,之后进行填充数据,完成之后再解锁。

 

填充数据由evdev_pass_values函数完成。

 

evdev_pass_values函数:

static void evdev_pass_values(struct evdev_client *client,  
            const struct input_value *vals, unsigned int count,  
            ktime_t *ev_time)  
{  
    struct evdev *evdev = client->evdev;  
    const struct input_value *v;  
    struct input_event event;  
    bool wakeup = false;  
  
    if (client->revoked)  
        return;  
  
    event.time = ktime_to_timeval(ev_time[client->clk_type]);  
  
    /* Interrupts are disabled, just acquire the lock. */  
    spin_lock(&client->buffer_lock);  
  
    for (v = vals; v != vals + count; v++) {  
        if (__evdev_is_filtered(client, v->type, v->code))  
            continue;  
  
        if (v->type == EV_SYN && v->code == SYN_REPORT) {  
            /* drop empty SYN_REPORT */  
            if (client->packet_head == client->head)  
                continue;  
  
            wakeup = true;  
        }  
  
        event.type = v->type;  
        event.code = v->code;  
        event.value = v->value;  
        __pass_event(client, &event);  
    }  
  
    spin_unlock(&client->buffer_lock);  
  
    if (wakeup)  
        wake_up_interruptible(&evdev->wait);  
}  

值得注意的是,当事件的类型为EV_SYN、事件代码为SYN_REPORT,并且client->packet_head != client->head(buffer采用的是环形方式存储,即当设备的信息包的头!=设备的头?不知道怎么理解)时,会唤醒 指定的注册在等待队列上的进程.

 

即通过input_sync函数产生:

static inline void input_sync(struct input_dev *dev)  
{  
    input_event(dev, EV_SYN, SYN_REPORT, 0);  
}  

当然,这个不是我们现在关心的,先码住,心理默念:会唤醒某个东西,会唤醒某个东西,会唤醒某个东西,,,,,,,我们比较关心__pass_event函数,就是在这个函数里填充消息。

 

__pass_event函数:

static void __pass_event(struct evdev_client *client,  
             const struct input_event *event)  
{  
    client->buffer[client->head++] = *event;  
    client->head &= client->bufsize - 1;  
  
    if (unlikely(client->head == client->tail)) {  
        /* 
         * This effectively "drops" all unconsumed events, leaving 
         * EV_SYN/SYN_DROPPED plus the newest event in the queue. 
         */  
        client->tail = (client->head - 2) & (client->bufsize - 1);  
  
        client->buffer[client->tail].time = event->time;  
        client->buffer[client->tail].type = EV_SYN;  
        client->buffer[client->tail].code = SYN_DROPPED;  
        client->buffer[client->tail].value = 0;  
  
        client->packet_head = client->tail;  
    }  
  
    if (event->type == EV_SYN && event->code == SYN_REPORT) {  
        client->packet_head = client->head;  
        kill_fasync(&client->fasync, SIGIO, POLL_IN);  
    }  
}  

就这样,将消息填充到了buffer中......

 

 

那用户应用程序又怎么操作的?

还记得之前说的evdev_connect函数吗?

里面有一句:cdev_init(&evdev->cdev, &evdev_fops);

chardevice的初始化,evdev_fops如下:

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,  
    .llseek     = no_llseek,  
};  

终于又回到了我们最熟悉的字符设备的file_operations啊,真是 这里的山路十八弯~不容易啊......

 

进入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;  
    struct input_event event;  
    size_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;  
  
        /* 
         * count == 0 is special - no IO is done but we check 
         * for error conditions (see above). 
         */  
        if (count == 0)  
            break;  
  
        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();  
        }  
  
        if (read)  
            break;  
  
        if (!(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;  
}  

有点长,但是为了最后的胜利,忍了..........

 

分析下这个函数:

第10行:如果读取的消息不为0且长度小于消息buffer里的长度

第17行:又见环形缓存存储,当信息包的头等于设备的尾时?不知道,估计想描述buffer是否为空时吧,当消息为空并且文件是非阻塞方式打开时,立即返回.......

第31行:最重要的一行了,input_event_to_user这个函数里面会调用copy_to_user函数,把buffer里的数据传输到应用空间,这样,即实现了应用程序的数据读取。

第40行:如果文件不是以非阻塞方式打开,则会执行wait_event_interruptible休眠等待。嘿嘿,还记得之前默念的东西吗,就是这里!!!!别慌,好好睡觉,当input_sync函数调用时,我就来叫你起床,嘿嘿嘿......

 

好了,写到这里好像写了挺多的了,累了,,,剩下的write、open之类的懒得分析了,套路都是一样的,剩下的自己练练手咯,祝你们成功!

 

 

 

 

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值