八、输入子系统

 

前面几章我们写的按键驱动程序虽然已经足够完善,但是这个驱动只有知道/dev/key设备节点和write()格式的人才能使用,不具有适应性

故本节引入标准的输入子系统,来编写通用的输入类设备。输入子系统是对所有的标准输入类设备的统一的管理系统,使用这个模型可以跨平台的处理所有的输入类设备

 

 

一、输入子系统分层

输入子系统将一个输入设备的输入过程分成了设备驱动(input device driver)和事件驱动(input event driver)两层。设备驱动负责从底层硬件采集数据,事件驱动负责给用户程序提供接口。通过分层设计,将不同的设备统一到几种驱动接口上。同一种事件驱动可以用来处理多个同类设备;同一个设备也可以和多种事件驱动相衔接。而事件驱动和设备驱动则由输入核心层进行连接,匹配。分层结构如下图:

 

 

输入子系统核心层定义在drivers/input/input.c中

由于输入子系统也是字符设备驱动程序,因此它一定也会有创建类、注册字符设备的过程,而且会有file_operations等结构体,我们可以从此进行分析

二、input.c分析

在input_init()函数中所做的和按键驱动程序中所做的大致相同,如创建类、注册名为input的字符设备

static int __init input_init(void)
{
    ...
    err = class_register(&input_class);
    ...
    err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
    ...
}

 

其file_operations结构体定义如下:

static const struct file_operations input_fops = {
    .owner = THIS_MODULE,
    .open = input_open_file,
    .llseek = noop_llseek,
};

 

当我们应用程序open()时,会调用file_operations input_fops对应的open()函数

static int input_open_file(struct inode *inode, struct file *file)
{
        ...
    handler = input_table[iminor(inode) >> 5];
    if (handler)
        new_fops = fops_get(handler->fops);
        ...
    old_fops = file->f_op;
    file->f_op = new_fops;

    err = new_fops->open(inode, file);
        ...
}

由上述代码可知:

1. input_table[]根据次设备号存储handler

2. open()函数使用新的fops(设备驱动中的fops)代替了旧的fops,这个操作也就解释了为什么file_operations结构体中没有读写函数

3. open()函数在替换之后,调用了新的fops的open()函数

 

handler是我们之前没有分析的,它定义为:

static struct input_handler *input_table[8];

至于数组大小为什么是8,这是因为目前常用的handler只有三种:evdev,mousedev,joydev。而且evdev是通用的handler,定义8个应该够用了

 

handler结构体定义为:

/**
 * struct input_handler - implements one of interfaces for input devices
 * ...
 */
struct input_handler {
    void *private;

    void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
    bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
    bool (*match)(struct input_handler *handler, struct input_dev *dev);
    int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
    void (*disconnect)(struct input_handle *handle);
    void (*start)(struct input_handle *handle);

    const struct file_operations *fops;
    int minor;
    const char *name;

    const struct input_device_id *id_table;

    struct list_head    h_list;
    struct list_head    node;
};

根据注释信息,handler应该就是输入事件驱动程序的结构体

 

在input_handler结构体中使用了input_handle结构体,其定义如下:

/**
 * struct input_handle - links input device with an input handler
 * ...
 */
struct input_handle {
    void *private;

    int open;
    const char *name;

    struct input_dev *dev;
    struct input_handler *handler;

    struct list_head    d_node;
    struct list_head    h_node;
};

根据注释信息,input_handle用于连接input_dev和input_handler,三者关系在下面分析

 

 

分析完了input.c文件,我们来一一分析input_handler、input_handle以及两者的连接过程

三、input_dev

input_dev使用方法遵循:分配、设置、注册

分配:

struct input_dev *input_allocate_device(void)

 

设置(首先设置事件类,然后设置具体事件):

void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
{
    switch (type) {
    case EV_KEY:    // 按键事件类,可指定按键如KEY_1、KEY_Q、KEY_ENTER等
        __set_bit(code, dev->keybit);
        break;

    case EV_REL:    // 相对位移事件类,可指定相对位移如REL_X、REL_Y等
        __set_bit(code, dev->relbit);
        break;

    case EV_ABS:    // 绝对位移事件类,可指定绝对位移如ABS_X、ABS_Y等
        __set_bit(code, dev->absbit);
        break;

    ...
    }

    __set_bit(type, dev->evbit);
}

