Linux驱动之输入子系统简析

发布者:EuphoricVoyage最新更新时间:2024-08-20 来源: elecfans关键字:Linux  驱动  输入子系统 手机看文章 扫描二维码
随时随地手机看文章

    printk(KERN_INFO 'input: %s as %sn',

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

}


可以看到它同样也调用了input_attach_handler函数,将设备驱动层与事件层联系起来。这个函数也位于driversinputinput.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: %dn',

            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 = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);//取得次设备号,如果还没有利用则evdev_table为空

    if (minor == EVDEV_MINORS) {

        printk(KERN_ERR 'evdev: no more free evdev devicesn');//没有剩余的空间可以用了

        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 = 1;                               //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 0;


 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 + 1) & (EVDEV_BUFFER_SIZE - 1);

        retval += evdev_event_size();

    }


    return retval;

}


接下来的问题就是谁将进程唤醒,我们直接看到设备驱动层,即driversinputtabletkbtab.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 + 1) & (EVDEV_BUFFER_SIZE - 1);


        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字节

[1] [2] [3]
关键字:Linux  驱动  输入子系统 引用地址:Linux驱动之输入子系统简析

上一篇:Linux驱动之一个简单的输入子系统程序编写
下一篇:Linux驱动之定时器在按键去抖中的应用

推荐阅读最新更新时间:2024-11-03 22:11

