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_term2\kernels\linux-2.6.22.6\drivers\input\keyboard\gpio_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\n\r", 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;">

posted on 2019-09-02 15:55  wawzzll  阅读(209)  评论(0编辑  收藏  举报

导航