也可以使用set_bit()函数,在源代码中使用set_bit()

 

注册:

int input_register_device(struct input_dev *dev)
{
...
    /* 通用的同步事件 */
    __set_bit(EV_SYN, dev->evbit);

...
    /* 注册的设备名字为input0, 1, 2, ... */
    dev_set_name(&dev->dev, "input%ld",
             (unsigned long) atomic_inc_return(&input_no) - 1);

    /* 添加device */
    error = device_add(&dev->dev);
...
    /* 把dev结构放到链表里面 */
    list_add_tail(&dev->node, &input_dev_list);

    /* 对每一个input_handler都调用input_attach_handler()函数 */
    list_for_each_entry(handler, &input_handler_list, node)
        /* 匹配dev和handler */
        input_attach_handler(dev, handler);
...
}

其中的input_attach_handler(dev, handler);对应input_handle,因为之前说过input_handle用于连接input_dev和input_handler

 

在注册完成后,若input_dev获得数据,需要向核心层上报事件,上报事件使用如下函数:

// 上报事件
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)

// 上报绝对坐标
void input_report_abs(struct input_dev *dev, unsigned int code, int value)

// 上报相对坐标
void input_report_rel(struct input_dev *dev, unsigned int code, int value)

// 上报键值
void input_report_key(struct input_dev *dev, unsigned int code, int value)

// 上报同步事件
void input_sync(struct input_dev *dev)

代码中后四个函数均使用input_event()函数实现,input_event()函数调用过程如下:

input_event()
  -> input_handle_event()
    -> input_pass_event()
      -> handler->event(handle, type, code, value);

 

上报事件最终会调用handler->event()函数

之前说过evdev是通用的handler,在此我便以/drivers/input/evdev.c进行分析

evdev_read()会进行休眠,evdev_event()在上报事件被调用后会唤醒休眠进程,从而完成read()操作

 

注销:

// 注销
void input_unregister_device(struct input_dev *dev)

// 释放
void input_free_device(struct input_dev *dev)

 

 

四、input_handler

注册:

int input_register_handler(struct input_handler *handler)
{
...
    INIT_LIST_HEAD(&handler->h_list);

    /* 设置input_table */
    if (handler->fops != NULL) {
        if (input_table[handler->minor >> 5]) {
            retval = -EBUSY;
            goto out;
        }
        input_table[handler->minor >> 5] = handler;
    }

    /* 把handler放入input_handler_list */
    list_add_tail(&handler->node, &input_handler_list);

    /* 对每一个dev调用input_attach_handler匹配handler */
    list_for_each_entry(dev, &input_dev_list, node)
        input_attach_handler(dev, handler);
...
}

 

注销:

void input_unregister_handler(struct input_handler *handler)

 

 

五、input_dev和input_handler的连接过程

两者匹配使用的是input_attach_handler()函数:

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
...
    id = input_match_device(handler, dev);
...
    error = handler->connect(handler, dev, id);
...
}

 

input_match_device()函数:

static const struct input_device_id *input_match_device(struct input_handler *handler,
                            struct input_dev *dev)
{
    for (id = handler->id_table; id->flags || id->driver_info; id++) {

        // 默认的匹配过程,使用handler->id_table和dev->id进行匹配

        if (!handler->match || handler->match(handler, dev))
            return id;
    }
}

在代码中,匹配成功退出调用handler的connect()函数,否则调用handler的match()函数

 

在此还是以evdev为例,其连接函数为evdev_connect()定义如下:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
             const struct input_device_id *id)
{
...
    /* 分配evdev */
    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
...
    /* 设置handle */
    evdev->handle.dev = input_get_device(dev);
    evdev->handle.name = dev_name(&evdev->dev);
    evdev->handle.handler = handler;
    evdev->handle.private = evdev;
...
    /* 注册handle */
    error = input_register_handle(&evdev->handle);

    error = evdev_install_chrdev(evdev);
    error = device_add(&evdev->dev);
...
}

 

input_dev、input_handler和input_handle三者关系如下:

 

 

六、总结

1. input_init()初始化输入子系统

1.1 调用register_chrdev(13, "input", &input_fops);

 

