summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/boards/riscv.rst2
-rw-r--r--Documentation/user/virtio.rst4
-rw-r--r--drivers/input/Kconfig7
-rw-r--r--drivers/input/Makefile1
-rw-r--r--drivers/input/input.c3
-rw-r--r--drivers/input/virtio_input.c290
-rw-r--r--include/linux/virtio.h7
-rw-r--r--include/linux/virtio_config.h62
-rw-r--r--include/linux/virtio_ring.h34
-rw-r--r--include/uapi/linux/virtio_input.h76
10 files changed, 483 insertions, 3 deletions
diff --git a/Documentation/boards/riscv.rst b/Documentation/boards/riscv.rst
index 049029b4f2..59cdc00a99 100644
--- a/Documentation/boards/riscv.rst
+++ b/Documentation/boards/riscv.rst
@@ -9,7 +9,7 @@ barebox supports both the qemu riscv32 and riscv64 ``-M virt`` boards::
make ARCH=riscv virt64_defconfig
qemu-system-riscv64 -M virt -serial stdio -kernel build/images/barebox-dt-2nd.img
-Replace ``64`` by ``32`` for 32-bit build. :ref:`virtio` over MMIO is supported and
+Replace ``64`` by ``32`` for 32-bit build. :ref:`virtio_sect` over MMIO is supported and
can be used for e.g. an extra console or to pass in a virtio-blk device::
qemu-system-riscv64 -M virt -serial stdio \
diff --git a/Documentation/user/virtio.rst b/Documentation/user/virtio.rst
index 7e125c0ca8..dde47d5f82 100644
--- a/Documentation/user/virtio.rst
+++ b/Documentation/user/virtio.rst
@@ -4,7 +4,7 @@
Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
Copyright (C) 2021, Ahmad Fatoum
-.. _virtio:
+.. _virtio_sect:
VirtIO Support
==============
@@ -35,7 +35,7 @@ queues configuration and buffer transfers are nearly identical. Both MMIO
and non-legacy PCI are supported in barebox.
The VirtIO spec defines a lots of VirtIO device types, however at present only
-block, console and RNG devices are supported.
+block, console, input and RNG devices are supported.
Build Instructions
------------------
diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
index 95aa51ebfc..ff3e9d33f6 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 36a4204d53..6c8acc6184 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/input.c b/drivers/input/input.c
index bcc8667417..1df52f56c8 100644
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -165,6 +165,9 @@ static void input_console_notify(struct input_notifier *in,
if (ic->modstate[4] || ic->modstate[5])
modstate |= 1 << 2;
+ if (ev->code >= NR_KEYS)
+ return;
+
if (modstate & (1 << 1)) {
ascii = keycode_bb_keys[ev->code];
ascii = ascii >= 'a' ? CTL_CH(ascii) : 0;
diff --git a/drivers/input/virtio_input.c b/drivers/input/virtio_input.c
new file mode 100644
index 0000000000..9c2e4d923f
--- /dev/null
+++ b/drivers/input/virtio_input.c
@@ -0,0 +1,290 @@
+// 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 <sound.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, *sts;
+ struct virtio_input_event evts[64];
+ struct bthread *bthread;
+ struct sound_card beeper;
+ unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
+};
+
+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;
+}
+
+/*
+ * On error we are losing the status update, which isn't critical as
+ * this is used for the bell.
+ */
+static int virtinput_send_status(struct sound_card *beeper, unsigned freq, unsigned duration)
+{
+ struct virtio_input *vi = container_of(beeper, struct virtio_input, beeper);
+ struct virtio_input_event *stsbuf;
+ struct virtio_sg sg[1];
+ u16 code;
+ int rc;
+
+ stsbuf = kzalloc(sizeof(*stsbuf), 0);
+ if (!stsbuf)
+ return -ENOMEM;
+
+ code = vi->sndbit[0] & BIT_MASK(SND_TONE) ? SND_TONE : SND_BELL;
+
+ stsbuf->type = cpu_to_le16(EV_SND);
+ stsbuf->code = cpu_to_le16(code);
+ stsbuf->value = cpu_to_le32(freq);
+ virtio_sg_init_one(sg, stsbuf, sizeof(*stsbuf));
+
+ rc = virtqueue_add_outbuf(vi->sts, sg, 1);
+ virtqueue_kick(vi->sts);
+
+ if (rc != 0)
+ kfree(stsbuf);
+ return rc;
+}
+
+static int virtinput_recv_status(struct virtio_input *vi)
+{
+ struct virtio_input_event *stsbuf;
+ unsigned int len;
+ int i = 0;
+
+ while ((stsbuf = virtqueue_get_buf(vi->sts, &len)) != NULL) {
+ kfree(stsbuf);
+ 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);
+ bufs += virtinput_recv_status(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_cfg_bits(struct virtio_input *vi, int select, int subsel,
+ unsigned long *bits, unsigned int bitcount)
+{
+ unsigned int bit;
+ u8 *virtio_bits;
+ u8 bytes;
+
+ bytes = virtinput_cfg_select(vi, select, subsel);
+ if (!bytes)
+ return;
+ if (bitcount > bytes * 8)
+ bitcount = bytes * 8;
+
+ /*
+ * Bitmap in virtio config space is a simple stream of bytes,
+ * with the first byte carrying bits 0-7, second bits 8-15 and
+ * so on.
+ */
+ virtio_bits = kzalloc(bytes, GFP_KERNEL);
+ if (!virtio_bits)
+ return;
+ virtio_cread_bytes(vi->vdev, offsetof(struct virtio_input_config,
+ u.bitmap),
+ virtio_bits, bytes);
+ for (bit = 0; bit < bitcount; bit++) {
+ if (virtio_bits[bit / 8] & (1 << (bit % 8)))
+ __set_bit(bit, bits);
+ }
+ kfree(virtio_bits);
+}
+
+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[2];
+ int err;
+
+
+ err = virtio_find_vqs(vi->vdev, 2, vqs);
+ if (err)
+ return err;
+
+ vi->evt = vqs[0];
+ vi->sts = vqs[1];
+
+ 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';
+
+ virtinput_cfg_bits(vi, VIRTIO_INPUT_CFG_EV_BITS, EV_SND,
+ vi->sndbit, SND_CNT);
+
+ 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;
+ }
+
+ if (IS_ENABLED(CONFIG_SOUND) &&
+ (vi->sndbit[0] & (BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE)))) {
+ struct sound_card *beeper;
+
+ beeper = &vi->beeper;
+ beeper->name = basprintf("%s/beeper0", dev_name(&vdev->dev));
+ beeper->beep = virtinput_send_status;
+
+ err = sound_card_register(&vi->beeper);
+ if (err)
+ dev_warn(&vdev->dev, "bell registration failed: %pe\n", ERR_PTR(err));
+ else
+ dev_info(&vdev->dev, "bell registered\n");
+ }
+
+ 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@redhat.com>");
+MODULE_AUTHOR("Ahmad Fatoum <a.fatoum@pengutronix.de>");
diff --git a/include/linux/virtio.h b/include/linux/virtio.h
index 8a1a80ddc8..719f45c975 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 f33cfdacaa..8160f0952f 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 c349af90ce..bdef47b0fa 100644
--- a/include/linux/virtio_ring.h
+++ b/include/linux/virtio_ring.h
@@ -181,6 +181,40 @@ 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
*
* @vq: the struct virtqueue
diff --git a/include/uapi/linux/virtio_input.h b/include/uapi/linux/virtio_input.h
new file mode 100644
index 0000000000..52084b1fb9
--- /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 */