[PATCH 2/3] input: add virtio input driver
Ahmad Fatoum
ahmad at a3f.at
Mon Mar 29 07:18:09 BST 2021
We already support Linux event codes, because they are used in the
device tree bindings for e.g. gpio-keys.
Virtio input devices report events using the same codes, so a driver
just has to shovel the codes from virtqueue into the input layer. Do so.
Signed-off-by: Ahmad Fatoum <ahmad at a3f.at>
---
drivers/input/Kconfig | 7 ++
drivers/input/Makefile | 1 +
drivers/input/virtio_input.c | 191 ++++++++++++++++++++++++++++++
include/linux/virtio.h | 7 ++
include/linux/virtio_config.h | 62 ++++++++++
include/linux/virtio_ring.h | 34 ++++++
include/uapi/linux/virtio_input.h | 76 ++++++++++++
7 files changed, 378 insertions(+)
create mode 100644 drivers/input/virtio_input.c
create mode 100644 include/uapi/linux/virtio_input.h
diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
index 95aa51ebfc9e..ff3e9d33f6d7 100644
--- a/drivers/input/Kconfig
+++ b/drivers/input/Kconfig
@@ -71,4 +71,11 @@ config INPUT_SPECIALKEYS
help
Say Y here to handle key events like KEY_RESTART and KEY_POWER.
+config VIRTIO_INPUT
+ bool "Virtio input driver"
+ depends on VIRTIO && BTHREAD
+ select INPUT
+ help
+ This driver supports virtio keyboard input devices.
+
endmenu
diff --git a/drivers/input/Makefile b/drivers/input/Makefile
index 36a4204d5308..6c8acc618427 100644
--- a/drivers/input/Makefile
+++ b/drivers/input/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_KEYBOARD_TWL6030) += twl6030_pwrbtn.o
obj-$(CONFIG_KEYBOARD_IMX_KEYPAD) += imx_keypad.o
obj-$(CONFIG_KEYBOARD_QT1070) += qt1070.o
obj-$(CONFIG_INPUT_SPECIALKEYS) += specialkeys.o
+obj-$(CONFIG_VIRTIO_INPUT) += virtio_input.o
diff --git a/drivers/input/virtio_input.c b/drivers/input/virtio_input.c
new file mode 100644
index 000000000000..406dc613dc37
--- /dev/null
+++ b/drivers/input/virtio_input.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <common.h>
+#include <bthread.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <linux/virtio_ring.h>
+#include <input/input.h>
+#include <dt-bindings/input/linux-event-codes.h>
+
+#include <uapi/linux/virtio_ids.h>
+#include <uapi/linux/virtio_input.h>
+
+struct virtio_input {
+ struct input_device idev;
+ struct virtio_device *vdev;
+ struct virtqueue *evt;
+ struct virtio_input_event evts[64];
+ struct bthread *bthread;
+};
+
+static void virtinput_queue_evtbuf(struct virtio_input *vi,
+ struct virtio_input_event *evtbuf)
+{
+ struct virtio_sg sg[1];
+ virtio_sg_init_one(sg, evtbuf, sizeof(*evtbuf));
+ virtqueue_add_inbuf(vi->evt, sg, 1);
+}
+
+static int virtinput_recv_events(struct virtio_input *vi)
+{
+ struct device_d *dev = &vi->vdev->dev;
+ struct virtio_input_event *event;
+ unsigned int len;
+ int i = 0;
+
+ while ((event = virtqueue_get_buf(vi->evt, &len)) != NULL) {
+ if (le16_to_cpu(event->type) == EV_KEY)
+ input_report_key_event(&vi->idev, le16_to_cpu(event->code),
+ le32_to_cpu(event->value));
+
+ pr_debug("\n%s: input event #%td received (type=%u, code=%u, value=%u)\n",
+ dev_name(dev),
+ event - &vi->evts[0],
+ le16_to_cpu(event->type), le16_to_cpu(event->code),
+ le32_to_cpu(event->value));
+
+ virtinput_queue_evtbuf(vi, event);
+ i++;
+ }
+
+ return i;
+}
+
+static int virtinput_poll_vqs(void *_vi)
+{
+ struct virtio_input *vi = _vi;
+
+ while (!bthread_should_stop()) {
+ int bufs = 0;
+
+ bufs += virtinput_recv_events(vi);
+
+ if (bufs)
+ virtqueue_kick(vi->evt);
+ }
+
+ return 0;
+}
+
+static u8 virtinput_cfg_select(struct virtio_input *vi,
+ u8 select, u8 subsel)
+{
+ u8 size;
+
+ virtio_cwrite_le(vi->vdev, struct virtio_input_config, select, &select);
+ virtio_cwrite_le(vi->vdev, struct virtio_input_config, subsel, &subsel);
+ virtio_cread_le(vi->vdev, struct virtio_input_config, size, &size);
+ return size;
+}
+
+static void virtinput_fill_evt(struct virtio_input *vi)
+{
+ int i, size;
+
+ size = virtqueue_get_vring_size(vi->evt);
+ if (size > ARRAY_SIZE(vi->evts))
+ size = ARRAY_SIZE(vi->evts);
+ for (i = 0; i < size; i++)
+ virtinput_queue_evtbuf(vi, &vi->evts[i]);
+ virtqueue_kick(vi->evt);
+}
+
+static int virtinput_init_vqs(struct virtio_input *vi)
+{
+ struct virtqueue *vqs[1];
+ int err;
+
+
+ err = virtio_find_vqs(vi->vdev, 1, vqs);
+ if (err)
+ return err;
+
+ vi->evt = vqs[0];
+
+ return 0;
+}
+
+static int virtinput_probe(struct virtio_device *vdev)
+{
+ struct virtio_input *vi;
+ char name[64];
+ size_t size;
+ int err;
+
+ if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1))
+ return -ENODEV;
+
+ vi = kzalloc(sizeof(*vi), GFP_KERNEL);
+ if (!vi)
+ return -ENOMEM;
+
+ vdev->priv = vi;
+ vi->vdev = vdev;
+
+ err = virtinput_init_vqs(vi);
+ if (err)
+ goto err_init_vq;
+
+ size = virtinput_cfg_select(vi, VIRTIO_INPUT_CFG_ID_NAME, 0);
+ virtio_cread_bytes(vi->vdev, offsetof(struct virtio_input_config, u.string),
+ name, min(size, sizeof(name)));
+ name[size] = '\0';
+
+ virtio_device_ready(vdev);
+
+ err = input_device_register(&vi->idev);
+ if (err)
+ goto err_input_register;
+
+ virtinput_fill_evt(vi);
+
+ vi->bthread = bthread_run(virtinput_poll_vqs, vi,
+ "%s/input0", dev_name(&vdev->dev));
+ if (!vi->bthread) {
+ err = -ENOMEM;
+ goto err_bthread_run;
+ }
+
+ dev_info(&vdev->dev, "'%s' probed\n", name);
+
+ return 0;
+
+err_bthread_run:
+ bthread_free(vi->bthread);
+err_input_register:
+ vdev->config->del_vqs(vdev);
+err_init_vq:
+ kfree(vi);
+ return err;
+}
+
+static void virtinput_remove(struct virtio_device *vdev)
+{
+ struct virtio_input *vi = vdev->priv;
+
+ bthread_stop(vi->bthread);
+ bthread_free(vi->bthread);
+
+ vdev->config->reset(vdev);
+ vdev->config->del_vqs(vdev);
+ kfree(vi);
+}
+
+static const struct virtio_device_id id_table[] = {
+ { VIRTIO_ID_INPUT, VIRTIO_DEV_ANY_ID },
+ { 0 },
+};
+
+static struct virtio_driver virtio_input_driver = {
+ .driver.name = "virtio_input",
+ .id_table = id_table,
+ .probe = virtinput_probe,
+ .remove = virtinput_remove,
+};
+device_virtio_driver(virtio_input_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Virtio input device driver");
+MODULE_AUTHOR("Gerd Hoffmann <kraxel at redhat.com>");
+MODULE_AUTHOR("Ahmad Fatoum <a.fatoum at pengutronix.de>");
diff --git a/include/linux/virtio.h b/include/linux/virtio.h
index 8a1a80ddc820..719f45c97560 100644
--- a/include/linux/virtio.h
+++ b/include/linux/virtio.h
@@ -24,6 +24,13 @@ struct virtio_sg {
size_t length;
};
+static inline void virtio_sg_init_one(struct virtio_sg *sg,
+ void *addr, size_t length)
+{
+ sg[0].addr = addr;
+ sg[0].length = length;
+}
+
struct virtio_config_ops;
/**
diff --git a/include/linux/virtio_config.h b/include/linux/virtio_config.h
index f33cfdacaa2c..8160f0952f13 100644
--- a/include/linux/virtio_config.h
+++ b/include/linux/virtio_config.h
@@ -458,6 +458,68 @@ static inline void virtio_cwrite64(struct virtio_device *vdev,
_r; \
})
+/*
+ * Nothing virtio-specific about these, but let's worry about generalizing
+ * these later.
+ */
+#define virtio_le_to_cpu(x) \
+ _Generic((x), \
+ __u8: (u8)(x), \
+ __le16: (u16)le16_to_cpu(x), \
+ __le32: (u32)le32_to_cpu(x), \
+ __le64: (u64)le64_to_cpu(x) \
+ )
+
+#define virtio_cpu_to_le(x, m) \
+ _Generic((m), \
+ __u8: (x), \
+ __le16: cpu_to_le16(x), \
+ __le32: cpu_to_le32(x), \
+ __le64: cpu_to_le64(x) \
+ )
+
+/* LE (e.g. modern) Config space accessors. */
+#define virtio_cread_le(vdev, structname, member, ptr) \
+ do { \
+ typeof(((structname*)0)->member) virtio_cread_v; \
+ \
+ /* Sanity check: must match the member's type */ \
+ typecheck(typeof(virtio_le_to_cpu(virtio_cread_v)), *(ptr)); \
+ \
+ switch (sizeof(virtio_cread_v)) { \
+ case 1: \
+ case 2: \
+ case 4: \
+ vdev->config->get_config((vdev), \
+ offsetof(structname, member), \
+ &virtio_cread_v, \
+ sizeof(virtio_cread_v)); \
+ break; \
+ default: \
+ __virtio_cread_many((vdev), \
+ offsetof(structname, member), \
+ &virtio_cread_v, \
+ 1, \
+ sizeof(virtio_cread_v)); \
+ break; \
+ } \
+ *(ptr) = virtio_le_to_cpu(virtio_cread_v); \
+ } while(0)
+
+#define virtio_cwrite_le(vdev, structname, member, ptr) \
+ do { \
+ typeof(((structname*)0)->member) virtio_cwrite_v = \
+ virtio_cpu_to_le(*(ptr), ((structname*)0)->member); \
+ \
+ /* Sanity check: must match the member's type */ \
+ typecheck(typeof(virtio_le_to_cpu(virtio_cwrite_v)), *(ptr)); \
+ \
+ vdev->config->set_config((vdev), offsetof(structname, member), \
+ &virtio_cwrite_v, \
+ sizeof(virtio_cwrite_v)); \
+ } while(0)
+
+
#ifdef CONFIG_ARCH_HAS_RESTRICTED_VIRTIO_MEMORY_ACCESS
int arch_has_restricted_virtio_memory_access(void);
#else
diff --git a/include/linux/virtio_ring.h b/include/linux/virtio_ring.h
index c349af90ce50..bdef47b0fa6c 100644
--- a/include/linux/virtio_ring.h
+++ b/include/linux/virtio_ring.h
@@ -180,6 +180,40 @@ struct virtio_sg;
int virtqueue_add(struct virtqueue *vq, struct virtio_sg *sgs[],
unsigned int out_sgs, unsigned int in_sgs);
+/**
+ * virtqueue_add_outbuf - expose output buffers to other end
+ * @vq: the struct virtqueue we're talking about.
+ * @sg: scatterlist (must be well-formed and terminated!)
+ * @num: the number of entries in @sg readable by other side
+ *
+ * Caller must ensure we don't call this with other virtqueue operations
+ * at the same time (except where noted).
+ *
+ * Returns zero or a negative error (ie. ENOSPC, ENOMEM, EIO).
+ */
+static inline int virtqueue_add_outbuf(struct virtqueue *vq,
+ struct virtio_sg *sg, unsigned int num)
+{
+ return virtqueue_add(vq, &sg, num, 0);
+}
+
+/**
+ * virtqueue_add_inbuf - expose input buffers to other end
+ * @vq: the struct virtqueue we're talking about.
+ * @sg: scatterlist (must be well-formed and terminated!)
+ * @num: the number of entries in @sg writable by other side
+ *
+ * Caller must ensure we don't call this with other virtqueue operations
+ * at the same time (except where noted).
+ *
+ * Returns zero or a negative error (ie. ENOSPC, ENOMEM, EIO).
+ */
+static inline int virtqueue_add_inbuf(struct virtqueue *vq,
+ struct virtio_sg *sg, unsigned int num)
+{
+ return virtqueue_add(vq, &sg, 0, num);
+}
+
/**
* virtqueue_kick - update after add_buf
*
diff --git a/include/uapi/linux/virtio_input.h b/include/uapi/linux/virtio_input.h
new file mode 100644
index 000000000000..52084b1fb965
--- /dev/null
+++ b/include/uapi/linux/virtio_input.h
@@ -0,0 +1,76 @@
+#ifndef _LINUX_VIRTIO_INPUT_H
+#define _LINUX_VIRTIO_INPUT_H
+/* This header is BSD licensed so anyone can use the definitions to implement
+ * compatible drivers/servers.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of IBM nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL IBM OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE. */
+
+#include <linux/types.h>
+
+enum virtio_input_config_select {
+ VIRTIO_INPUT_CFG_UNSET = 0x00,
+ VIRTIO_INPUT_CFG_ID_NAME = 0x01,
+ VIRTIO_INPUT_CFG_ID_SERIAL = 0x02,
+ VIRTIO_INPUT_CFG_ID_DEVIDS = 0x03,
+ VIRTIO_INPUT_CFG_PROP_BITS = 0x10,
+ VIRTIO_INPUT_CFG_EV_BITS = 0x11,
+ VIRTIO_INPUT_CFG_ABS_INFO = 0x12,
+};
+
+struct virtio_input_absinfo {
+ __le32 min;
+ __le32 max;
+ __le32 fuzz;
+ __le32 flat;
+ __le32 res;
+};
+
+struct virtio_input_devids {
+ __le16 bustype;
+ __le16 vendor;
+ __le16 product;
+ __le16 version;
+};
+
+struct virtio_input_config {
+ __u8 select;
+ __u8 subsel;
+ __u8 size;
+ __u8 reserved[5];
+ union {
+ char string[128];
+ __u8 bitmap[128];
+ struct virtio_input_absinfo abs;
+ struct virtio_input_devids ids;
+ } u;
+};
+
+struct virtio_input_event {
+ __le16 type;
+ __le16 code;
+ __le32 value;
+};
+
+#endif /* _LINUX_VIRTIO_INPUT_H */
--
2.30.0
More information about the barebox
mailing list