2. open()输入子系统文件:int input_open_file()

2.1 替换替换file_oprations

2.2 执行new_fops->open()函数

 

3. 注册input_handler:input_register_handler()

3.1 添加handler到input_table[]数组

3.2 添加handler到input_handler_list链表

3.3 调用input_attach_handler()

 

4. 注册input_dev:input_register_device()

4.1 添加dev到input_dev链表

4.2 调用input_attach_handler()

 

5. 匹配:input_attach_handler()

5.1 匹配dev->id和handler->id_table

5.2 成功,调用input_handler->connect()

 

6. 连接:input_handler->connect()

6.1 创建input_handle,三者连接


7. event发生(如按键中断),在中断函数中上报事件:input_event()

7.1 调用input_handler->event()

 

 

七、更改key.c为输入子系统

主要更改的函数有keys_init()和key_timer_func():

keys_init():

 1 static int keys_init(void)
 2 {
 3     /* 1. 分配 */
 4     inputdev = input_allocate_device();
 5 
 6     /* 2. 设置 */
 7     /* 2.1 设置事件类 */
 8     set_bit(EV_KEY, inputdev->evbit);
 9     set_bit(EV_REP, inputdev->evbit);    /* 重复类事件 */
10 
11     /* 2.2 设置按键事件 */
12     set_bit(KEY_L, inputdev->keybit);
13     set_bit(KEY_S, inputdev->keybit);
14     set_bit(KEY_ENTER, inputdev->keybit);
15     set_bit(KEY_LEFTSHIFT, inputdev->keybit);
16 
17     /* 3. 注册 */
18     input_register_device(inputdev);
19 ...
20 }

set_bit(EV_REP, inputdev->evbit);表示可产生重复类事件,也就是长按按键,就会产生多次按键效果 

 

key_timer_func():

 1 static void key_timer_func(unsigned long arg)
 2 {
 3 ...
 4     if (pinval) /* 松开 */ {
 5         /* 上传数据 */
 6         input_event(inputdev, EV_KEY, pindesc->val, 0);
 7         input_sync(inputdev);
 8     }
 9     else /* 按下 */ {
10         input_event(inputdev, EV_KEY, pindesc->val, 1);
11         input_sync(inputdev);
12     }
13 }

input_sync(inputdev);表示已完成当前上报工作

 

