Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fa9da5163 | ||
|
|
4bdf99b6ce | ||
|
|
a8ca829f58 | ||
|
|
203f498cf5 | ||
|
|
2f22976bc9 | ||
|
|
5a4a8278ee | ||
|
|
ff37f4218a | ||
|
|
b05dd27c97 | ||
|
|
6a078d8cb7 |
@@ -23,9 +23,14 @@
|
|||||||
compatible = "efi,reset";
|
compatible = "efi,reset";
|
||||||
bootph-all;
|
bootph-all;
|
||||||
};
|
};
|
||||||
|
|
||||||
efi-fb {
|
efi-fb {
|
||||||
compatible = "efi-fb";
|
compatible = "efi-fb";
|
||||||
bootph-some-ram;
|
bootph-some-ram;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mouse {
|
||||||
|
compatible = "efi,mouse";
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,4 +30,8 @@
|
|||||||
bootph-some-ram;
|
bootph-some-ram;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mouse {
|
||||||
|
compatible = "efi,mouse";
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
16
cmd/mouse.c
16
cmd/mouse.c
@@ -18,13 +18,21 @@ static int do_mouse_dump(struct cmd_tbl *cmdtp, int flag, int argc,
|
|||||||
struct udevice *dev;
|
struct udevice *dev;
|
||||||
bool running;
|
bool running;
|
||||||
int count;
|
int count;
|
||||||
|
int mouse_dev = 0;
|
||||||
int ret;
|
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) {
|
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;
|
return CMD_RET_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printf("Using mouse device %d: %s\n", mouse_dev, dev->name);
|
||||||
for (running = true, count = 0; running;) {
|
for (running = true, count = 0; running;) {
|
||||||
struct mouse_event evt;
|
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[] =
|
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_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));
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ CONFIG_CMD_MEM_SEARCH=y
|
|||||||
CONFIG_CMD_IDE=y
|
CONFIG_CMD_IDE=y
|
||||||
CONFIG_CMD_SPI=y
|
CONFIG_CMD_SPI=y
|
||||||
CONFIG_CMD_USB=y
|
CONFIG_CMD_USB=y
|
||||||
|
CONFIG_USB=y
|
||||||
|
CONFIG_USB_XHCI_HCD=y
|
||||||
|
CONFIG_USB_XHCI_PCI=y
|
||||||
CONFIG_CMD_CAT=y
|
CONFIG_CMD_CAT=y
|
||||||
# CONFIG_CMD_SETEXPR is not set
|
# CONFIG_CMD_SETEXPR is not set
|
||||||
CONFIG_BOOTP_BOOTFILESIZE=y
|
CONFIG_BOOTP_BOOTFILESIZE=y
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ CONFIG_CMD_MEM_SEARCH=y
|
|||||||
CONFIG_CMD_IDE=y
|
CONFIG_CMD_IDE=y
|
||||||
CONFIG_CMD_SPI=y
|
CONFIG_CMD_SPI=y
|
||||||
CONFIG_CMD_USB=y
|
CONFIG_CMD_USB=y
|
||||||
|
CONFIG_USB=y
|
||||||
|
CONFIG_USB_XHCI_HCD=y
|
||||||
|
CONFIG_USB_XHCI_PCI=y
|
||||||
CONFIG_CMD_CAT=y
|
CONFIG_CMD_CAT=y
|
||||||
# CONFIG_CMD_SETEXPR is not set
|
# CONFIG_CMD_SETEXPR is not set
|
||||||
CONFIG_BOOTP_BOOTFILESIZE=y
|
CONFIG_BOOTP_BOOTFILESIZE=y
|
||||||
|
|||||||
66
doc/usage/cmd/mouse.rst
Normal file
66
doc/usage/cmd/mouse.rst
Normal 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.
|
||||||
@@ -94,6 +94,7 @@ Shell commands
|
|||||||
cmd/mbr
|
cmd/mbr
|
||||||
cmd/md
|
cmd/md
|
||||||
cmd/mmc
|
cmd/mmc
|
||||||
|
cmd/mouse
|
||||||
cmd/msr
|
cmd/msr
|
||||||
cmd/mtest
|
cmd/mtest
|
||||||
cmd/mtrr
|
cmd/mtrr
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ config TWL4030_INPUT
|
|||||||
config MOUSE
|
config MOUSE
|
||||||
bool "Support for mice and other pointing devices"
|
bool "Support for mice and other pointing devices"
|
||||||
depends on INPUT
|
depends on INPUT
|
||||||
default y if SANDBOX
|
default y if SANDBOX || EFI_APP || MACH_QEMU
|
||||||
help
|
help
|
||||||
This allows U-Boot to access mouse input, typically needed for
|
This allows U-Boot to access mouse input, typically needed for
|
||||||
graphics boot menus and the like. The driver can provide mouse
|
graphics boot menus and the like. The driver can provide mouse
|
||||||
@@ -113,8 +113,19 @@ config MOUSE
|
|||||||
|
|
||||||
config USB_MOUSE
|
config USB_MOUSE
|
||||||
bool "USB mouse support"
|
bool "USB mouse support"
|
||||||
|
default y if MACH_QEMU
|
||||||
help
|
help
|
||||||
This enables using a USB mouse to control a feature in U-Boot,
|
This enables using a USB mouse to control a feature in U-Boot,
|
||||||
typically a boot menu. The driver uses the USB boot interface of
|
typically a boot menu. The driver uses the USB boot interface of
|
||||||
the mouse and attempts to auto-configure itself for normal
|
the mouse and attempts to auto-configure itself for normal
|
||||||
operation.
|
operation.
|
||||||
|
|
||||||
|
config EFI_MOUSE
|
||||||
|
bool "EFI mouse support"
|
||||||
|
depends on EFI_APP
|
||||||
|
default y if EFI_APP
|
||||||
|
help
|
||||||
|
This enables mouse support when U-Boot is running as an EFI
|
||||||
|
application. It uses the EFI Simple Pointer Protocol to access
|
||||||
|
mouse input from the underlying EFI firmware. This is useful
|
||||||
|
when U-Boot is used as an EFI boot manager or application.
|
||||||
|
|||||||
@@ -21,3 +21,4 @@ ifdef CONFIG_MOUSE
|
|||||||
obj-$(CONFIG_SANDBOX) += sandbox_mouse.o
|
obj-$(CONFIG_SANDBOX) += sandbox_mouse.o
|
||||||
endif
|
endif
|
||||||
obj-$(CONFIG_USB_MOUSE) += usb_mouse.o
|
obj-$(CONFIG_USB_MOUSE) += usb_mouse.o
|
||||||
|
obj-$(CONFIG_EFI_MOUSE) += efi_mouse.o
|
||||||
|
|||||||
221
drivers/input/efi_mouse.c
Normal file
221
drivers/input/efi_mouse.c
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0+
|
||||||
|
/*
|
||||||
|
* EFI mouse driver using Simple Pointer Protocol
|
||||||
|
*
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* Written by Claude <noreply@anthropic.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define LOG_CATEGORY UCLASS_MOUSE
|
||||||
|
|
||||||
|
#include <dm.h>
|
||||||
|
#include <efi.h>
|
||||||
|
#include <efi_api.h>
|
||||||
|
#include <log.h>
|
||||||
|
#include <mouse.h>
|
||||||
|
|
||||||
|
struct efi_mouse_priv {
|
||||||
|
struct efi_simple_pointer_protocol *pointer;
|
||||||
|
struct efi_simple_pointer_state last_state;
|
||||||
|
bool has_last_state;
|
||||||
|
int x, y;
|
||||||
|
int buttons;
|
||||||
|
int old_buttons;
|
||||||
|
struct efi_event *timer_event;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int efi_mouse_get_event(struct udevice *dev, struct mouse_event *event)
|
||||||
|
{
|
||||||
|
struct efi_mouse_priv *priv = dev_get_priv(dev);
|
||||||
|
struct efi_simple_pointer_state state;
|
||||||
|
efi_status_t ret;
|
||||||
|
int new_buttons;
|
||||||
|
if (!priv->pointer)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
/* Use timer-based polling approach like EFI keyboard */
|
||||||
|
if (priv->timer_event) {
|
||||||
|
struct efi_boot_services *boot = efi_get_boot();
|
||||||
|
efi_uintn_t index;
|
||||||
|
struct efi_event *events[2];
|
||||||
|
efi_uintn_t num_events = 1;
|
||||||
|
|
||||||
|
events[0] = priv->timer_event;
|
||||||
|
if (priv->pointer->wait_for_input) {
|
||||||
|
events[1] = priv->pointer->wait_for_input;
|
||||||
|
num_events = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = boot->wait_for_event(num_events, events, &index);
|
||||||
|
if (ret != EFI_SUCCESS)
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get current pointer state */
|
||||||
|
ret = priv->pointer->get_state(priv->pointer, &state);
|
||||||
|
if (ret != EFI_SUCCESS) {
|
||||||
|
if (ret == EFI_NOT_READY)
|
||||||
|
return -EAGAIN;
|
||||||
|
printf("EFI mouse: get_state failed (ret=0x%lx)\n", ret);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for button changes */
|
||||||
|
new_buttons = 0;
|
||||||
|
if (state.left_button)
|
||||||
|
new_buttons |= 1 << 0;
|
||||||
|
if (state.right_button)
|
||||||
|
new_buttons |= 1 << 1;
|
||||||
|
|
||||||
|
if (new_buttons != priv->old_buttons) {
|
||||||
|
struct mouse_button *but = &event->button;
|
||||||
|
u8 diff = new_buttons ^ priv->old_buttons;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
event->type = MOUSE_EV_BUTTON;
|
||||||
|
/* Find first changed button */
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
u8 mask = 1 << i;
|
||||||
|
if (diff & mask) {
|
||||||
|
but->button = i;
|
||||||
|
but->press_state = (new_buttons & mask) ? 1 : 0;
|
||||||
|
but->clicks = 1;
|
||||||
|
but->x = priv->x;
|
||||||
|
but->y = priv->y;
|
||||||
|
priv->old_buttons ^= mask;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for movement */
|
||||||
|
if (state.relative_movement_x || state.relative_movement_y) {
|
||||||
|
struct mouse_motion *motion = &event->motion;
|
||||||
|
|
||||||
|
/* Update absolute position */
|
||||||
|
priv->x += state.relative_movement_x;
|
||||||
|
priv->x = max(priv->x, 0);
|
||||||
|
priv->x = min(priv->x, 0xffff);
|
||||||
|
|
||||||
|
priv->y += state.relative_movement_y;
|
||||||
|
priv->y = max(priv->y, 0);
|
||||||
|
priv->y = min(priv->y, 0xffff);
|
||||||
|
|
||||||
|
event->type = MOUSE_EV_MOTION;
|
||||||
|
motion->state = new_buttons;
|
||||||
|
motion->x = priv->x;
|
||||||
|
motion->y = priv->y;
|
||||||
|
motion->xrel = state.relative_movement_x;
|
||||||
|
motion->yrel = state.relative_movement_y;
|
||||||
|
|
||||||
|
priv->buttons = new_buttons;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
priv->buttons = new_buttons;
|
||||||
|
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int efi_mouse_probe(struct udevice *dev)
|
||||||
|
{
|
||||||
|
struct efi_mouse_priv *priv = dev_get_priv(dev);
|
||||||
|
struct efi_boot_services *boot = efi_get_boot();
|
||||||
|
efi_status_t ret;
|
||||||
|
efi_handle_t *handles;
|
||||||
|
efi_uintn_t num_handles;
|
||||||
|
|
||||||
|
log_debug("EFI mouse probe\n");
|
||||||
|
|
||||||
|
/* Find Simple Pointer Protocol handles */
|
||||||
|
ret = boot->locate_handle_buffer(BY_PROTOCOL, &efi_guid_simple_pointer,
|
||||||
|
NULL, &num_handles, &handles);
|
||||||
|
if (ret != EFI_SUCCESS) {
|
||||||
|
printf("EFI mouse: No EFI pointer devices found (ret=0x%lx)\n", ret);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_debug("Found %zu EFI pointer device(s)\n", num_handles);
|
||||||
|
|
||||||
|
/* Use the first pointer device */
|
||||||
|
ret = boot->open_protocol(handles[0], &efi_guid_simple_pointer,
|
||||||
|
(void **)&priv->pointer, efi_get_parent_image(),
|
||||||
|
NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
||||||
|
if (ret != EFI_SUCCESS) {
|
||||||
|
printf("EFI mouse: Failed to open EFI pointer protocol (ret=0x%lx)\n", ret);
|
||||||
|
efi_free_pool(handles);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
efi_free_pool(handles);
|
||||||
|
|
||||||
|
/* Reset the pointer device */
|
||||||
|
ret = priv->pointer->reset(priv->pointer, false);
|
||||||
|
if (ret != EFI_SUCCESS) {
|
||||||
|
log_warning("Failed to reset EFI pointer device\n");
|
||||||
|
/* Continue anyway - some devices might not support reset */
|
||||||
|
}
|
||||||
|
|
||||||
|
priv->x = 0;
|
||||||
|
priv->y = 0;
|
||||||
|
priv->buttons = 0;
|
||||||
|
priv->old_buttons = 0;
|
||||||
|
priv->has_last_state = false;
|
||||||
|
|
||||||
|
/* Create a timer event for periodic checking */
|
||||||
|
ret = boot->create_event(EVT_TIMER, TPL_NOTIFY, NULL, NULL,
|
||||||
|
&priv->timer_event);
|
||||||
|
if (ret != EFI_SUCCESS) {
|
||||||
|
printf("EFI mouse: Failed to create timer event (ret=0x%lx)\n", ret);
|
||||||
|
/* Continue without timer - fallback to direct polling */
|
||||||
|
priv->timer_event = NULL;
|
||||||
|
} else {
|
||||||
|
/* Set timer to trigger every 10ms (100000 x 100ns = 10ms) */
|
||||||
|
ret = boot->set_timer(priv->timer_event, EFI_TIMER_PERIODIC, 10000);
|
||||||
|
if (ret != EFI_SUCCESS) {
|
||||||
|
printf("EFI mouse: Failed to set timer (ret=0x%lx)\n", ret);
|
||||||
|
boot->close_event(priv->timer_event);
|
||||||
|
priv->timer_event = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("EFI mouse initialized\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int efi_mouse_remove(struct udevice *dev)
|
||||||
|
{
|
||||||
|
struct efi_mouse_priv *priv = dev_get_priv(dev);
|
||||||
|
struct efi_boot_services *boot = efi_get_boot();
|
||||||
|
|
||||||
|
if (priv->timer_event) {
|
||||||
|
boot->close_event(priv->timer_event);
|
||||||
|
priv->timer_event = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priv->pointer) {
|
||||||
|
/* Protocol will be automatically closed when the image is unloaded */
|
||||||
|
priv->pointer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct mouse_ops efi_mouse_ops = {
|
||||||
|
.get_event = efi_mouse_get_event,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct udevice_id efi_mouse_ids[] = {
|
||||||
|
{ .compatible = "efi,mouse" },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
U_BOOT_DRIVER(efi_mouse) = {
|
||||||
|
.name = "efi_mouse",
|
||||||
|
.id = UCLASS_MOUSE,
|
||||||
|
.of_match = efi_mouse_ids,
|
||||||
|
.ops = &efi_mouse_ops,
|
||||||
|
.probe = efi_mouse_probe,
|
||||||
|
.remove = efi_mouse_remove,
|
||||||
|
.priv_auto = sizeof(struct efi_mouse_priv),
|
||||||
|
};
|
||||||
@@ -110,4 +110,17 @@ config VIRTIO_SCSI
|
|||||||
A specification for the protocol is available at
|
A specification for the protocol is available at
|
||||||
https://docs.oasis-open.org/virtio/virtio/v1.3/virtio-v1.3.html
|
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
|
endmenu
|
||||||
|
|||||||
@@ -14,3 +14,4 @@ obj-$(CONFIG_VIRTIO_BLK) += virtio_blk.o
|
|||||||
obj-$(CONFIG_VIRTIO_RNG) += virtio_rng.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_FS) += fs.o fs_dir.o fs_file.o fs_compat.o
|
||||||
obj-$(CONFIG_VIRTIO_SCSI) += virtio_scsi.o
|
obj-$(CONFIG_VIRTIO_SCSI) += virtio_scsi.o
|
||||||
|
obj-$(CONFIG_VIRTIO_INPUT) += virtio_input.o
|
||||||
|
|||||||
369
drivers/virtio/virtio_input.c
Normal file
369
drivers/virtio/virtio_input.c
Normal 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,
|
||||||
|
};
|
||||||
@@ -112,6 +112,9 @@ class BuildEfi:
|
|||||||
extra += ['-device', 'qemu-xhci', '-device', 'usb-kbd',
|
extra += ['-device', 'qemu-xhci', '-device', 'usb-kbd',
|
||||||
'-device', 'usb-tablet']
|
'-device', 'usb-tablet']
|
||||||
extra += ['-display', 'default,show-cursor=on']
|
extra += ['-display', 'default,show-cursor=on']
|
||||||
|
else: # x86
|
||||||
|
extra += ['-device', 'qemu-xhci', '-device', 'usb-kbd',
|
||||||
|
'-device', 'usb-tablet']
|
||||||
extra += ['-serial', 'mon:stdio']
|
extra += ['-serial', 'mon:stdio']
|
||||||
serial_msg = ''
|
serial_msg = ''
|
||||||
if self.args.kvm:
|
if self.args.kvm:
|
||||||
|
|||||||
@@ -274,7 +274,12 @@ class BuildQemu:
|
|||||||
elif self.args.arch == 'arm':
|
elif self.args.arch == 'arm':
|
||||||
qemu_cmd.extend(['-device', 'virtio-gpu-pci'])
|
qemu_cmd.extend(['-device', 'virtio-gpu-pci'])
|
||||||
qemu_cmd.extend(['-device', 'qemu-xhci', '-device', 'usb-kbd',
|
qemu_cmd.extend(['-device', 'qemu-xhci', '-device', 'usb-kbd',
|
||||||
'-device', 'usb-tablet'])
|
'-device', 'usb-tablet', '-device', 'usb-mouse'])
|
||||||
|
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'])
|
||||||
|
qemu_cmd.extend(['-device', 'virtio-tablet-pci'])
|
||||||
qemu_cmd.extend(['-display', 'default,show-cursor=on'])
|
qemu_cmd.extend(['-display', 'default,show-cursor=on'])
|
||||||
if not any(item.startswith('-serial') for item in self.qemu_extra):
|
if not any(item.startswith('-serial') for item in self.qemu_extra):
|
||||||
qemu_cmd.extend(['-serial', 'mon:stdio'])
|
qemu_cmd.extend(['-serial', 'mon:stdio'])
|
||||||
|
|||||||
Reference in New Issue
Block a user