Linux驱动开发之输入设备驱动

 

概述

输入设备(如按键、键盘、触摸屏、鼠标等)是典型的字符设备,其一般的工作机理是底层在按键、触摸等动作发送时产生一个中断(或驱动通过Timer定时查询),然后CPU通过SPI、I2C或外部存储器总线读取键值、坐标等数据,并将它们放入一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值、坐标等数据。

显然,在这些工作中,只是中断、读键值/坐标值是与设备相关的,而输入事件的缓冲区管理以及字符设备驱动的file_operations接口则对输入设备是通用的。基于此,内核设计了输入子系统,由核心层处理公共的工作。Linux内核输入子系统的框架如下图所示。

guanxi

输入核心提供了底层输入设备驱动程序所需的API,如分配/释放一个输入设备:

struct input_dev *input_allocate_device(void);
void input_free_device(struct input_dev *dev);

input_allocate_device()返回的是1个input_dev的结构体,此结构体用于表征1个输入设备。注册/注销输入设备用的接口如下:

int __must_check input_register_device(struct input_dev *);
void input_unregister_device(struct input_dev *);

报告输入事件用的接口如下:

/* 报告指定type、code的输入事件 */
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
/* 报告键值 */
void input_report_key(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_abs(struct input_dev *dev, unsigned int code, int value);
/* 报告同步事件 */
void input_sync(struct input_dev *dev);

而对于所有的输入事件,内核都用统一的数据结构来描述,这个数据结构是input_event,如代码所示。

struct input_event {
    struct timeval time;
    __u16type;
    __u16code;
    __s32value;
};

drivers/input/keyboard/gpio_keys.c基于input架构实现了一个通用的GPIO按键驱动。该驱动是基于platform_driver架构的,名为“gpio-keys”。它将与硬件相关的信息(如使用的GPIO号,按下和抬起时的电平等)屏蔽在板文件platform_device的platform_data中,因此该驱动可应用于各个处理器,具有良好的跨平台性。代码清单12.11列出了该驱动的probe()函数。

 1 static int gpio_keys_probe(struct platform_device *pdev)
 2 {
 3 struct device *dev = &pdev->dev;
 4 const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);
 5 struct gpio_keys_drvdata *ddata;
 6 struct input_dev *input;
 7 size_t size;
 8 int i, error;
 9 int wakeup = 0;
10
11 if (!pdata) {
12     pdata = gpio_keys_get_devtree_pdata(dev);
13     if (IS_ERR(pdata))
14         return PTR_ERR(pdata);
15 }
16
17 size = sizeof(struct gpio_keys_drvdata) +
18         pdata->nbuttons * sizeof(struct gpio_button_data);
19 ddata = devm_kzalloc(dev, size, GFP_KERNEL);
20 if (!ddata) {
21     dev_err(dev, "failed to allocate state\n");
22     return -ENOMEM;
23 }
24
25 input = devm_input_allocate_device(dev);
26 if (!input) {
27     dev_err(dev, "failed to allocate input device\n");
28     return -ENOMEM;
29 }
30
31 ddata->pdata = pdata;
32 ddata->input = input;
33 mutex_init(&ddata->disable_lock);
34
35 platform_set_drvdata(pdev, ddata);
36 input_set_drvdata(input, ddata);
37
38 input->name = pdata->name   : pdev->name;
39 input->phys = "gpio-keys/input0";
40 input->dev.parent = &pdev->dev;
41 input->open = gpio_keys_open;
42 input->close = gpio_keys_close;
43
44 input->id.bustype = BUS_HOST;
45 input->id.vendor = 0x0001;
46 input->id.product = 0x0001;
47 input->id.version = 0x0100;
48
49 /* Enable auto repeat feature of Linux input subsystem */
50 if (pdata->rep)
51     __set_bit(EV_REP, input->evbit);
52
53 for (i = 0; i < pdata->nbuttons; i++) {
54     const struct gpio_keys_button *button = &pdata->buttons[i];
55     struct gpio_button_data *bdata = &ddata->data[i];
56
57     error = gpio_keys_setup_key(pdev, input, bdata, button);
58     if (error)
59         return error;
60
61     if (button->wakeup)
62         wakeup = 1;
63 }
64
65 error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
66 ...
67 error = input_register_device(input);
68 ...
69 }

上述代码的第25行分配了1个输入设备,第31~47行初始化了该input_dev的一些属性,第58行注册了这个输入设备。第53~63行则初始化了所用到的GPIO,第67行完成了这个输入设备的注册。

