8、輸入子系統


 

前面幾章我們寫的按鍵驅動程序雖然已經足夠完善,但是這個驅動只有知道/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

接下來按鍵,效果如下圖:

 

 

下一章  9、總線設備驅動模型

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM