
From: Simon Glass <sjg@chromium.org> In some cases the device may have a touchpad or mouse, so it is useful to be able to use this in U-Boot. Add a simple driver for a mouse that uses the EFI simple-pointer protocol. Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/input/Kconfig | 12 ++- drivers/input/Makefile | 1 + drivers/input/efi_mouse.c | 221 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 drivers/input/efi_mouse.c diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index e98471c7c2e..4bc94de0a0a 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -104,7 +104,7 @@ config TWL4030_INPUT config MOUSE bool "Support for mice and other pointing devices" depends on INPUT - default y if SANDBOX + default y if SANDBOX || EFI_APP help This allows U-Boot to access mouse input, typically needed for graphics boot menus and the like. The driver can provide mouse @@ -118,3 +118,13 @@ config USB_MOUSE typically a boot menu. The driver uses the USB boot interface of the mouse and attempts to auto-configure itself for normal 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. diff --git a/drivers/input/Makefile b/drivers/input/Makefile index 797284ad8f8..4debad9e713 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -21,3 +21,4 @@ ifdef CONFIG_MOUSE obj-$(CONFIG_SANDBOX) += sandbox_mouse.o endif obj-$(CONFIG_USB_MOUSE) += usb_mouse.o +obj-$(CONFIG_EFI_MOUSE) += efi_mouse.o diff --git a/drivers/input/efi_mouse.c b/drivers/input/efi_mouse.c new file mode 100644 index 00000000000..84347951f62 --- /dev/null +++ b/drivers/input/efi_mouse.c @@ -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), +}; -- 2.43.0