在注册输入设备后,底层输入设备驱动的核心工作只剩下在按键、触摸等人为动作发生时报告事件。代码清单12.12列出了GPIO按键中断发生时的事件报告代码。

 1 static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
 2 {
 3 struct gpio_button_data *bdata = dev_id;
 4 const struct gpio_keys_button *button = bdata->button;
 5 struct input_dev *input = bdata->input;
 6 unsigned long flags;
 7
 8 BUG_ON(irq != bdata->irq);
 9
10 spin_lock_irqsave(&bdata->lock, flags);
11
12 if (!bdata->key_pressed) {
13     if (bdata->button->wakeup)
14         pm_wakeup_event(bdata->input->dev.parent, 0);
15
16     input_event(input, EV_KEY, button->code, 1);
17     input_sync(input);
18
19     if (!bdata->timer_debounce) {
20         input_event(input, EV_KEY, button->code, 0);
21         input_sync(input);
22         goto out;
23     }
24
25     bdata->key_pressed = true;
26 }
27
28 if (bdata->timer_debounce)
29     mod_timer(&bdata->timer,
30         jiffies + msecs_to_jiffies(bdata->timer_debounce));
31 out:
32 spin_unlock_irqrestore(&bdata->lock, flags);
33 return IRQ_HANDLED;
34 }

GPIO按键驱动通过input_event()、input_sync()这样的函数来汇报按键事件以及同步事件。从底层的GPIO按键驱动可以看出,该驱动中没有任何file_operations的动作,也没有各种I/O模型,注册进入系统也用的是input_register_device()这样的与input相关的API。这是由于与Linux VFS接口的这一部分代码全部都在drivers/input/evdev.c中实现了,代码清单12.13摘取了部分关键代码。

 1 static ssize_t evdev_read(struct file *file, char __user *buffer,
 2            size_t count, loff_t *ppos)
 3 {
 4 struct evdev_client *client = file->private_data;
 5 struct evdev *evdev = client->evdev;
 6 struct input_event event;
 7 size_t read = 0;
 8 int error;
 9
10 if (count != 0&& count < input_event_size())
11     return -EINVAL;
12
13 for (;;) {
14     if (!evdev->exist || client->revoked)
15         return -ENODEV;
16
17     if (client->packet_head == client->tail &&
18         (file->f_flags & O_NONBLOCK))
19         return -EAGAIN;
20
21     /*
22      * count == 0is special - no IO is done but we check
23      * for error conditions (see above).
24      */
25     if (count == 0)
26         break;
27
28     while (read + input_event_size() <= count &&
29            evdev_fetch_next_event(client, &event)) {
30
31         if (input_event_to_user(buffer + read, &event))
32             return -EFAULT;
33
34         read += input_event_size();
35     }
36
37     if (read)
38         break;
39
40     if (!(file->f_flags & O_NONBLOCK)) {
41         error = wait_event_interruptible(evdev->wait,
42                 client->packet_head != client->tail ||
43                 !evdev->exist || client->revoked);
44         if (error)
45             return error;
46     }
47 }
48
49 return read;
50}
51
52 static const struct file_operations evdev_fops = {
53 .owner        = THIS_MODULE,
54 .read         = evdev_read,
55 .write        = evdev_write,
56 .pol          = evdev_poll,
57 .open         = evdev_open,
58 .release      = evdev_release,
59 .unlocked_ioct= evdev_ioctl,
60 #ifdef CONFIG_COMPAT
61 .compat_ioct= evdev_ioctl_compat,
62 #endif
63 .fasync        = evdev_fasync,
64 .flush         = evdev_flush,
65 .llseek        = no_llseek,
66 };

上述代码中的17~19行在检查出是非阻塞访问后,立即返回EAGAIN错误,而第29行和第41~43行的代码则处理了阻塞的睡眠情况。回过头来想,其实gpio_keys驱动里面调用的input_event()、input_sync()有间接唤醒这个等待队列evdev->wait的功能,只不过这些代码都隐藏在其内部实现里了。

添加gpio_keys的设备树,使按键可用。

 +11 arch/arm/boot/dts/imx6ull-alientek-emmc.dts 
@@ -154,6 +154,17 @@
            default-state = "off";
        };
    };
+    gpio-keys {
+        compatible = "gpio-keys";
+        #address-cells = <1>;
+        #size-cells = <0>;
+        autorepeat;
+        key0 {
+            label = "GPIO Key Enter";
+            linux,code = <KEY_ENTER>;
+            gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
+         };
+     };

    alphaled {
        #address-cells = <1>;