
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; + 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, +}; -- 2.43.0 base-commit: 203f498cf558fb86f9e66cafdb7cfee8f0724139 branch: tab