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

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

From: Simon Glass <sjg@chromium.org> Enhance the mouse dump command to support selecting a specific mouse device by number. This allows testing multiple mouse devices when available in the system. Add some documentation while we are here. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <sjg@chromium.org> --- cmd/mouse.c | 16 +++++++--- doc/usage/cmd/mouse.rst | 66 +++++++++++++++++++++++++++++++++++++++++ doc/usage/index.rst | 1 + 3 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 doc/usage/cmd/mouse.rst diff --git a/cmd/mouse.c b/cmd/mouse.c index 60210b9f868..ee19bb07aea 100644 --- a/cmd/mouse.c +++ b/cmd/mouse.c @@ -18,13 +18,21 @@ static int do_mouse_dump(struct cmd_tbl *cmdtp, int flag, int argc, struct udevice *dev; bool running; int count; + int mouse_dev = 0; int ret; - ret = uclass_first_device_err(UCLASS_MOUSE, &dev); + /* Parse optional device number */ + if (argc > 1) + mouse_dev = dectoul(argv[1], NULL); + + /* Get the specified mouse device */ + ret = uclass_get_device(UCLASS_MOUSE, mouse_dev, &dev); if (ret) { - printf("Mouse not found (err=%d)\n", ret); + printf("Mouse device %d not found (err=%d)\n", mouse_dev, ret); return CMD_RET_FAILURE; } + + printf("Using mouse device %d: %s\n", mouse_dev, dev->name); for (running = true, count = 0; running;) { struct mouse_event evt; @@ -63,7 +71,7 @@ static int do_mouse_dump(struct cmd_tbl *cmdtp, int flag, int argc, } static char mouse_help_text[] = - "dump - Dump input from a mouse"; + "dump [dev] - Dump input from mouse device (default: 0)"; U_BOOT_CMD_WITH_SUBCMDS(mouse, "Mouse input", mouse_help_text, - U_BOOT_SUBCMD_MKENT(dump, 1, 1, do_mouse_dump)); + U_BOOT_SUBCMD_MKENT(dump, 2, 1, do_mouse_dump)); diff --git a/doc/usage/cmd/mouse.rst b/doc/usage/cmd/mouse.rst new file mode 100644 index 00000000000..665980111ae --- /dev/null +++ b/doc/usage/cmd/mouse.rst @@ -0,0 +1,66 @@ +.. SPDX-License-Identifier: GPL-2.0+: + +.. index:: + single: mouse (command) + +mouse command +============= + +Synopsis +-------- + +:: + + mouse dump [dev] + +The mouse command is used to access mouse input devices. + +mouse dump +---------- + +Dump input events from a mouse device in real-time. Events are displayed +until the user presses Ctrl+C to stop. + +dev + Optional device number (default: 0). Use this to select a specific mouse + device when multiple mouse devices are available. + +The command displays: +- Motion events with absolute/relative coordinates and button state +- Button events (left, right, middle) with press/release state and position +- Event count when finished + +Example +------- + +:: + + => mouse dump + Using mouse device 0: xhci_pci.p0.usb_hub.p6.usb_mo + motion: Xrel=-27, Yrel=84, X=0, Y=84, but=0 + motion: Xrel=-78, Yrel=87, X=0, Y=171, but=0 + motion: Xrel=-1, Yrel=87, X=0, Y=258, but=0 + motion: Xrel=76, Yrel=88, X=76, Y=346, but=0 + button: button==0, press=1, clicks=1, X=76, Y=346 + motion: Xrel=76, Yrel=88, X=152, Y=434, but=1 + motion: Xrel=76, Yrel=88, X=228, Y=522, but=1 + motion: Xrel=76, Yrel=88, X=304, Y=610, but=1 + motion: Xrel=76, Yrel=88, X=380, Y=698, but=1 + button: button==0, press=0, clicks=1, X=380, Y=698 + motion: Xrel=76, Yrel=88, X=456, Y=786, but=0 + motion: Xrel=76, Yrel=88, X=532, Y=874, but=0 + motion: Xrel=50, Yrel=88, X=582, Y=962, but=0 + motion: Xrel=-1, Yrel=87, X=581, Y=1049, but=0 + motion: Xrel=76, Yrel=87, X=657, Y=1136, but=0 + motion: Xrel=-104, Yrel=86, X=553, Y=1222, but=0 + motion: Xrel=24, Yrel=86, X=577, Y=1308, but=0 + motion: Xrel=-104, Yrel=85, X=473, Y=1393, but=0 + 18 events received + + => mouse dump 1 + Mouse device 1 not found (err=-19) + +Configuration +------------- + +The mouse command is available when CONFIG_CMD_MOUSE is enabled. diff --git a/doc/usage/index.rst b/doc/usage/index.rst index eeda632b1a0..c03ea7df7c4 100644 --- a/doc/usage/index.rst +++ b/doc/usage/index.rst @@ -94,6 +94,7 @@ Shell commands cmd/mbr cmd/md cmd/mmc + cmd/mouse cmd/msr cmd/mtest cmd/mtrr -- 2.43.0 base-commit: 203f498cf558fb86f9e66cafdb7cfee8f0724139 branch: tab

From: Simon Glass <sjg@chromium.org> Switch the x86 QEMU configuration to use virtio-tablet-pci instead of usb-tablet. This allows testing the new virtio input device driver and does not need 'usb start' to work. The virtio tablet provides the same functionality as USB tablet but integrates with the VirtIO subsystem that U-Boot already supports. Signed-off-by: Simon Glass <sjg@chromium.org> --- scripts/build-qemu | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/build-qemu b/scripts/build-qemu index 62f0bc41fad..cba3a54b9e6 100755 --- a/scripts/build-qemu +++ b/scripts/build-qemu @@ -278,7 +278,8 @@ class BuildQemu: qemu_cmd.extend(['-display', 'default,show-cursor=on']) elif self.args.arch == 'x86': qemu_cmd.extend(['-device', 'qemu-xhci']) - qemu_cmd.extend(['-device', 'usb-kbd', '-device', 'usb-tablet']) + qemu_cmd.extend(['-device', 'usb-kbd']) + qemu_cmd.extend(['-device', 'virtio-tablet-pci']) qemu_cmd.extend(['-display', 'default,show-cursor=on']) if not any(item.startswith('-serial') for item in self.qemu_extra): qemu_cmd.extend(['-serial', 'mon:stdio']) -- 2.43.0 base-commit: 203f498cf558fb86f9e66cafdb7cfee8f0724139 branch: tab

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, +};
participants (2)
-
Heinrich Schuchardt
-
Simon Glass