当前位置:首页 > 芯闻号 > 充电吧
[导读]部分内容参考Linux学习之路,表示感谢. 输入子系统一般将该类驱动划分为3部分,事件处理层为纯软件的东西,设备层涉及底层硬件,它们通过核心层建立联系,对外提供open write等接口。

部分内容参考Linux学习之路,表示感谢.

输入子系统一般将该类驱动划分为3部分,事件处理层为纯软件的东西,设备层涉及底层硬件,它们通过核心层建立联系,对外提供open write等接口。 一、核心层 input.c向外界提供接口

① 在 input_init 中注册了字符设备驱动

err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
static const struct file_operations input_fops = {
        .owner = THIS_MODULE,
        .open = input_open_file,
    };

② 在注册的fops中,仅仅只有1个open函数,下面我们来看这个open函数input_open_file

static int input_open_file(struct inode *inode, struct file *file)
{
 // 定义一个input_handler指针,根据次设备号,从 input_table 数组中取出对应的 handler
    struct input_handler *handler = input_table[iminor(inode) >> 5];
    const struct file_operations *old_fops, *new_fops = NULL;
// 将 handler 的fops赋值给file->f_op,并用调用新的open函数
        old_fops = file->f_op;
        file->f_op = fops_get(handler->fops);
        err = file->f_op->open(inode, file);
那么,必定有个地方创建了handler并对它进行一定的设置,并提供fops函数,将它放入input_table。

就这样,Input.c 实现了一个通用对外接口。

二、事件处理层,注册input_handler ① 放入链表、数组(input_register_handler)

input.c/input_register_handler 函数中 创建了handler并对它进行一定的设置,提供fops函数,将它放入input_table

int input_register_handler(struct input_handler *handler)
    {
        // 将 handler 放入 input_table
        input_table[handler->minor >> 5] = handler;

        // 将 handler 放入 input_handler_list 链表
        list_add_tail(&handler->node, &input_handler_list);

        // 取出 input_dev_list 链表中的每一个 dev 与 该 handler 进行 比对
        list_for_each_entry(dev, &input_dev_list, node)
        input_attach_handler(dev, handler);
    }

以 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 int __init evdev_init(void)
    {
        return input_register_handler(&evdev_handler);
    }
我们与核心层提供的接口对应一下,假设APP需要读按键:

app: read > ... > file->f_op->read == handler->fops->read == evdev_handler->evdev_fops->evdev_read

 /* 读函数中 如果没有事件上报休眠,等待上报事件 唤醒休眠,将事件传送到用户空间 */
static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
//如果无数据可读,且为非阻塞方式 立刻返回
    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);
 //将内核空间数据拷贝到用户空间,略
    return retval;
}
② 匹配 (input_attach_handler)
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
    {
        // 看 dev.id 是否存在于 handler->id_table 中
        id = input_match_device(handler->id_table, dev);
        if (!id)
        return -ENODEV;
        // 在的话,调用 handler->connect
        error = handler->connect(handler, dev, id);
    }
③ 建立连接

我们以 Evdev.c 为例,看一下connect函数

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
    const struct input_device_id *id)
    {
        // 不要关心 evdev ,只看 evdev->handle 即可,这里构建了一个 handle ,注意不是handler
        // handle 就是个 中间件,可以理解成胶带,它把 hander 与 dev 连在一起
        evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);

        // 第一次建立联系,在 handle 中记录 dev 与 handle 的信息,这样通过handle就可以找到dev与handler
        // 即是 实现 handle -> dev   handle -> hander 的联系
        evdev->handle.dev = dev;
        evdev->handle.handler = handler;

        // 申请设备号,创建设备节点
        devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
        dev_set_name(&evdev->dev, "event%d", minor);
        // 在input 类下面创建设备,文件夹的名字是 evdev->name ->inputn ,设备名是 dev->cdev.dev.name -> eventn
        cdev = class_device_create(&input_class, &dev->cdev, devt,
        dev->cdev.dev, evdev->name);
        // 注册 handle
        error = input_register_handle(&evdev->handle);
    }
④ 注册handle,第二次建立联系
int input_register_handle(struct input_handle *handle)
    {
        struct input_handler *handler = handle->handler;
        // 将handle 记录在 dev->h_list 中
        list_add_tail(&handle->d_node, &handle->dev->h_list);
        // 将handle 记录在 handler->h_list 中
        list_add_tail(&handle->h_node, &handler->h_list);
        // 至此,dev 与 hander 也可以找到handle了,dev <-> handle <-> handler 之间畅通无阻
    }
