Compare commits

...

3 Commits
tab2 ... tab

Author SHA1 Message Date
Simon Glass
4fa9da5163 build-qemu: Use the virtio tablet instead of USB for x86
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.

Series-to: concept
Series-cc: heinrich

Signed-off-by: Simon Glass <sjg@chromium.org>
Series-links: 1:35
2025-09-15 13:35:13 -06:00
Simon Glass
4bdf99b6ce mouse: Allow selecting the device to use
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>
2025-09-15 13:35:10 -06:00
Simon Glass
a8ca829f58 virtio: Add input-device driver
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>
2025-09-15 13:35:09 -06:00
7 changed files with 464 additions and 5 deletions

View File

@@ -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));

66
doc/usage/cmd/mouse.rst Normal file
View File

@@ -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.

View File

@@ -94,6 +94,7 @@ Shell commands
cmd/mbr
cmd/md
cmd/mmc
cmd/mouse
cmd/msr
cmd/mtest
cmd/mtrr

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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'])