key源代码:

  1 #include <linux/module.h>
  2 #include <linux/fs.h>
  3 #include <linux/init.h>
  4 #include <linux/cdev.h>
  5 #include <linux/slab.h>
  6 #include <linux/device.h>
  7 #include <linux/irq.h>
  8 #include <linux/interrupt.h>
  9 #include <linux/wait.h>
 10 #include <linux/timer.h>
 11 #include <linux/gpio.h>
 12 #include <linux/sched.h>
 13 #include <linux/input.h>
 14 
 15 #include <asm/uaccess.h>
 16 #include <asm/irq.h>
 17 #include <asm/io.h>
 18 
 19 #include <mach/gpio.h>
 20 
 21 #define KEY_MAJOR        255
 22 
 23 struct pin_desc {
 24     unsigned int gpio;
 25     int val;
 26     char *name;
 27 };
 28 
 29 static struct timer_list    key_timer;
 30 static struct pin_desc*        pindesc;
 31 static struct input_dev*    inputdev;
 32 
 33 static struct pin_desc desc[4] = {
 34     { EXYNOS4_GPX3(2), KEY_L, "KEY0" },
 35     { EXYNOS4_GPX3(3), KEY_S, "KEY1" },
 36     { EXYNOS4_GPX3(4), KEY_ENTER, "KEY2" },
 37     { EXYNOS4_GPX3(5), KEY_LEFTSHIFT, "KEY3" },
 38 };
 39 
 40 static void key_timer_func(unsigned long arg)
 41 {
 42     struct pin_desc *irq_pd = pindesc;
 43 
 44     if (!irq_pd)
 45         return ;
 46     
 47     unsigned int pinval;
 48 
 49     pinval = gpio_get_value(pindesc->gpio);
 50 
 51     if (pinval) /* 松开 */ {
 52         /* 上传数据 */
 53         input_event(inputdev, EV_KEY, pindesc->val, 0);
 54         input_sync(inputdev);
 55     }
 56     else /* 按下 */ {
 57         input_event(inputdev, EV_KEY, pindesc->val, 1);
 58         input_sync(inputdev);
 59     }
 60 }
 61 
 62 static irqreturn_t key_interrupt(int irq, void *dev_id)
 63 {
 64     pindesc = (struct pin_desc *)dev_id;
 65 
 66     mod_timer(&key_timer, jiffies + HZ / 100);
 67 
 68     return IRQ_HANDLED;
 69 }
 70 
 71 static int keys_init(void)
 72 {
 73     /* 1. 分配 */
 74     inputdev = input_allocate_device();
 75 
 76     /* 2. 设置 */
 77     /* 2.1 设置事件类 */
 78     set_bit(EV_KEY, inputdev->evbit);
 79     set_bit(EV_REP, inputdev->evbit);    /* 重复类事件 */
 80 
 81     /* 2.2 设置按键事件 */
 82     set_bit(KEY_L, inputdev->keybit);
 83     set_bit(KEY_S, inputdev->keybit);
 84     set_bit(KEY_ENTER, inputdev->keybit);
 85     set_bit(KEY_LEFTSHIFT, inputdev->keybit);
 86 
 87     /* 3. 注册 */
 88     input_register_device(inputdev);
 89 
 90     /* 注册中断 */
 91     int irq, i;
 92     for (i = 0; i < ARRAY_SIZE(desc); i++) {
 93         irq = gpio_to_irq(desc[i].gpio);
 94         request_irq(irq, key_interrupt, IRQ_TYPE_EDGE_BOTH, desc[i].name, (void *)&desc[i]);
 95     }
 96     
 97     init_timer(&key_timer);
 98     key_timer.function = key_timer_func;
 99     add_timer(&key_timer);
100 
101     return 0;
102 }
103 
104 static void keys_exit(void)
105 {    
106     // 释放中断
107     int irq, i;
108 
109     for (i = 0; i < ARRAY_SIZE(desc); i++) {
110         irq = gpio_to_irq(desc[i].gpio);
111         free_irq(irq, (void *)&desc[i]);
112     }
113 
114     del_timer(&key_timer);
115 
116     input_unregister_device(inputdev);
117     input_free_device(inputdev);
118 }
119 
120 module_init(keys_init);
121 module_exit(keys_exit);
122 
123 MODULE_LICENSE("GPL");
View Code

Makefile:

 1 KERN_DIR = /work/tiny4412/tools/linux-3.5
 2 
 3 all:
 4     make -C $(KERN_DIR) M=`pwd` modules 
 5 
 6 clean:
 7     make -C $(KERN_DIR) M=`pwd` modules clean
 8     rm -rf modules.order
 9 
10 obj-m    += key.o
View Code

测试文件:

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <sys/types.h>
 4 #include <sys/stat.h>
 5 #include <fcntl.h>
 6 #include <string.h>
 7 
 8 int main(int argc, char** argv)
 9 {
10     if (argc != 2) {
11         printf("Usage:\n");
12         printf("%s <event1|event2>\n", argv[0]);
13         return 0;
14     }
15     
16     char buf[100] = "/dev/";
17     strcat(buf, argv[1]);
18     printf("You Will Open %s\n", buf);
19     
20     int fd;
21     
22     fd = open(buf, O_RDWR);
23     if (fd < 0) {
24         printf("can't open %s\n", buf);
25         return -1;
26     }
27 
28     unsigned char key_val;
29     
30     while (1) {
31         read(fd, &key_val, 1);
32         printf("key_val = 0x%x\n", key_val);
33     }
34     
35     close(fd);
36 
37     return 0;
38 }
View Code

 

测试:

在编译并在开发板上insmod后,会出现如下信息:

<6>input: Unspecified device as /devices/virtual/input/input3

 

这是由于未设置input_dev的名字所导致的,暂时不需要管

接下来执行:

# ps -ef

确定-/bin/sh的pid为108(不同开发板-/bin/sh的pid不同)

# ls -l /proc/108/fd

确定使用tty1

# exec 0</dev/tty1

接下来按键,效果如下图:

 

 

下一章  九、总线设备驱动模型

 

转载于:https://www.cnblogs.com/Lioker/p/10871151.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值