[Concept,4/6] usb: Add a USB mouse driver

Message ID 20250825144018.4.I74dbe55eb065be1782737c8b2b86558e53e0292a@changeid
State New
Headers
Series Provide basic support for a mouse |

Commit Message

Simon Glass Aug. 25, 2025, 8:40 p.m. UTC
  From: Simon Glass <sjg@chromium.org>

Add a basic mouse driver for USB mice. It only handles very basic mice so
assumes that the reports are in the basic format described by the USB HID
specification 1.11.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

 drivers/input/Kconfig     |   8 +
 drivers/input/Makefile    |   1 +
 drivers/input/usb_mouse.c | 325 ++++++++++++++++++++++++++++++++++++++
 include/usb.h             |   1 +
 4 files changed, 335 insertions(+)
 create mode 100644 drivers/input/usb_mouse.c
  

Patch

diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
index 6bfee40ccac..bcab0408d70 100644
--- a/drivers/input/Kconfig
+++ b/drivers/input/Kconfig
@@ -109,3 +109,11 @@  config MOUSE
 	  graphics boot menus and the like. The driver can provide mouse
 	  events based on user interaction and these can be used to control
 	  U-Boot's operation.
+
+config USB_MOUSE
+	bool "USB mouse support"
+	help
+	  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
+	  the mouse and attempts to auto-configure itself for normal
+	  operation.
diff --git a/drivers/input/Makefile b/drivers/input/Makefile
index a4938d19903..f1c136b214c 100644
--- a/drivers/input/Makefile
+++ b/drivers/input/Makefile
@@ -18,3 +18,4 @@  endif
 
 obj-$(CONFIG_MOUSE) += mouse-uclass.o
 obj-$(CONFIG_SANDBOX) += sandbox_mouse.o
