Files
u-boot/drivers/input/efi_mouse.c
Simon Glass 99479f5b7c efi: mouse: Scale the pointer to the display
If a display is provided to the mouse uclass, use it to scale the coords
returned by the absolute-pointer protocol.

Series-to: concept
Series-cc: heinrich
Cover-letter:
expo: Complete mouse operation in the EFI app
This series includes various improvements which allow the mouse to be
used when running as an EFI app.

In particular:
- support for the absolute-pointer protocol, since this provides better
  integration when running under QEMU
- input tweaks to improve performance under QEMU

It also includes some x86-specific fixes for i8042 and MTRRs.

Finally, a new --bootcmd option is added to the build-qemu script to
allow passing a boot command to U-Boot.

This series is part F
END

Signed-off-by: Simon Glass <sjg@chromium.org>
Series-links: 1:48
Series-version: 2
2025-10-07 09:55:13 -06:00

458 lines
12 KiB
C

// 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>
#include <video.h>
/* Maximum coordinate value for mouse position */
#define MOUSE_MAX_COORD 0xffff
/**
* struct efi_mouse_priv - Private data for EFI mouse driver
*
* @simple: Simple pointer protocol (relative movement)
* @abs: Absolute pointer protocol (absolute position)
* @simple_last: Last simple pointer state
* @abs_last: Last absolute pointer state
* @has_last_state: True if we have a previous state for delta calculation
* @use_absolute: True to use absolute pointer, false for simple/relative
* @x: Current X position
* @y: Current Y position
* @buttons: Current button state
* @old_buttons: Previous button state for detecting changes
* @timer_event: EFI timer event for periodic polling
*/
struct efi_mouse_priv {
struct efi_simple_pointer_protocol *simple;
struct efi_absolute_pointer_protocol *abs;
struct efi_simple_pointer_state simple_last;
struct efi_absolute_pointer_state abs_last;
bool has_last_state;
bool use_absolute;
int x, y;
int buttons;
int old_buttons;
struct efi_event *timer_event;
};
/**
* get_abs_pointer() - Handle absolute pointer input
*
* @priv: Private data
* @uc_priv: Uclass-private data
* @rel_x: Returns relative X movement
* @rel_y: Returns relative Y movement
* @new_buttons: Returns button state
* Return: 0 if OK, -EAGAIN if no event, -ve on error
*/
static int get_abs_pointer(struct efi_mouse_priv *priv,
struct mouse_uc_priv *uc_priv, int *rel_x,
int *rel_y, int *new_buttons)
{
struct efi_absolute_pointer_state state;
efi_status_t ret;
/* Debug: check structure size and alignment */
log_debug("State struct size: %zu, address: %p\n", sizeof(state),
&state);
ret = priv->abs->get_state(priv->abs, &state);
if (ret == EFI_NOT_READY)
return -EAGAIN;
if (ret) {
log_debug("abs: get_state failed (ret=0x%lx)\n", ret);
return -EIO;
}
/* Always log the state values to see what we're getting */
log_debug("abs: X=%llu Y=%llu Buttons=0x%x\n", state.current_x,
state.current_y, state.active_buttons);
/* Calculate relative movement */
if (priv->has_last_state) {
*rel_x = (int)(state.current_x - priv->abs_last.current_x);
*rel_y = (int)(state.current_y - priv->abs_last.current_y);
log_debug("abs: rel_x=%d, rel_y=%d\n", *rel_x, *rel_y);
}
priv->abs_last = state;
/* Update absolute position - scale to video display if available */
if (uc_priv->video_dev && priv->abs->mode) {
struct efi_absolute_pointer_mode *mode = priv->abs->mode;
u64 x_range = mode->abs_max_x - mode->abs_min_x;
u64 y_range = mode->abs_max_y - mode->abs_min_y;
if (x_range > 0 && y_range > 0) {
log_debug("abs: unscaled x=%llx y=%llx\n",
state.current_x, state.current_y);
priv->x = ((state.current_x - mode->abs_min_x) *
uc_priv->video_width) / x_range;
priv->y = ((state.current_y - mode->abs_min_y) *
uc_priv->video_height) / y_range;
} else {
priv->x = state.current_x;
priv->y = state.current_y;
}
} else {
priv->x = state.current_x;
priv->y = state.current_y;
}
/* Extract button state */
*new_buttons = state.active_buttons & 0x3; /* Left and right buttons */
return 0;
}
/**
* get_rel_pointer() - Handle relative pointer input
*
* @priv: Private data
* @rel_x: Returns relative X movement
* @rel_y: Returns relative Y movement
* @new_buttons: Returns button state
* Return: 0 if OK, -EAGAIN if no event, -ve on error
*/
static int get_rel_pointer(struct efi_mouse_priv *priv, int *rel_x,
int *rel_y, int *new_buttons)
{
struct efi_simple_pointer_state state;
efi_status_t ret;
/* 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->simple->wait_for_input) {
events[1] = priv->simple->wait_for_input;
num_events = 2;
}
ret = boot->wait_for_event(num_events, events, &index);
if (ret != EFI_SUCCESS)
return -EAGAIN;
}
log_debug("rel: calling get_state\n");
ret = priv->simple->get_state(priv->simple, &state);
log_debug("rel: get_state returned 0x%lx\n", ret);
if (ret == EFI_NOT_READY)
return -EAGAIN;
if (ret) {
log_debug("rel: get_state failed (ret=0x%lx)\n", ret);
return -EIO;
}
log_debug("rel: RelX=%d RelY=%d LeftBtn=%d RightBtn=%d\n",
state.relative_movement_x, state.relative_movement_y,
state.left_button, state.right_button);
/*
* Scale down large movement values that seem to be incorrectly
* reported
*/
*rel_x = state.relative_movement_x;
*rel_y = state.relative_movement_y;
/* If movement values are very large, scale them down */
if (abs(*rel_x) > 1000) {
*rel_x = *rel_x / 1000;
if (*rel_x == 0 && state.relative_movement_x != 0)
*rel_x = (state.relative_movement_x > 0) ? 1 : -1;
}
if (abs(*rel_y) > 1000) {
*rel_y = *rel_y / 1000;
if (*rel_y == 0 && state.relative_movement_y != 0)
*rel_y = (state.relative_movement_y > 0) ? 1 : -1;
}
log_debug("rel: scaled RelX=%d RelY=%d\n", *rel_x, *rel_y);
/* Update absolute position */
priv->x += *rel_x;
priv->x = max(priv->x, 0);
priv->x = min(priv->x, MOUSE_MAX_COORD);
priv->y += *rel_y;
priv->y = max(priv->y, 0);
priv->y = min(priv->y, MOUSE_MAX_COORD);
/* Extract button state */
*new_buttons = 0;
if (state.left_button)
*new_buttons |= 1 << 0;
if (state.right_button)
*new_buttons |= 1 << 1;
return 0;
}
/**
* get_button_event() - Check for button-change events
*
* @priv: Private data
* @new_buttons: New button state
* @event: Event to populate if button changed
* Return: 0 if button event found, -EAGAIN if no button change
*/
static int get_button_event(struct efi_mouse_priv *priv, int new_buttons,
struct mouse_event *event)
{
struct mouse_button *but = &event->button;
int diff = new_buttons ^ priv->old_buttons;
int i;
if (new_buttons == priv->old_buttons)
return -EAGAIN;
event->type = MOUSE_EV_BUTTON;
/* Find first changed button */
for (i = 0; i < 2; i++) {
u8 mask = 1 << i;
if (!(diff & mask))
continue;
but->button = i;
but->pressed = (new_buttons & mask) ? true : false;
but->clicks = 1;
but->x = priv->x;
but->y = priv->y;
priv->old_buttons ^= mask;
return 0;
}
return -EAGAIN;
}
static int efi_mouse_get_event(struct udevice *dev, struct mouse_event *event)
{
struct mouse_uc_priv *uc_priv = dev_get_uclass_priv(dev);
struct efi_mouse_priv *priv = dev_get_priv(dev);
struct mouse_motion *motion;
int new_buttons;
int rel_x, rel_y;
int ret;
/*
* Get current pointer state. Under QEMU, EFI pointer-events are broken
* so we poll directly
*/
if (priv->use_absolute)
ret = get_abs_pointer(priv, uc_priv, &rel_x, &rel_y,
&new_buttons);
else
ret = get_rel_pointer(priv, &rel_x, &rel_y, &new_buttons);
if (ret)
return ret;
priv->has_last_state = true;
/* Check for button changes */
ret = get_button_event(priv, new_buttons, event);
if (!ret)
return 0;
/* If there's no movement, nothing to do */
if (!rel_x && !rel_y) {
priv->buttons = new_buttons;
return -EAGAIN;
}
motion = &event->motion;
event->type = MOUSE_EV_MOTION;
motion->state = new_buttons;
motion->x = priv->x;
motion->y = priv->y;
motion->xrel = rel_x;
motion->yrel = rel_y;
priv->buttons = new_buttons;
return 0;
}
/**
* setup_abs_pointer() - Set up absolute pointer protocol
*
* @priv: Private data
* Return: 0 if OK, -ve on error
*/
static int setup_abs_pointer(struct efi_mouse_priv *priv)
{
struct efi_boot_services *boot = efi_get_boot();
efi_handle_t *handles;
efi_uintn_t num_handles;
efi_status_t ret;
ret = boot->locate_handle_buffer(BY_PROTOCOL,
&efi_guid_absolute_pointer,
NULL, &num_handles, &handles);
if (ret)
return -ENODEV;
log_debug("Found %zu absolute pointer device(s) guid %pU\n",
num_handles, &efi_guid_absolute_pointer);
/* Use the first absolute pointer device */
ret = boot->open_protocol(handles[0], &efi_guid_absolute_pointer,
(void **)&priv->abs,
efi_get_parent_image(), NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (ret) {
log_debug("Cannot open absolute pointer protocol (ret=0x%lx)\n",
ret);
efi_free_pool(handles);
return -EIO;
}
priv->use_absolute = true;
log_debug("Using absolute pointer protocol\n");
efi_free_pool(handles);
return 0;
}
/**
* setup_simple_pointer() - Set up simple pointer protocol
*
* @priv: Private data
* Return: 0 if OK, -ve on error
*/
static int setup_simple_pointer(struct efi_mouse_priv *priv)
{
struct efi_boot_services *boot = efi_get_boot();
efi_handle_t *handles;
efi_uintn_t num_handles;
efi_status_t ret;
log_debug("EFI simple-pointer mouse probe\n");
ret = boot->locate_handle_buffer(BY_PROTOCOL, &efi_guid_simple_pointer,
NULL, &num_handles, &handles);
if (ret)
return -ENODEV;
log_debug("Found %zu simple pointer device(s)\n", num_handles);
/* Use the first simple pointer device */
ret = boot->open_protocol(handles[0], &efi_guid_simple_pointer,
(void **)&priv->simple,
efi_get_parent_image(), NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (ret) {
log_debug("Cannot open simple pointer protocol (ret=0x%lx)\n",
ret);
efi_free_pool(handles);
return -EIO;
}
priv->use_absolute = false;
log_debug("Using simple pointer protocol\n");
efi_free_pool(handles);
return 0;
}
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;
/* Try absolute pointer first, then fall back to simple pointer */
if (setup_abs_pointer(priv) && setup_simple_pointer(priv))
return -ENODEV;
/* Reset the pointer device */
if (priv->use_absolute)
ret = priv->abs->reset(priv->abs, true);
else
ret = priv->simple->reset(priv->simple, true);
if (ret) {
log_warning("Failed to reset device (err=0x%lx)\n", ret);
/* Continue anyway - some devices might not support reset */
}
/* Create a timer event for periodic checking */
ret = boot->create_event(EVT_TIMER, TPL_NOTIFY, NULL, NULL,
&priv->timer_event);
if (ret) {
log_debug("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) {
log_debug("Failed to set timer (ret=0x%lx)\n", ret);
boot->close_event(priv->timer_event);
priv->timer_event = NULL;
}
}
/* Test protocol validity */
if (priv->use_absolute && priv->abs->mode) {
struct efi_absolute_pointer_mode *mode = priv->abs->mode;
log_debug("absolute mouse mode: x %llx-%llx y %llx-%llx\n",
mode->abs_min_x, mode->abs_max_x,
mode->abs_min_y, mode->abs_max_y);
log_debug("absolute mouse wait_for_input event: %p\n",
priv->abs->wait_for_input);
} else if (!priv->use_absolute && priv->simple) {
log_debug("simple mouse wait_for_input event: %p\n",
priv->simple->wait_for_input);
}
log_debug("initialized (%s protocol)\n",
priv->use_absolute ? "absolute" : "simple");
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);
/* Protocol will be automatically closed when the image is unloaded */
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),
};