小结: 事件处理层,构建 handler , 通过 input_register_handler 进行注册,注册时 1、将 handler 放入 input_handler_list 链表 2、将 handler 放入 input_table 3、取出 input_dev_list链表中的每一个dev 调用 input_attach_handler 进行id匹配 4、如果匹配成功,则调用 handler->connect 第一次建立连接 5、创建 handle 来进行第二次建立连接,在 handle 中记录 dev 与 handler 的信息,这样通过handle就可以找到dev与handler 6、在dev hander 中记录 handle的信息,实现 dev <-> handle <-> handler 三、设备层,注册input_dev
int input_register_device(struct input_dev *dev)
    {
        // 将 dev 放入 input_dev_list
        list_add_tail(&dev->node, &input_dev_list);
        // 匹配 handler ,参考 ①、②
        list_for_each_entry(handler, &input_handler_list, node)
        input_attach_handler(dev, handler);
    }
建立设备层与设备处理层联系作用

以上面Evdev.c读按键为例:假设无按键按下进如休眠模式

wait_event_interruptible(evdev->wait,
        client->head != client->tail || !evdev->exist);

那么谁来唤醒休眠呢?

static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
    wake_up_interruptible(&evdev->wait);
}
这样我们似乎明白了,在设备层,我们写驱动的时候,比如按键按了一下,我们要上报event 到Handler层进行处理,然后提交给用户程序。

例如:Gpio_keys.c 中断处理函数中

static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
    {
        // 上报事件
        input_event(input, type, button->code, !!state);
        input_sync(input);
        return IRQ_HANDLED;
    }

回看input.c/input_event函数

input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
    struct input_handle *handle;

    list_for_each_entry(handle, &dev->h_list, d_node)
        if (handle->open)
            handle->handler->event(handle, type, code, value);
最终调用 handler->event(handle, type, code, value); 刚好与我们的例子对应:evdev_handler->evdev_event >> wake_up_interruptible(&evdev->wait); 四、写一个基于Input子系统的设备驱动 事件处理层不用我们管了,- -是暂时能力有限管不了。写写设备层的程序就好了。 软件设计流程: /* 1. 分配一个Input_dev结构体 */ /* 2. 设置 支持哪一类事件,该类事件里的那些事件*/ /* 3.注册 */ /* 4.硬件相关操作 */ 流程图:

设置事件的类型:

/*事件类型:*/

struct input_dev {  

        void *private;                           //输入设备私有指针,一般指向用于描述设备驱动层的设备结构  

        const char *name;                        //提供给用户的输入设备的名称  
        const char *phys;                        //提供给编程者的设备节点的名称  
        const char *uniq;                        //指定唯一的ID号,就像MAC地址一样  
        struct input_id id;                      //输入设备标识ID,用于和事件处理层进行匹配  

        unsigned long evbit[NBITS(EV_MAX)];      //位图,记录设备支持的事件类型  
        /* 
         *  #define EV_SYN          0x00    //同步事件 
         *  #define EV_KEY          0x01    //按键事件 
         *  #define EV_REL          0x02    //相对坐标 
         *  #define EV_ABS          0x03    //绝对坐标 
         *  #define EV_MSC          0x04    //其它 
         *  #define EV_SW           0x05    //开关事件 
         *  #define EV_LED          0x11    //LED事件 
         *  #define EV_SND          0x12 
         *  #define EV_REP          0x14    //重复上报 
         *  #define EV_FF           0x15 
         *  #define EV_PWR          0x16 
         *  #define EV_FF_STATUS    0x17 
         *  #define EV_MAX          0x1f 
         */  
        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;       //同步事件完成标识,为1说明事件同步完成  

        int abs[ABS_MAX + 1];                //记录坐标的值  
        int rep[REP_MAX + 1];                //记录重复按键的参数值  

        unsigned long key[NBITS(KEY_MAX)];   //位图,按键的状态  
        unsigned long led[NBITS(LED_MAX)];   //位图,led的状态  
        unsigned long snd[NBITS(SND_MAX)];   //位图,声音的状态  
        unsigned long sw[NBITS(SW_MAX)];     //位图,开关的状态  

        int absmax[ABS_MAX + 1];             //位图,记录坐标的最大值  
        int absmin[ABS_MAX + 1];             //位图,记录坐标的最小值  
        int absfuzz[ABS_MAX + 1];            //位图,记录坐标的分辨率  
        int absflat[ABS_MAX + 1];            //位图,记录坐标的基准值  

        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;                     //用于open、close函数的连续访问互斥  
        unsigned int users;                  

        struct class_device cdev;               //输入设备的类信息  
        union {                                 //设备结构体  
                struct device *parent;  
        } dev;  

        struct list_head        h_list;         //handle链表  
        struct list_head        node;           //input_dev链表  
};  

驱动函数:buttons_drv.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include   
#include   


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

struct pin_desc pins_desc[3] = {
    {IRQ_EINT0, "s2",S3C2410_GPF0,KEY_L},
    {IRQ_EINT2, "s3",S3C2410_GPF2,KEY_S},
    {IRQ_EINT11,"s4",S3C2410_GPG3,KEY_ENTER},
};


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

}

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 */
    set_bit(KEY_L,buttons_dev->keybit);
    set_bit(KEY_S,buttons_dev->keybit);
    set_bit(KEY_ENTER,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 < 3;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 < 3;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");
测试方法: ① 通过控制台查看cat /dev/tty1

这里有个问题就是必须按下回车才会出现回显,因为我们默认的输出设备为串口,现在将他改为tty1,使用exec 0



② 通过hexdump /dev/event1 查看用户得到的数据和驱动中的数据比对

硬件有数据产生时,调用input_event 上报时间(上报事件核心)
--》handle->handler->event(handle, type, code, value);//从输入设备的h_list里面找出handle,从handle得到handler,调用它的event函数
--》evdev_event//记录按键值-->发信号--》唤醒程序,因为之前read时没数据会休眠;

那么我们这里:
open(/dev/event1)–》read > ... > –》evdev_handler->evdev_fops->evdev_read–》evdev_event_to_user –》读到input_event -->

struct input_event {
struct timeval time;  时间
__u16 type;   类别(按键类、相对位移、绝对位移)
__u16 code;那个位置位置
__s32 value;
};//这个结构体可以支持所有的输入事件。


刚好一一对应测试成功。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

前端传递:表名 user,字段 username 字符串、age 数字、is_ikun 布尔,并且把这些值封装为了一个对象

关键字: 框架 前端

摘要:J2EE是目前企业开发的主流Java开发应用平台,文章主要介绍了轻量级J2EE中流行的Struts2、Spring和Hibernate三种框架。具体阐述了SSH框架的基本特征、优点以及SSH的无缝集成,并结合病房信...

关键字: 轻量级J2EE SSH 框架 无缝集成 病房信息管理系统

点击上方名片关注我们朱老师推荐语:此岗位为AIoT终身成长大会员同学提供的自己公司的岗位内推,总部在深圳,是一家专业从事闭路电视监控设备、会议摄像机的研发、制造、销售的高科技企业,有学过嵌入式课程或者海思项目的同学,想换...

关键字: 开发工程师 linux驱动 驱动开发

在软件研发这个领域,程序员的终极目标都是想成为一名合格的架构师。

关键字: 架构 框架 软件研发

在嵌入式系统开发中,经常通过键盘来实现人机交互。本文介绍了一种直接利用ARM的I/O口扩展矩阵键盘的方法。同时以TQ2440开发板为例,对硬件电路连接和相应的linux驱动设计方法都作了详细说明。

关键字: ARM 矩阵键盘 linux驱动

通常在以往接触的Linux驱动,没遇到使用电池供电的情况,因此几乎没关注电源的管理。然而实际中,不少使用电池供电的硬件平台,例如手机、POS机等,就需要对电源进行管理,比如在不使用设备的时候,休眠屏幕省电。

关键字: 电源管理 嵌入式基础 linux驱动

在芯片性能提升有限的今天,分布式训练成为了应对超大规模数据集和模型的主要方法。本文将向你介绍流行深度学习框架 PyTorch 最新版本( v1.5)的分布式数据并行包的设计、实现和评估。 论文地

关键字: pytorch 机器学习 框架

近日,中国国防科技大学、美国加州大学洛杉矶分校和哈佛医学院的研究人员研发了一个深度强化学习框架FINDER。相比于现有的解决方案,FINDER能够更快速、更高效地找到复杂网络中一组最关键的节点,

关键字: AI 开发 框架

众所周知,清华-伯克利深圳学院更是成立了“RISC-V 国际开源实验室”,直接将图灵奖得主、最早提出“精简指令集”(RISC)体系的大卫·帕特森(David Patterson)引入,抓住了开源和源创的源头,有可能在芯片...

关键字: 开源 框架

关于如何集成TabBar,请看上一节《【搭建react-native项目框架】3.集成第三方路由和tab页》本节只讲解如何自定义TabBar的中间按钮,以及播放时旋转动画的实现。关于动画与播放器的集成

关键字: native react 框架
关闭
关闭