+obj-$(CONFIG_USB_MOUSE) += usb_mouse.o
diff --git a/drivers/input/usb_mouse.c b/drivers/input/usb_mouse.c
new file mode 100644
index 00000000000..b5b5ba2f894
--- /dev/null
+++ b/drivers/input/usb_mouse.c
@@ -0,0 +1,325 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * USB mouse driver (parts taken from usb_kbd.c)
+ *
+ * (C) Copyright 2001
+ * Denis Peter, MPL AG Switzerland
+ *
+ * Part of this source has been derived from the Linux USB
+ * project.
+ *
+ * Copyright 2020 Google LLC
+ */
+
+#define LOG_CATEGORY UCLASS_MOUSE
+
+#include <dm.h>
+#include <log.h>
+#include <malloc.h>
+#include <mouse.h>
+#include <time.h>
+#include <usb.h>
+
+enum {
+	RPT_BUTTON,
+	RPT_XREL,
+	RPT_YREL,
+	RPT_SCROLLY,
+};
+
+struct usb_mouse_priv {
+	ulong	intpipe;
+	int		intpktsize;
+	int		intinterval;
+	ulong	last_report;
+	struct int_queue *intq;
+	u32	repeat_delay;
+	int xrel;
+	int yrel;
+	int x;
+	int y;
+	int buttons;
+	int old_buttons;
+	int yscroll;
+	/*
+	 * TODO(sjg@chromium.org): Use an array instead, with the
+	 * DM_FLAG_ALLOC_PRIV_DMA flag
+	 */
+	s8		*buf;
+	u8		flags;
+};
+
+/* Interrupt service routine */
+static int usb_mouse_irq_worker(struct udevice *dev)
+{
+	struct usb_mouse_priv *priv = dev_get_priv(dev);
+	s8 *buf = priv->buf;
+
+	priv->buttons = buf[RPT_BUTTON];
+	priv->xrel = buf[RPT_XREL];
+	if (priv->xrel < -127)
+		priv->xrel = 0;
+	priv->yrel = buf[RPT_YREL];
+	if (priv->yrel < -127)
+		priv->yrel = 0;
+	priv->yscroll = buf[RPT_SCROLLY];
+
+	return 1;
+}
+
+/* Mouse interrupt handler */
+static int usb_mouse_irq(struct usb_device *udev)
+{
+	struct udevice *dev = udev->dev;
+
+	if (udev->irq_status || udev->irq_act_len != USB_MOUSE_BOOT_REPORT_SIZE) {
+		log_warning("Error %lx, len %d\n", udev->irq_status,
+			    udev->irq_act_len);
+		return 1;
+	}
+
+	return usb_mouse_irq_worker(dev);
+}
+
+/* Interrupt polling */
+static void usb_mouse_poll_for_event(struct udevice *dev)
+{
+	struct usb_device *udev = dev_get_parent_priv(dev);
+	struct usb_mouse_priv *priv = dev_get_priv(dev);
+	int ret;
+
+	if (IS_ENABLED(CONFIG_SYS_USB_EVENT_POLL)) {
+		/* Submit an interrupt transfer request */
+		if (usb_int_msg(udev, priv->intpipe, priv->buf,
+				priv->intpktsize, priv->intinterval, true) >= 0)
+			usb_mouse_irq_worker(dev);
+	} else if (IS_ENABLED(CONFIG_SYS_USB_EVENT_POLL_VIA_CONTROL_EP) ||
+		   IS_ENABLED(CONFIG_SYS_USB_EVENT_POLL_VIA_INT_QUEUE)) {
+		bool got_report = false;
+
+		if (IS_ENABLED(CONFIG_SYS_USB_EVENT_POLL_VIA_CONTROL_EP)) {
+			struct usb_interface *iface;
+
+			iface = &udev->config.if_desc[0];
+			ret = usb_get_report(udev, iface->desc.bInterfaceNumber,
+					     1, 0, priv->buf,
+					     USB_MOUSE_BOOT_REPORT_SIZE);
+			printf("control ret=%d\b", ret);
+		} else {
+			if (poll_int_queue(udev, priv->intq)) {
+				usb_mouse_irq_worker(dev);
+				/* We've consumed all queued int packets, create new */
+				destroy_int_queue(udev, priv->intq);
+				priv->intq = create_int_queue(udev,
+					priv->intpipe, 1,
+					USB_MOUSE_BOOT_REPORT_SIZE, priv->buf,
+					priv->intinterval);
+				got_report = true;
+			}
+		}
+		if (got_report)
+			priv->last_report = get_timer(0);
+	}
+}
+
+static int usb_mouse_get_event(struct udevice *dev, struct mouse_event *event)
+{
+	struct usb_mouse_priv *priv = dev_get_priv(dev);
+
+	if (priv->buttons != priv->old_buttons) {
+		struct mouse_button *but = &event->button;
+		u8 diff;
+		int i;
+
+		event->type = MOUSE_EV_BUTTON;
+		diff = priv->buttons ^ priv->old_buttons;
+		log_debug("buttons=%d, old=%d, diff=%d\n", priv->buttons,
+			  priv->old_buttons, diff);
+		for (i = 0; i < 3; i++) {
+			u8 mask = 1 << i;
+
+			if (diff && mask) {
+				but->button = i;
+				but->press_state = priv->buttons & mask;
+				but->clicks = 1;
+				but->x = priv->x;
+				but->y = priv->y;
+				priv->old_buttons ^= mask;
+				break;
+			}
+		}
+		log_debug(" end: buttons=%d, old=%d, diff=%d\n", priv->buttons,
+			  priv->old_buttons, diff);
+	} else if (priv->xrel || priv->yrel) {
+		struct mouse_motion *motion = &event->motion;
+
+		priv->x += priv->xrel;
+		priv->x = max(priv->x, 0);
+		priv->x = min(priv->x, 0xffff);
+
+		priv->y += priv->yrel;
+		priv->y = max(priv->y, 0);
+		priv->y = min(priv->y, 0xffff);
+
+		event->type = MOUSE_EV_MOTION;
+		motion->state = priv->buttons;
+		motion->x = priv->x;
+		motion->y = priv->y;
+		motion->xrel = priv->xrel;
+		motion->yrel = priv->yrel;
+		priv->xrel = 0;
+		priv->yrel = 0;
+	} else {
+		usb_mouse_poll_for_event(dev);
+		return -EAGAIN;
+	}
+
+	return 0;
+}
+
+static int check_mouse(struct usb_device *udev, int ifnum)
+{
+	struct usb_endpoint_descriptor *ep;
+	struct usb_interface *iface;
+
+	if (udev->descriptor.bNumConfigurations != 1)
+		return log_msg_ret("numcfg", -EINVAL);
+
+	iface = &udev->config.if_desc[ifnum];
+
+	if (iface->desc.bInterfaceClass != USB_CLASS_HID)
+		return log_msg_ret("if class", -EINVAL);
+
+	if (iface->desc.bInterfaceSubClass != USB_SUB_HID_BOOT)
+		return log_msg_ret("if subclass", -EINVAL);
+
+	if (iface->desc.bInterfaceProtocol != USB_PROT_HID_MOUSE)
+		return log_msg_ret("if protocol", -EINVAL);
+
+	if (iface->desc.bNumEndpoints != 1)
+		return log_msg_ret("num endpoints", -EINVAL);
+
+	ep = &iface->ep_desc[0];
+
+	/* Check if endpoint 1 is interrupt endpoint */
+	if (!(ep->bEndpointAddress & 0x80))
+		return log_msg_ret("ep not irq", -EINVAL);
+
+	if ((ep->bmAttributes & 3) != 3)
+		return log_msg_ret("ep attr", -EINVAL);
+
+	return 0;
+}
+
+/* probes the USB device dev for mouse type */
+static int usb_mouse_probe(struct udevice *dev)
+{
+	struct usb_device *udev = dev_get_parent_priv(dev);
+	struct usb_mouse_priv *priv = dev_get_priv(dev);
+	struct usb_endpoint_descriptor *ep;
+	struct usb_interface *iface;
+	const int ifnum = 0;
+	int ret;
+
+	ret = check_mouse(udev, ifnum);
+	if (ret) {
+		log_warning("Mouse detect fail (err=%d)\n", ret);
+		return log_msg_ret("probe", ret);
+	}
+	log_debug("USB mouse: found set protocol...\n");
+
+	/* allocate input buffer aligned and sized to USB DMA alignment */
+	priv->buf = memalign(USB_DMA_MINALIGN,
+		roundup(USB_MOUSE_BOOT_REPORT_SIZE, USB_DMA_MINALIGN));
+
+	/* Insert private data into USB device structure */
+	udev->privptr = priv;
+
+	/* Set IRQ handler */
+	udev->irq_handle = usb_mouse_irq;
+
+	iface = &udev->config.if_desc[ifnum];
+	ep = &iface->ep_desc[0];
+	priv->intpipe = usb_rcvintpipe(udev, ep->bEndpointAddress);
+	priv->intpktsize = min(usb_maxpacket(udev, priv->intpipe),
+			       USB_MOUSE_BOOT_REPORT_SIZE);
+	priv->intinterval = ep->bInterval;
+	priv->last_report = -1;
+
+	/* We found a USB Keyboard, install it. */
+	usb_set_protocol(udev, iface->desc.bInterfaceNumber, 0);
+
+	log_debug("Found set idle...\n");
+	usb_set_idle(udev, iface->desc.bInterfaceNumber, 0, 0);
+
+	log_debug("Enable interrupt pipe...\n");
+	if (IS_ENABLED(CONFIG_SYS_USB_EVENT_POLL_VIA_INT_QUEUE)) {
+		priv->intq = create_int_queue(udev, priv->intpipe, 1,
+					      USB_MOUSE_BOOT_REPORT_SIZE,
+					      priv->buf, priv->intinterval);
+		printf("priv->intq %p\n", priv->intq);
+		ret = priv->intq ? 0 : -EBUSY;
+	} else if (IS_ENABLED(CONFIG_SYS_USB_EVENT_POLL_VIA_CONTROL_EP)) {
+		ret = usb_get_report(udev, iface->desc.bInterfaceNumber, 1, 0,
+				     priv->buf, USB_MOUSE_BOOT_REPORT_SIZE);
+	} else {
+		ret = usb_int_msg(udev, priv->intpipe, priv->buf,
+				  priv->intpktsize, priv->intinterval, false);
+	}
+	if (ret < 0) {
+		log_warning("Failed to get mouse state from device %04x:%04x (err=%d)\n",
+			    udev->descriptor.idVendor,
+			    udev->descriptor.idProduct, ret);
+		/* Abort, we don't want to use that non-functional keyboard */
+		return ret;
+	}
+	log_info("USB mouse OK\n");
+
+	/* Success */
+	return 0;
+}
+
+static int usb_mouse_remove(struct udevice *dev)
+{
+	struct usb_device *udev = dev_get_parent_priv(dev);
+	struct usb_mouse_priv *priv = dev_get_priv(dev);
+
+	if (IS_ENABLED(CONFIG_SYS_USB_EVENT_POLL_VIA_INT_QUEUE))
+		destroy_int_queue(udev, priv->intq);
+	free(priv->buf);
+
+	return 0;
+}
+
+const struct mouse_ops usb_mouse_ops = {
+	.get_event	= usb_mouse_get_event,
+};
+
+static const struct udevice_id usb_mouse_ids[] = {
+	{ .compatible = "usb-mouse" },
+	{ }
+};
+
+U_BOOT_DRIVER(usb_mouse) = {
+	.name	= "usb_mouse",
+	.id	= UCLASS_MOUSE,
+	.of_match = usb_mouse_ids,
+	.ops	= &usb_mouse_ops,
+	.probe = usb_mouse_probe,
+	.remove = usb_mouse_remove,
+	.priv_auto = sizeof(struct usb_mouse_priv),
+};
+
+static const struct usb_device_id mouse_id_table[] = {
+	{
+		.match_flags = USB_DEVICE_ID_MATCH_INT_CLASS |
+			USB_DEVICE_ID_MATCH_INT_SUBCLASS |
+			USB_DEVICE_ID_MATCH_INT_PROTOCOL,
+		.bInterfaceClass = USB_CLASS_HID,
+		.bInterfaceSubClass = USB_SUB_HID_BOOT,
+		.bInterfaceProtocol = USB_PROT_HID_MOUSE,
+	},
+	{ }		/* Terminating entry */
+};
+
+U_BOOT_USB_DEVICE(usb_mouse, mouse_id_table);
diff --git a/include/usb.h b/include/usb.h
index be37ed272e1..7e05ef26d6b 100644
--- a/include/usb.h
+++ b/include/usb.h
@@ -255,6 +255,7 @@  int usb_host_eth_scan(int mode);
  * Appendix B of HID Device Class Definition 1.11
  */
 #define USB_KBD_BOOT_REPORT_SIZE 8
+#define USB_MOUSE_BOOT_REPORT_SIZE 8
 
 /*
  * usb_init() - initialize the USB Controllers