提升LED驱动能力的级联变换器
用3节碱性电池给20个~30个白色 发光二极管 ( LED )供电,呈现了一个和传统的升压变换器有关的十分有趣的问题。所需的升压比率和占空因子是不切实际和不可能实现的。如果用现存元件来设计并且级联两级升压是可以产生合理结果的。这种拓扑已经形成大约十年了,而工程师往往认为它太复杂。但是,这种方法对元件方面的要求有一定的好处。第一级转换不需要容忍第二级转换的总输出电压,第二级转换没有第一级转换的电流要求。如果占空因子不是一个关注点,单级升压的电流/电压需求将需要一个更大,更昂贵的转换器,这个转换器可能轻易达到级联升压中两个转换器的成本。你也可以实现电感、整流器、滤波 电容器 的类似的优势。 原文位置 这个设计实例可以给24个串接的白色
[应用]
直流驱动电源LED调光技术介绍
用调正向电流的方法来调亮度要改变LED的亮度,是很容易实现的。首先想到的是改变它的驱动电流,因为LED的亮度是几乎和它的驱动电流直接成正比关系。   调节正向电流的方法   调节LED的电流最简单的方法就是改变和LED负载串联的电流检测电阻,几乎所有DC-DC恒流芯片都有一个检测电流的接口,是检测到的电压和芯片内部的参考电压比较,来控制电流的恒定。但是这个检测电阻的值通常很小,只有零点几欧,如果要在墙上装一个零点几欧的电位器来调节电流是不大可能的,因为引线电阻也会有零点几欧了。所以有些芯片提供一个控制电压接口,改变输入的控制电压就可以改变其输出恒流值。   调正向电流会使色谱偏移   然而用调正向电流的方法来调亮度会产生一
[模拟电子]
通过分布式架构驱动下一代电动汽车系统
电动汽车(EV)和混合动力电动汽车(HEV)正在不断演进,其中的电子设备同样也在发生变化。在这些车辆的整体构造和功能方面,越来越多的电子设备发挥着重要作用。但是,司机并没有改变。他们仍然希望自己的电动汽车和混合动力电动汽车能够顺利地行驶更远,变得更经济实惠,充电速度更快,并确保他们的安全。那么设计人员如何才能以更低的成本为他们提供更多服务? 随着对安全性、功率密度和电磁干扰(EMI)的要求越来越严格,涌现了不同的电源架构来应对这些挑战,包括为每个关键负载配备独立偏置电源的分布式电源架构。 电动汽车中的传统电源架构 汽车设计工程师可以根据电动汽车的电源要求为某些电源架构设计方案。图1所示的传统方法是集中式电源架构,它使用
[汽车电子]
通过分布式架构<font color='red'>驱动</font>下一代电动汽车系统
Xilinx与行业领导厂商共同打造开发生态系统 加快PCI EXPRESS上市
不断扩展的开发合作伙伴生态系统将提供完整的基于FPGA的PCI Express解决方案 2007年10月11日,北京 ——全球可编程逻辑解决方案领导厂商赛灵思公司(Xilinx, Inc. (NASDAQ: XLNX))今天宣布推出一系列基于赛灵思Virtex-5 LXT FPGA的PCI Express协议解决方案。这些解决方案包括由独立设计服务商和IP供应商组成的赛灵思生态系统合作伙伴提供的软件/驱动开发套件、参考设计以及IP。这些解决方案提供的兼容技术使用户能够快速进入PCI Express产品的开发,从而加快产品的上市速度。这些生态系统解决方案由CG-CoreEl systems、Jungo和Northwest Log
[新品]
基于ht1632c芯片的点阵驱动模块在STM32F103mini的应用
ht1632c点阵模块 STM32F103RC(mini) 杜邦线 工程代码:https://download.csdn.net/download/goolete/10922678 驱动代码:https://download.csdn.net/download/goolete/10922879 取模软件:https://download.csdn.net/download/goolete/10922876 (取模软件用之前必须破解,否则生成为乱码,破解文件在压缩包里) HT1632C点阵模块使用手册V1.0:https://download.csdn.net/download/goolete/10922
[单片机]
基于ht1632c芯片的点阵<font color='red'>驱动</font>模块在STM32F103mini的应用
NXPSSL210314W可调光LED驱动方案
NXP公司的SSL2103是用于LED照明的可调光Greenchip控制器, 适合于反激和降压配置应用.器件支持大多数的调光解决方案,具有最佳的效率,中性调光曲线可对数修正到1%,超温保护(OTP),绕组短路保护(SWP)和过流保护(OCP).本文介绍了SSL2103主要特性和优势,方框图,亮度控制框图,降压和反激配置应用电路图,以及SSL2103演示板主要指标,230 V (AC)14 W A55 LED 反激电路图和材料清单. SSL2103: Dimmable Greenchip controller for LED lighting The SSL2103 is a Switched Mode Power Sup
[电源管理]
NXPSSL210314W可调光LED<font color='red'>驱动</font>方案
1_5.5.3_字符设备驱动程序之LED驱动程序_测试改进_P
使用cat /proc/devices查看目前内核支持的字符类设备。 将编译好的first_drv.ko推到挂接的目录下。 使用insmod命令来加载,有一条提示信息,暂时不管。 然后再cat /proc/devices查看一下,发现已经注册进来了,这里的主设备号是252,因为我在程序里使用的是自动分配,所以给分了一个255以下最大的未使用设备号。 编写一个firstdrivertest.c文件,生成可执行文件并推到挂接的目录来测试一下。 显然open失败,因为这个/dev/xxx不存在。 那么先来创建这个设备节点,这设备号是252,次设备号是0。 然后再执行就可以成功了。 驱动:自动或
[单片机]
1_5.5.3_字符设备<font color='red'>驱动</font>程序之LED<font color='red'>驱动</font>程序_测试改进_P
移植OK6410 LCD驱动
1.本次移植过程选择 linux-2.6.28 lcd驱动为参考移植到 linux-2.6.34 ok6410 开发板上。 2.移植过程 主要以给内核增加驱动的思想,在/driver/video/ 下增加 samsung目录,提取 2.6.28 中驱动相关源代码,将源代码复制到samsung 下。 修改 /driver/video 下的Makefile 文件,增加进入samsung目录编译的编译条目 obj-$(CONFIG_FB_S3C) += samsung/ 修改 /driver/video 下的Kconfig 文件,增加 /samsung/Kconfig 配置条目 comment F
[单片机]
移植OK6410 LCD<font color='red'>驱动</font>
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved