zoukankan      html  css  js  c++  java
  • 002输入子系统驱动

    输入子系统概念介绍(第十三课/第一节)

    回顾第三个驱动程序(中断方式的按键驱动程序)和测试程序,发现有一些缺点:
    这个驱动程序没办法用在别人写的现成的应用程序上(比如:QT),因为别人写的应用程序肯定不会来打开你这个"/dev/third_chrdev"。别人打开的是一些现成的设备(比如:/dev/tty),甚至别人都不打开设备,而是直接调用 scanf() 就可以获取按键的输入。

    所以,以前写的驱动程序只能自己使用。如果要写一个通用的驱动程序,让其它的应用程序无缝(无缝就是指不需要修改别人的应用程序)的移植到单板上,就需要使用现成的驱动程序(在内核现成的驱动程序里面把自己的东西融合进去)。这个现成的驱动程序就是输入子系统(input子系统)。
    要想把自己东西融合进这个驱动程序,就得把这个输入子系统的框架弄清楚。
    以前我们自己写驱动时的步骤,在input子系统里面也会有,只不过是系统已经做好了。

    1、最上层/核心层(drivers/input.c):

    首先,看一个驱动程序是从"入口函数"开始查看


    以前注册字符设备驱动的函数是自己写的,现在是内核里面已经有了
    来看看 "input_fops" 这个结构,这个结构里面只有一个 open 成员。

    疑问,这不是输入子系统吗,你不是想要读按键吗,怎么会只有一个 open 函数呢?可以猜想这个 open 函数肯定做了某些工作。进入这个去看看

    定义一个"input_handler"的结构体指针并指向以打开文件的次设备在"input_table"这个数组中找到一项

    然后把(handler)里面的(fops)结构指针赋值给(new_fops)

    把(new_fops)赋值给此函数参数的(file)中的(f_op)结构

    调用(file_operation)结构中的 open 成员

    这样下来最后用到的是(struct input_handler)类型的指针(handler)中的(fops),input.c是一个中转的作用。最终还是会用到(input_table[])这个数组。
    以后app来读的时候最终就会调用(file->f_op_read),但是这个(input_table[])是如何定义的,又是怎样被构造的?

    被static修饰的全局变量只能在该文件被使用,搜索一下

    那这个(input_register_handler)函数又是被谁来调用的呢?


    这些handler向上注册

    接下来看看这个读的过程


    查看这个结构的原型

    它是一个(input_handler)类型的结构体,我们来看看的它的定义

    以前这个file_operation使我们自己构造的,现在是系统已经做好了。所以(64>>5)也就是64除以32等于2,放在(input_table[2])里

    这些handler的会放入input.c中的input_table中,软件方面会向核心层注册handler,硬件方面会向核心层注册device。

    一边是(handler)软件处理者,一边是(device)设备,那么两边应该如何建立连接呢?
    (input_handler)中有一个(id_table),表示能支持哪些设备。

    当我们注册(handler)和(device)时,这两者就会两两比较,看(handler)能否支持这个设备,若支持则会调用(input_handler)结构中的(connet)函数建立连接

    看看谁会调用这(input_register_device),搜索一下(有各种按键,各种鼠标等等)

    分析一下(input_register_device)会做哪些事情

    放入链表

    对于每一个(input_handler),都调用(input_attach_handler)这个函数,它会根据input_handler的id_table判断能否支持这个设备

    再来分析一下(input_register_handler)

    首先先放在数组里

    然后同样也放入链表里

    紧接着遍历 input_dev_list(全局链表,连接所有的 input_dev) 上的dev,并调用 input_attach_handler来进行输入设备和软件处理的关联。

    所以不管是先注册右边的(handler)还是左边的(device),最终都会调用(input_attach_handler)来进行匹配
    现在我们来看看(input_attach_handler)这个函数到底做了什么?

    以(evdev.c)为例子,看看是如何建立连接的(看 connet 函数),可能不同的(handler)有自己不同的方式
    分析(evdev_connet)函数

    分配一个(evdev)结构体,但里面包含(input_handle)结构体

    看看这个(evdev)结构体


    分配(input_handle)后,下面进行设置

    然后注册这个(input_handle)

    看看这个(handle)它是如何注册的

    app:read

    应用程序来读,最终会调用(input_handler->fops->read)

    怎么读按键?

    既然有休眠,那么谁来唤醒它呢?它在(evdev->wait)上休眠,所以在该文件中搜索这个关键字就能找到

    这个(evdev_event)事件处理函数是怎样被触发的,也就是被谁调用的?
    猜测:应该是硬件相关的代码,在(input_dev)那一层被触发
    回过头看看硬件那一层,以(gpio_keys.c)它作为例子,分析


    接着分析(input_event)函数

    所以,这个事件处理函数就是被(input_dev)这一层调用的。

    写符合输入子系统框架的程序(第十三课/第二节)

    首先要明白处理函数那一层系统已经做好了,所以我们只要写左边的硬件层,然后注册,就可以调用与右边层建立连接,进而可以调用右边的函数
    看看我们以前自己写驱动程序的步骤:

    在上一节我们已经弄清楚这个子系统的框架了,那么怎么写符合输入子系统框架的驱动程序?

    1. 分配一个input_dev结构体
    2. 设置
    3. 注册
    4. 硬件相关的代码,比如在中断服务程序里上报事件

    以(gpio_keys.c)为例看看别人是怎么写的,然后模仿
    首先看看别人是如何分配一个input_dev结构体

    设置这个结构体,先看看这个结构体需要设置些什么

    设置产生哪类事件,哪类事件下的哪些事件

    注册

    下面我们自己来写驱动程序

    1.分配一个input_dev结构体


    2.1 设置能产生哪类事件

    设置能产生按键类的哪些事件,以前写的按键值只有我们自己知道是什么意思,现在我们要写一个通用的驱动程序


    3.注册,注册后就会放入input_dev的链表中,然后会遍历input_handler链表通过handler的id_table一个一个的进行匹配比较,匹配成功后会建立连接(调用connet函数创建一个input_handle结构体)

    4.硬件相关的操作(注册中断,添加定时器防抖)


    看看这个中断函数(button_irq)

    按下按键10ms后执行的函数
    以前有按键按下时,会根据按键值进行处理,现在只需要上报事件即可,只需调用到(input.c)中的(input_event)函数,最终就会调用handler里的event函数

    这个上报事件最终是执行handler里的event函数,这个event函数会把传入的参数记录下来,最后唤醒中断

    在出口函数里面释放申请的资源

    测试:
    在装载驱动之前先看看(/dev)下的event有哪些

    装载驱动

    那么这个event是怎么来的呢?

    第一步:创建主设备号为13

    第二步:创建类

    类名是input

    第三步:在类里面创建次设备号,进而通过mdev创建设备结点


    调试方法一:hexdump /dev/event1 表示(open(/dev/event1),然后读取内容,紧接着用十六进制显示出来),第一排和第三排分别表示按下和松开,第二排和第四排表示上传的同步事件



    问:这些值是怎么来的呢?答:在(evdev.c)中(evdev_read)函数会调用下面的函数

    这个函数的原型其实就是(copy_to_usr)

    拷贝的内容

    调试方法二:cat /dev/tty1

    (cat /dev/tty1)它主设备号是4,此设备号是1,它是通过(tty_io.c)里面的驱动程序来访问(keybord.c)
    这个(tty_io.c)比较复杂,暂且不去分析,现在大概来分析(keybord.c)
    在入口函数调用这个函数



    若上报事件,则会从input_dev里的h_list指向的input_handle的链表里找到一项(因为每一个connet函数会创建一个input_handle),所以会有多项(input_handle)结构。找到后调用handler里面的event函数(event.c的handler调用它的input_handler结构中的event函数,同理,keybord.c的handler则调用它的input_handler结构中的event函数)。

    使用(cat /dev/tty1)时,它不是从input子系统进入,而是通过(tty_io.c)与(keybord.c)的联系进入的

    按键驱动程序(eighth_input.c)

    /*参考F:编程之路编程之路(Linux)link_to_term1_and_term2kernelslinux-2.6.22.6driversinputkeyboardgpio_keys.c*/
    
    #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>
    
    
    static struct input_dev *buttons_dev;
    static struct pin_desc *irq_pd;
    static struct timer_list buttons_timer;
    static int irq_val;
    
    static struct pin_desc{
            int irq;
            char *name;
            unsigned int pin;
            unsigned int key_val;
        };
    static 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 irqreturn_t button_irq(int irq, void *dev_id)
    {
        irq_pd = (struct pin_desc *)dev_id;
        irq_val = irq;
        mod_timer(&buttons_timer, jiffies+HZ/100);    
        return IRQ_HANDLED;
    }
    
    static void buttons_timer_function(unsigned long data)
    {
        struct pin_desc *pindesc = irq_pd;
        unsigned char pinval;
    
        if(!pindesc)
            return;
        //printk("irq_val = %d
    
    ", irq_val);
        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,ENTRY,LEFTSHIT */
        set_bit(pins_desc[0].key_val, buttons_dev->keybit);
        set_bit(pins_desc[1].key_val, buttons_dev->keybit);
        set_bit(pins_desc[2].key_val, buttons_dev->keybit);
        set_bit(pins_desc[3].key_val,  buttons_dev->keybit);
    
        /* 3.注册 */
        input_register_device(buttons_dev);
    
        /* 4.硬件相关的操作 */
        /* 4.1.定时器初始化(防抖动) */
        init_timer(&buttons_timer);
        buttons_timer.function = buttons_timer_function;
        add_timer(&buttons_timer);
    
        /* 4.2.注册中断 */
        for(i=0; i<4; i++)
        {
            request_irq(pins_desc[i].irq,   button_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");
    

    测试:

    在串口上等待输入的程序,根据我们输入内容执行的程序就是(shell)程序。
    可以查看我们的(shell)程序打开了哪些文件

    以前是从串口上得到输入,现在把它的0号文件(标准输入文件改为tty1),就可以从键值得到输入。

    不足点:按下后不松开不能重复打印该键值。加上这一句就可以了。

    那么它是如何实现的呢?
    首先当有按键按下并消抖后会上报事件

    根据事件类型判断是否为重复类事件


    在注册(input_register_device)就会初始化一个定时器


    再次测试:

    补充:环形缓冲区(本质是一个数组)

    <wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">

  • 相关阅读:
    原始数据导入ods
    flume job
    flume拦截器
    Pandas用法总结
    NumPy用法总结
    matplotlib的使用
    【Java】Java线程中断(Interrupt)与阻塞(park)的区别
    【MySQL】MySQL中的索引原理与索引分类
    【JUC】从Lock到AQS了解Java中的锁
    【Redis】Redis 持久化之 RDB 与 AOF 详解
  • 原文地址:https://www.cnblogs.com/luosir520/p/11447057.html
Copyright © 2011-2022 走看看