[Concept,1/3] virtio: Add input-device driver
Commit Message
From: Simon Glass <sjg@chromium.org>
Add a virtio input device driver that supports tablets and mice using
the existing mouse uclass. The driver handles absolute and relative
coordinates.
Mouse buttons are supported (left, right, middle). EV_SYN events are
skipped.
Co-developed-by: Claude <noreply@anthropic.com>
Signed-off-by: Simon Glass <sjg@chromium.org>
---
drivers/virtio/Kconfig | 13 ++
drivers/virtio/Makefile | 1 +
drivers/virtio/virtio_input.c | 369 ++++++++++++++++++++++++++++++++++
3 files changed, 383 insertions(+)
create mode 100644 drivers/virtio/virtio_input.c
Comments
Am 15. September 2025 21:35:33 MESZ schrieb Simon Glass <sjg@u-boot.org>:
>From: Simon Glass <sjg@chromium.org>
>
>Add a virtio input device driver that supports tablets and mice using
>the existing mouse uclass. The driver handles absolute and relative
>coordinates.
>
>Mouse buttons are supported (left, right, middle). EV_SYN events are
>skipped.
>
>Co-developed-by: Claude <noreply@anthropic.com>
>Signed-off-by: Simon Glass <sjg@chromium.org>
>---
>
> drivers/virtio/Kconfig | 13 ++
> drivers/virtio/Makefile | 1 +
> drivers/virtio/virtio_input.c | 369 ++++++++++++++++++++++++++++++++++
> 3 files changed, 383 insertions(+)
> create mode 100644 drivers/virtio/virtio_input.c
>
>diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
>index eb4ff368f12..471f40b85aa 100644
>--- a/drivers/virtio/Kconfig
>+++ b/drivers/virtio/Kconfig
>@@ -110,4 +110,17 @@ config VIRTIO_SCSI
> A specification for the protocol is available at
> https://docs.oasis-open.org/virtio/virtio/v1.3/virtio-v1.3.html
>
>+config VIRTIO_INPUT
>+ bool "Input device driver for virtio devices"
>+ depends on VIRTIO
>+ select MOUSE
>+ default y
>+ help
>+ This driver provides support for virtio-based input devices such as
>+ tablets, mice and keyboards. It implements the mouse uclass for
>+ tablet and pointer devices.
>+
>+ A specification for the protocol is available at
>+ https://docs.oasis-open.org/virtio/virtio/v1.3/virtio-v1.3.html
>+
> endmenu
>diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile
>index f101536a863..9183dd56a79 100644
>--- a/drivers/virtio/Makefile
>+++ b/drivers/virtio/Makefile
>@@ -14,3 +14,4 @@ obj-$(CONFIG_VIRTIO_BLK) += virtio_blk.o
> obj-$(CONFIG_VIRTIO_RNG) += virtio_rng.o
> obj-$(CONFIG_VIRTIO_FS) += fs.o fs_dir.o fs_file.o fs_compat.o
> obj-$(CONFIG_VIRTIO_SCSI) += virtio_scsi.o
>+obj-$(CONFIG_VIRTIO_INPUT) += virtio_input.o
>diff --git a/drivers/virtio/virtio_input.c b/drivers/virtio/virtio_input.c
>new file mode 100644
>index 00000000000..5edc59b6037
>--- /dev/null
>+++ b/drivers/virtio/virtio_input.c
>@@ -0,0 +1,369 @@
>+// SPDX-License-Identifier: GPL-2.0+
>+/*
>+ * VirtIO Input (tablet) driver for U-Boot
>+ *
>+ * Copyright 2025 Simon Glass <sjg@chromium.org>
>+ */
>+
>+#define LOG_CATEGORY UCLASS_VIRTIO
>+
>+#include <dm.h>
>+#include <errno.h>
>+#include <mouse.h>
>+#include <virtio_types.h>
>+#include <virtio.h>
>+#include <virtio_ring.h>
>+#include <linux/byteorder/little_endian.h>
>+#include <dt-bindings/input/linux-event-codes.h>
>+
>+#define VIRTIO_INPUT_CFG_UNSET 0x00
>+#define VIRTIO_INPUT_CFG_ID_NAME 0x01
>+#define VIRTIO_INPUT_CFG_ID_SERIAL 0x02
>+#define VIRTIO_INPUT_CFG_ID_DEVIDS 0x03
>+#define VIRTIO_INPUT_CFG_PROP_BITS 0x10
>+#define VIRTIO_INPUT_CFG_EV_BITS 0x11
>+#define VIRTIO_INPUT_CFG_ABS_INFO 0x12
>+
>+/* absolute axis information for input devices */
>+struct virtio_input_absinfo {
>+ __le32 min; /* minimum value */
>+ __le32 max; /* maximum value */
>+ __le32 fuzz; /* fuzz value for noise filtering */
>+ __le32 flat; /* flat area around center */
>+ __le32 res; /* resolution in units per mm */
>+};
>+
>+/* device identification information */
>+struct virtio_input_devids {
>+ __le16 bustype; /* bus type identifier */
>+ __le16 vendor; /* vendor identifier */
>+ __le16 product; /* product identifier */
>+ __le16 version; /* version number */
>+};
>+
>+/* configuration space for querying device capabilities */
>+struct vinp_config {
>+ __u8 select; /* configuration item to select */
>+ __u8 subsel; /* sub-selection within item */
>+ __u8 size; /* size of returned data */
>+ __u8 reserved[5]; /* padding */
>+ union {
>+ char string[128]; /* for name/serial strings */
>+ __u8 bitmap[128]; /* for capability bitmaps */
>+ struct virtio_input_absinfo abs; /* for absolute axis info */
>+ struct virtio_input_devids ids; /* for device IDs */
>+ } u;
>+};
>+
>+/* input event structure (follows Linux input_event format) */
>+struct virtio_input_event {
>+ __le16 type; /* event type (EV_KEY, EV_ABS, EV_SYN, etc.) */
>+ __le16 code; /* event code (button/axis identifier) */
>+ __le32 value; /* event value */
>+};
>+
>+#define BUF_COUNT 8
>+#define BUF_SIZE (16 * sizeof(struct virtio_input_event))
>+#define COORD_MAX 0xffff
>+
>+/* private data for virtio input device */
>+struct virtio_input_priv {
>+ struct virtqueue *event_vq; /* event virtqueue */
>+ struct virtqueue *status_vq; /* status virtqueue */
>+ char event_bufs[BUF_COUNT][BUF_SIZE]; /* event buffers */
>+ bool rx_running; /* true if buffers are set up */
>+ int abs_x_max; /* maximum X coordinate */
>+ int abs_y_max; /* maximum Y coordinate */
>+ int button_state; /* current button state */
>+ int last_x; /* last X coordinate */
>+ int last_y; /* last Y coordinate */
>+};
>+
>+static int virtio_input_free_buffer(struct udevice *dev, void *buf)
>+{
>+ struct virtio_input_priv *priv = dev_get_priv(dev);
>+ struct virtio_sg sg = { buf, BUF_SIZE };
>+ struct virtio_sg *sgs[] = { &sg };
>+
>+ /* put the buffer back to the event ring */
>+ virtqueue_add(priv->event_vq, sgs, 0, 1);
>+
>+ return 0;
>+}
>+
>+static int process_event(struct virtio_input_priv *priv,
>+ struct virtio_input_event *vio_event,
>+ struct mouse_event *evt)
>+{
>+ u16 type = le16_to_cpu(vio_event->type);
>+ u16 code = le16_to_cpu(vio_event->code);
>+ u32 value = le32_to_cpu(vio_event->value);
>+
>+ /* skip EV_SYN events immediately */
>+ if (type == EV_SYN)
>+ return -EAGAIN;
>+
>+ log_debug("processing event: type=%d code=%d value=%d\n", type, code,
>+ value);
>+
>+ switch (type) {
>+ case EV_ABS:
>+ if (code == ABS_X)
>+ priv->last_x = value;
>+ else if (code == ABS_Y)
>+ priv->last_y = value;
>+
>+ /* report motion event */
>+ evt->type = MOUSE_EV_MOTION;
>+ evt->motion.state = priv->button_state;
>+ evt->motion.x = (priv->last_x * COORD_MAX) / priv->abs_x_max;
>+ evt->motion.y = (priv->last_y * COORD_MAX) / priv->abs_y_max;
>+ evt->motion.xrel = 0; /* Absolute mode */
>+ evt->motion.yrel = 0;
>+ return 0;
>+
>+ case EV_KEY:
>+ switch (code) {
>+ case BTN_LEFT:
>+ evt->type = MOUSE_EV_BUTTON;
>+ evt->button.button = BUTTON_LEFT;
>+ evt->button.press_state = value ? BUTTON_PRESSED :
>+ BUTTON_RELEASED;
>+ evt->button.clicks = 1;
>+ evt->button.x = (priv->last_x * COORD_MAX) /
>+ priv->abs_x_max;
>+ evt->button.y = (priv->last_y * COORD_MAX) /
>+ priv->abs_y_max;
>+
>+ if (evt->button.press_state == BUTTON_PRESSED)
>+ priv->button_state |= BUTTON_LEFT;
>+ else
>+ priv->button_state &= ~BUTTON_LEFT;
>+ return 0;
>+ case BTN_RIGHT:
>+ evt->type = MOUSE_EV_BUTTON;
>+ evt->button.button = BUTTON_RIGHT;
>+ evt->button.press_state = value ? BUTTON_PRESSED :
>+ BUTTON_RELEASED;
>+ evt->button.clicks = 1;
>+ evt->button.x = (priv->last_x * COORD_MAX) /
>+ priv->abs_x_max;
>+ evt->button.y = (priv->last_y * COORD_MAX) /
>+ priv->abs_y_max;
>+
>+ if (evt->button.press_state == BUTTON_PRESSED)
>+ priv->button_state |= BUTTON_RIGHT;
>+ else
>+ priv->button_state &= ~BUTTON_RIGHT;
>+ return 0;
>+ case BTN_MIDDLE:
>+ evt->type = MOUSE_EV_BUTTON;
>+ evt->button.button = BUTTON_MIDDLE;
>+ evt->button.press_state = value ? BUTTON_PRESSED :
>+ BUTTON_RELEASED;
>+ evt->button.clicks = 1;
>+ evt->button.x = (priv->last_x * COORD_MAX) /
>+ priv->abs_x_max;
>+ evt->button.y = (priv->last_y * COORD_MAX) /
>+ priv->abs_y_max;
>+
>+ if (evt->button.press_state == BUTTON_PRESSED)
>+ priv->button_state |= BUTTON_MIDDLE;
>+ else
>+ priv->button_state &= ~BUTTON_MIDDLE;
>+ return 0;
>+ }
>+ break;
>+ }
>+
>+ return -EAGAIN;
>+}
>+
>+static int virtio_input_start(struct udevice *dev)
>+{
>+ struct virtio_input_priv *priv = dev_get_priv(dev);
>+
>+ if (!priv->rx_running) {
>+ struct virtio_sg sg;
>+ struct virtio_sg *sgs[] = { &sg };
>+ int i;
>+
>+ /* setup event buffers */
>+ sg.length = BUF_SIZE;
>+
>+ /* add all buffers to the event queue */
>+ for (i = 0; i < BUF_COUNT; i++) {
>+ sg.addr = priv->event_bufs[i];
>+ virtqueue_add(priv->event_vq, sgs, 0, 1);
>+ }
>+
>+ virtqueue_kick(priv->event_vq);
>+
>+ /* setup the event queue only once */
>+ priv->rx_running = true;
>+ log_debug("VirtIO input buffers initialized\n");
>+ }
>+
>+ return 0;
>+}
>+
>+static int virtio_input_get_event(struct udevice *dev, struct mouse_event *evt)
>+{
>+ struct virtio_input_priv *priv = dev_get_priv(dev);
>+ struct virtio_input_event *vio_event;
>+ int i, num_events, ret;
>+ void *buf;
>+ uint len;
>+
>+ /* ensure buffers are setup */
>+ ret = virtio_input_start(dev);
>+ if (ret)
>+ return ret;
>+
>+ log_debug("starting\n");
>+
>+ /* check for ready buffer */
>+ buf = virtqueue_get_buf(priv->event_vq, &len);
>+ if (!buf) {
>+ log_debug("No events available\n");
>+ return -EAGAIN;
>+ }
>+
>+ log_debug("got buffer back: addr=%p len=%d\n", buf, len);
>+ if (len < sizeof(struct virtio_input_event)) {
>+ log_debug("Invalid event length: %d\n", len);
>+ /* put buffer back */
>+ virtio_input_free_buffer(dev, buf);
>+ return -EAGAIN;
>+ }
>+
>+ /* process all events in the buffer */
>+ num_events = len / sizeof(struct virtio_input_event);
>+ vio_event = (struct virtio_input_event *)buf;
>+
>+ for (i = 0; i < num_events; i++) {
>+ ret = process_event(priv, &vio_event[i], evt);
>+ if (!ret) {
>+ /*
>+ * found a valid event to return, put buffer back
>+ * Note: this loses any remaining events in the buffer,
>+ * but for input devices this is acceptable
>+ */
>+ virtio_input_free_buffer(dev, buf);
>+ return 0;
>+ }
>+ /* -EAGAIN means continue to next event */
>+ }
>+
>+ /* no useful events found, put buffer back */
>+ virtio_input_free_buffer(dev, buf);
>+ evt->type = MOUSE_EV_NULL;
>+
>+ return -EAGAIN;
>+}
>+
>+static int virtio_input_probe(struct udevice *dev)
>+{
>+ struct virtio_input_priv *priv = dev_get_priv(dev);
>+ struct virtqueue *vqs[2];
>+ struct vinp_config cfg;
>+ int ret;
>+
>+ ret = virtio_find_vqs(dev, 2, vqs);
>+ if (ret) {
>+ log_debug("Failed to find virtqueues: %d\n", ret);
>+ return ret;
>+ }
>+
>+ priv->event_vq = vqs[0];
>+ priv->status_vq = vqs[1];
>+
>+ /* check what event types this device supports */
>+ cfg.select = VIRTIO_INPUT_CFG_EV_BITS;
>+ cfg.subsel = EV_KEY;
>+ virtio_cwrite8(dev, offsetof(struct vinp_config, select), cfg.select);
>+ virtio_cwrite8(dev, offsetof(struct vinp_config, subsel), cfg.subsel);
>+ virtio_cread_bytes(dev, offsetof(struct vinp_config, u.bitmap),
>+ cfg.u.bitmap, 16);
>+ log_debug("EV_KEY bitmap: %02x %02x %02x %02x\n", cfg.u.bitmap[0],
>+ cfg.u.bitmap[1], cfg.u.bitmap[2], cfg.u.bitmap[3]);
>+
>+ cfg.select = VIRTIO_INPUT_CFG_EV_BITS;
>+ cfg.subsel = EV_REL;
>+ virtio_cwrite8(dev, offsetof(struct vinp_config, select), cfg.select);
>+ virtio_cwrite8(dev, offsetof(struct vinp_config, subsel), cfg.subsel);
>+ virtio_cread_bytes(dev, offsetof(struct vinp_config, u.bitmap),
>+ cfg.u.bitmap, 16);
>+ log_debug("EV_REL bitmap: %02x %02x %02x %02x\n", cfg.u.bitmap[0],
>+ cfg.u.bitmap[1], cfg.u.bitmap[2], cfg.u.bitmap[3]);
>+
>+ /* check if this device supports absolute coordinates (tablet) */
>+ cfg.select = VIRTIO_INPUT_CFG_EV_BITS;
>+ cfg.subsel = EV_ABS;
>+ virtio_cwrite8(dev, offsetof(struct vinp_config, select), cfg.select);
>+ virtio_cwrite8(dev, offsetof(struct vinp_config, subsel), cfg.subsel);
>+
>+ /* read the bitmap to see if ABS_X and ABS_Y are supported */
>+ virtio_cread_bytes(dev, offsetof(struct vinp_config, u.bitmap),
>+ cfg.u.bitmap, 16);
>+
>+ log_debug("EV_ABS bitmap: %02x %02x %02x %02x\n", cfg.u.bitmap[0],
>+ cfg.u.bitmap[1], cfg.u.bitmap[2], cfg.u.bitmap[3]);
>+
>+ /* check if ABS_X (0) and ABS_Y (1) bits are set */
>+ if ((cfg.u.bitmap[0] & (1 << 0)) &&
>+ (cfg.u.bitmap[0] & (1 << 1))) {
>+ /* get ABS_X range */
>+ cfg.select = VIRTIO_INPUT_CFG_ABS_INFO;
>+ cfg.subsel = ABS_X;
>+ virtio_cwrite8(dev, offsetof(struct vinp_config, select),
>+ cfg.select);
>+ virtio_cwrite8(dev, offsetof(struct vinp_config, subsel),
>+ cfg.subsel);
>+ virtio_cread_bytes(dev, offsetof(struct vinp_config, u.abs),
>+ &cfg.u.abs, sizeof(cfg.u.abs));
>+ priv->abs_x_max = le32_to_cpu(cfg.u.abs.max);
>+
>+ /* get ABS_Y range */
>+ cfg.subsel = ABS_Y;
>+ virtio_cwrite8(dev, offsetof(struct vinp_config, subsel),
>+ cfg.subsel);
>+ virtio_cread_bytes(dev, offsetof(struct vinp_config, u.abs),
>+ &cfg.u.abs, sizeof(cfg.u.abs));
>+ priv->abs_y_max = le32_to_cpu(cfg.u.abs.max);
>+
>+ log_debug("tablet: X range 0-%d, Y range 0-%d\n",
>+ priv->abs_x_max, priv->abs_y_max);
>+ } else {
>+ /* no absolute coordinates, use default range */
>+ priv->abs_x_max = 32767;
>+ priv->abs_y_max = 32767;
Wouldn't we always scale absolute coordinates to the video size?
Best regards
Heinrich
>+ log_debug("table: No absolute coords, using relative mode\n");
>+ }
>+
>+ return 0;
>+}
>+
>+static int virtio_input_bind(struct udevice *dev)
>+{
>+ struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent);
>+
>+ /* indicate what driver features we support */
>+ virtio_driver_features_init(uc_priv, NULL, 0, NULL, 0);
>+
>+ return 0;
>+}
>+
>+static const struct mouse_ops virtio_input_ops = {
>+ .get_event = virtio_input_get_event,
>+};
>+
>+U_BOOT_DRIVER(virtio_input) = {
>+ .name = VIRTIO_INPUT_DRV_NAME,
>+ .id = UCLASS_MOUSE,
>+ .bind = virtio_input_bind,
>+ .probe = virtio_input_probe,
>+ .ops = &virtio_input_ops,
>+ .priv_auto = sizeof(struct virtio_input_priv),
>+ .flags = DM_FLAG_ACTIVE_DMA,
>+};
@@ -110,4 +110,17 @@ config VIRTIO_SCSI
A specification for the protocol is available at
https://docs.oasis-open.org/virtio/virtio/v1.3/virtio-v1.3.html
+config VIRTIO_INPUT
+ bool "Input device driver for virtio devices"
+ depends on VIRTIO
+ select MOUSE
+ default y
+ help
+ This driver provides support for virtio-based input devices such as
+ tablets, mice and keyboards. It implements the mouse uclass for
+ tablet and pointer devices.
+
+ A specification for the protocol is available at
+ https://docs.oasis-open.org/virtio/virtio/v1.3/virtio-v1.3.html
+
endmenu
@@ -14,3 +14,4 @@ obj-$(CONFIG_VIRTIO_BLK) += virtio_blk.o
obj-$(CONFIG_VIRTIO_RNG) += virtio_rng.o
obj-$(CONFIG_VIRTIO_FS) += fs.o fs_dir.o fs_file.o fs_compat.o
obj-$(CONFIG_VIRTIO_SCSI) += virtio_scsi.o
+obj-$(CONFIG_VIRTIO_INPUT) += virtio_input.o
new file mode 100644
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * VirtIO Input (tablet) driver for U-Boot
+ *
+ * Copyright 2025 Simon Glass <sjg@chromium.org>
+ */
+
+#define LOG_CATEGORY UCLASS_VIRTIO
+
+#include <dm.h>
+#include <errno.h>
+#include <mouse.h>
+#include <virtio_types.h>
+#include <virtio.h>
+#include <virtio_ring.h>
+#include <linux/byteorder/little_endian.h>
+#include <dt-bindings/input/linux-event-codes.h>
+
+#define VIRTIO_INPUT_CFG_UNSET 0x00
+#define VIRTIO_INPUT_CFG_ID_NAME 0x01
+#define VIRTIO_INPUT_CFG_ID_SERIAL 0x02
+#define VIRTIO_INPUT_CFG_ID_DEVIDS 0x03
+#define VIRTIO_INPUT_CFG_PROP_BITS 0x10
+#define VIRTIO_INPUT_CFG_EV_BITS 0x11
+#define VIRTIO_INPUT_CFG_ABS_INFO 0x12
+
+/* absolute axis information for input devices */
+struct virtio_input_absinfo {
+ __le32 min; /* minimum value */
+ __le32 max; /* maximum value */
+ __le32 fuzz; /* fuzz value for noise filtering */
+ __le32 flat; /* flat area around center */
+ __le32 res; /* resolution in units per mm */
+};
+
+/* device identification information */
+struct virtio_input_devids {
+ __le16 bustype; /* bus type identifier */
+ __le16 vendor; /* vendor identifier */
+ __le16 product; /* product identifier */
+ __le16 version; /* version number */
+};
+
+/* configuration space for querying device capabilities */
+struct vinp_config {
+ __u8 select; /* configuration item to select */
+ __u8 subsel; /* sub-selection within item */
+ __u8 size; /* size of returned data */
+ __u8 reserved[5]; /* padding */
+ union {
+ char string[128]; /* for name/serial strings */
+ __u8 bitmap[128]; /* for capability bitmaps */
+ struct virtio_input_absinfo abs; /* for absolute axis info */
+ struct virtio_input_devids ids; /* for device IDs */
+ } u;
+};
+
+/* input event structure (follows Linux input_event format) */
+struct virtio_input_event {
+ __le16 type; /* event type (EV_KEY, EV_ABS, EV_SYN, etc.) */
+ __le16 code; /* event code (button/axis identifier) */
+ __le32 value; /* event value */
+};
+
+#define BUF_COUNT 8
+#define BUF_SIZE (16 * sizeof(struct virtio_input_event))
+#define COORD_MAX 0xffff
+
+/* private data for virtio input device */
+struct virtio_input_priv {
+ struct virtqueue *event_vq; /* event virtqueue */
+ struct virtqueue *status_vq; /* status virtqueue */
+ char event_bufs[BUF_COUNT][BUF_SIZE]; /* event buffers */
+ bool rx_running; /* true if buffers are set up */
+ int abs_x_max; /* maximum X coordinate */
+ int abs_y_max; /* maximum Y coordinate */
+ int button_state; /* current button state */
+ int last_x; /* last X coordinate */
+ int last_y; /* last Y coordinate */
+};
+
+static int virtio_input_free_buffer(struct udevice *dev, void *buf)
+{
+ struct virtio_input_priv *priv = dev_get_priv(dev);
+ struct virtio_sg sg = { buf, BUF_SIZE };
+ struct virtio_sg *sgs[] = { &sg };
+
+ /* put the buffer back to the event ring */
+ virtqueue_add(priv->event_vq, sgs, 0, 1);
+
+ return 0;
+}
+
+static int process_event(struct virtio_input_priv *priv,
+ struct virtio_input_event *vio_event,
+ struct mouse_event *evt)
+{
+ u16 type = le16_to_cpu(vio_event->type);
+ u16 code = le16_to_cpu(vio_event->code);
+ u32 value = le32_to_cpu(vio_event->value);
+
+ /* skip EV_SYN events immediately */
+ if (type == EV_SYN)
+ return -EAGAIN;
+
+ log_debug("processing event: type=%d code=%d value=%d\n", type, code,
+ value);
+
+ switch (type) {
+ case EV_ABS:
+ if (code == ABS_X)
+ priv->last_x = value;
+ else if (code == ABS_Y)
+ priv->last_y = value;
+
+ /* report motion event */
+ evt->type = MOUSE_EV_MOTION;
+ evt->motion.state = priv->button_state;
+ evt->motion.x = (priv->last_x * COORD_MAX) / priv->abs_x_max;
+ evt->motion.y = (priv->last_y * COORD_MAX) / priv->abs_y_max;
+ evt->motion.xrel = 0; /* Absolute mode */
+ evt->motion.yrel = 0;
+ return 0;
+
+ case EV_KEY:
+ switch (code) {
+ case BTN_LEFT:
+ evt->type = MOUSE_EV_BUTTON;
+ evt->button.button = BUTTON_LEFT;
+ evt->button.press_state = value ? BUTTON_PRESSED :
+ BUTTON_RELEASED;
+ evt->button.clicks = 1;
+ evt->button.x = (priv->last_x * COORD_MAX) /
+ priv->abs_x_max;
+ evt->button.y = (priv->last_y * COORD_MAX) /
+ priv->abs_y_max;
+
+ if (evt->button.press_state == BUTTON_PRESSED)
+ priv->button_state |= BUTTON_LEFT;
+ else
+ priv->button_state &= ~BUTTON_LEFT;
+ return 0;
+ case BTN_RIGHT:
+ evt->type = MOUSE_EV_BUTTON;
+ evt->button.button = BUTTON_RIGHT;
+ evt->button.press_state = value ? BUTTON_PRESSED :
+ BUTTON_RELEASED;
+ evt->button.clicks = 1;
+ evt->button.x = (priv->last_x * COORD_MAX) /
+ priv->abs_x_max;
+ evt->button.y = (priv->last_y * COORD_MAX) /
+ priv->abs_y_max;
+
+ if (evt->button.press_state == BUTTON_PRESSED)
+ priv->button_state |= BUTTON_RIGHT;
+ else
+ priv->button_state &= ~BUTTON_RIGHT;
+ return 0;
+ case BTN_MIDDLE:
+ evt->type = MOUSE_EV_BUTTON;
+ evt->button.button = BUTTON_MIDDLE;
+ evt->button.press_state = value ? BUTTON_PRESSED :
+ BUTTON_RELEASED;
+ evt->button.clicks = 1;
+ evt->button.x = (priv->last_x * COORD_MAX) /
+ priv->abs_x_max;
+ evt->button.y = (priv->last_y * COORD_MAX) /
+ priv->abs_y_max;
+
+ if (evt->button.press_state == BUTTON_PRESSED)
+ priv->button_state |= BUTTON_MIDDLE;
+ else
+ priv->button_state &= ~BUTTON_MIDDLE;
+ return 0;
+ }
+ break;
+ }
+
+ return -EAGAIN;
+}
+
+static int virtio_input_start(struct udevice *dev)
+{
+ struct virtio_input_priv *priv = dev_get_priv(dev);
+
+ if (!priv->rx_running) {
+ struct virtio_sg sg;
+ struct virtio_sg *sgs[] = { &sg };
+ int i;
+
+ /* setup event buffers */
+ sg.length = BUF_SIZE;
+
+ /* add all buffers to the event queue */
+ for (i = 0; i < BUF_COUNT; i++) {
+ sg.addr = priv->event_bufs[i];
+ virtqueue_add(priv->event_vq, sgs, 0, 1);
+ }
+
+ virtqueue_kick(priv->event_vq);
+
+ /* setup the event queue only once */
+ priv->rx_running = true;
+ log_debug("VirtIO input buffers initialized\n");
+ }
+
+ return 0;
+}
+
+static int virtio_input_get_event(struct udevice *dev, struct mouse_event *evt)
+{
+ struct virtio_input_priv *priv = dev_get_priv(dev);
+ struct virtio_input_event *vio_event;
+ int i, num_events, ret;
+ void *buf;
+ uint len;
+
+ /* ensure buffers are setup */
+ ret = virtio_input_start(dev);
+ if (ret)
+ return ret;
+
+ log_debug("starting\n");
+
+ /* check for ready buffer */
+ buf = virtqueue_get_buf(priv->event_vq, &len);
+ if (!buf) {
+ log_debug("No events available\n");
+ return -EAGAIN;
+ }
+
+ log_debug("got buffer back: addr=%p len=%d\n", buf, len);
+ if (len < sizeof(struct virtio_input_event)) {
+ log_debug("Invalid event length: %d\n", len);
+ /* put buffer back */
+ virtio_input_free_buffer(dev, buf);
+ return -EAGAIN;
+ }
+
+ /* process all events in the buffer */
+ num_events = len / sizeof(struct virtio_input_event);
+ vio_event = (struct virtio_input_event *)buf;
+
+ for (i = 0; i < num_events; i++) {
+ ret = process_event(priv, &vio_event[i], evt);
+ if (!ret) {
+ /*
+ * found a valid event to return, put buffer back
+ * Note: this loses any remaining events in the buffer,
+ * but for input devices this is acceptable
+ */
+ virtio_input_free_buffer(dev, buf);
+ return 0;
+ }
+ /* -EAGAIN means continue to next event */
+ }
+
+ /* no useful events found, put buffer back */
+ virtio_input_free_buffer(dev, buf);
+ evt->type = MOUSE_EV_NULL;
+
+ return -EAGAIN;
+}
+
+static int virtio_input_probe(struct udevice *dev)
+{
+ struct virtio_input_priv *priv = dev_get_priv(dev);
+ struct virtqueue *vqs[2];
+ struct vinp_config cfg;
+ int ret;
+
+ ret = virtio_find_vqs(dev, 2, vqs);
+ if (ret) {
+ log_debug("Failed to find virtqueues: %d\n", ret);
+ return ret;
+ }
+
+ priv->event_vq = vqs[0];
+ priv->status_vq = vqs[1];
+
+ /* check what event types this device supports */
+ cfg.select = VIRTIO_INPUT_CFG_EV_BITS;
+ cfg.subsel = EV_KEY;
+ virtio_cwrite8(dev, offsetof(struct vinp_config, select), cfg.select);
+ virtio_cwrite8(dev, offsetof(struct vinp_config, subsel), cfg.subsel);
+ virtio_cread_bytes(dev, offsetof(struct vinp_config, u.bitmap),
+ cfg.u.bitmap, 16);
+ log_debug("EV_KEY bitmap: %02x %02x %02x %02x\n", cfg.u.bitmap[0],
+ cfg.u.bitmap[1], cfg.u.bitmap[2], cfg.u.bitmap[3]);
+
+ cfg.select = VIRTIO_INPUT_CFG_EV_BITS;
+ cfg.subsel = EV_REL;
+ virtio_cwrite8(dev, offsetof(struct vinp_config, select), cfg.select);
+ virtio_cwrite8(dev, offsetof(struct vinp_config, subsel), cfg.subsel);
+ virtio_cread_bytes(dev, offsetof(struct vinp_config, u.bitmap),
+ cfg.u.bitmap, 16);
+ log_debug("EV_REL bitmap: %02x %02x %02x %02x\n", cfg.u.bitmap[0],
+ cfg.u.bitmap[1], cfg.u.bitmap[2], cfg.u.bitmap[3]);
+
+ /* check if this device supports absolute coordinates (tablet) */
+ cfg.select = VIRTIO_INPUT_CFG_EV_BITS;
+ cfg.subsel = EV_ABS;
+ virtio_cwrite8(dev, offsetof(struct vinp_config, select), cfg.select);
+ virtio_cwrite8(dev, offsetof(struct vinp_config, subsel), cfg.subsel);
+
+ /* read the bitmap to see if ABS_X and ABS_Y are supported */
+ virtio_cread_bytes(dev, offsetof(struct vinp_config, u.bitmap),
+ cfg.u.bitmap, 16);
+
+ log_debug("EV_ABS bitmap: %02x %02x %02x %02x\n", cfg.u.bitmap[0],
+ cfg.u.bitmap[1], cfg.u.bitmap[2], cfg.u.bitmap[3]);
+
+ /* check if ABS_X (0) and ABS_Y (1) bits are set */
+ if ((cfg.u.bitmap[0] & (1 << 0)) &&
+ (cfg.u.bitmap[0] & (1 << 1))) {
+ /* get ABS_X range */
+ cfg.select = VIRTIO_INPUT_CFG_ABS_INFO;
+ cfg.subsel = ABS_X;
+ virtio_cwrite8(dev, offsetof(struct vinp_config, select),
+ cfg.select);
+ virtio_cwrite8(dev, offsetof(struct vinp_config, subsel),
+ cfg.subsel);
+ virtio_cread_bytes(dev, offsetof(struct vinp_config, u.abs),
+ &cfg.u.abs, sizeof(cfg.u.abs));
+ priv->abs_x_max = le32_to_cpu(cfg.u.abs.max);
+
+ /* get ABS_Y range */
+ cfg.subsel = ABS_Y;
+ virtio_cwrite8(dev, offsetof(struct vinp_config, subsel),
+ cfg.subsel);
+ virtio_cread_bytes(dev, offsetof(struct vinp_config, u.abs),
+ &cfg.u.abs, sizeof(cfg.u.abs));
+ priv->abs_y_max = le32_to_cpu(cfg.u.abs.max);
+
+ log_debug("tablet: X range 0-%d, Y range 0-%d\n",
+ priv->abs_x_max, priv->abs_y_max);
+ } else {
+ /* no absolute coordinates, use default range */
+ priv->abs_x_max = 32767;
+ priv->abs_y_max = 32767;
+ log_debug("table: No absolute coords, using relative mode\n");
+ }
+
+ return 0;
+}
+
+static int virtio_input_bind(struct udevice *dev)
+{
+ struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent);
+
+ /* indicate what driver features we support */
+ virtio_driver_features_init(uc_priv, NULL, 0, NULL, 0);
+
+ return 0;
+}
+
+static const struct mouse_ops virtio_input_ops = {
+ .get_event = virtio_input_get_event,
+};
+
+U_BOOT_DRIVER(virtio_input) = {
+ .name = VIRTIO_INPUT_DRV_NAME,
+ .id = UCLASS_MOUSE,
+ .bind = virtio_input_bind,
+ .probe = virtio_input_probe,
+ .ops = &virtio_input_ops,
+ .priv_auto = sizeof(struct virtio_input_priv),
+ .flags = DM_FLAG_ACTIVE_DMA,
+};