[Concept,1/3] virtio: Add input-device driver

Message ID 20250915193538.2316642-1-sjg@u-boot.org
State New
Headers
Series [Concept,1/3] virtio: Add input-device driver |

Commit Message

Simon Glass Sept. 15, 2025, 7:35 p.m. UTC
  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

Heinrich Schuchardt Sept. 15, 2025, 8:01 p.m. UTC | #1
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,
>+};
  

Patch

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;
+		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,
+};