From patchwork Mon Sep 15 10:46:51 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 309 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1757933274; bh=HXCJcp/+vBKLd5HtJGQHsTd7+yH8B9/61NiC7wq7sEU=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=Da17s1AL/znVqFYJjztyyNbfiG0h7oIgCijAPpfyP2DSBXo3QF4rq8CAKTwT8zums JcJV1tyWZszOzE4TDQHCU1z2UwzIMwaACBoycI/VUrd0YfgMMDQ+i3SUn1XQbMNPMF Bl3759JsNWQ7cTz9bUT5W4Va46dGe2eyXHBrRv1LBa/I/KmLndeKlNhDtjIPMhgqjT MZSJhtBzRXjo8ZVzr5xjynn0YSzQdUTPIlwWrGhTQ/tkD68zReRSzTZxDiFkwpFynh 1UuvrVDJikLFCWlwH8OWtLvG9hocLkx8+biF+NHlV8z3Qa1h3cecLZ9IxBPvNYXjQb 0hQcB2A7aGaew== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 02F8567BB1 for ; Mon, 15 Sep 2025 04:47:54 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id CPhUszSt9p_B for ; Mon, 15 Sep 2025 04:47:53 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1757933273; bh=HXCJcp/+vBKLd5HtJGQHsTd7+yH8B9/61NiC7wq7sEU=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=LtjbsGdfycnXxqZmjc9+8GZqgel73ljsrTPlBvwNKU773C7dZ/4V8f9RAOwznKqen RA4g86Kzs30FxqsVOI4WZ9CWHVN0W1nh7uJsfTdpvXkMM1heEyPjo9Mvv4LtYI3qAK VhLzJfsoiISkrpw2RYhcc06geNjgmRHnPfi942hrxMvJv94MFjlSjk9+I4S9KTii5r yTFeZgk0c4hjI21IiFgYNc/JZ6o3cpAXAgoWL5k8Psa2CIf3tOxRN6gVNmpK3RaLEz EPmWpmDj8DkoedC/Qq6CBCmSulcz+cq01LNjQAu5CqRzMcBLINZLy6MMeW2W9LFqLJ ER+kxX3tS8TPg== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E44F667B04 for ; Mon, 15 Sep 2025 04:47:53 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1757933272; bh=ZkAp2x+9oxxRJKFPaZcpj6P0qIw2BRszlBEs/Ajx+GQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=YsyeSf63mp5pThH17k2ci41nXqGhrgv9g9bb+K8TteUXPvaM94zZB67C6l9UbwgFm wBYXfwGMlcsk0/X6OmwUzEPWxmMpEovdVJF9BygI3jo9pJosoPP0/G/EvFiaeyuFUb 4QRCcy8rs1pu1guCJN2W7lFDDVx8VC7MxlWSepgk5fMSxVQEwY7zh0c4cOoAOizIQl iFLH7UGOFR6TzwKBwZtPEHA+DCxPQvLMp0pY9uEb3lm9fdrmnlM8KKNxmup4pZCEM2 aOb/f3Ox2ZAz0DECyNXBg5js29/SaBDVc0HtR5/RMiL2ikzDRfARBzRIhV+0+WcdVZ CUTKN6Nfvgzzg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 13C9D67B04; Mon, 15 Sep 2025 04:47:52 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id aZwt9OSvnWSJ; Mon, 15 Sep 2025 04:47:52 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1757933266; bh=bU8rlSBNm+5/ylNmq52I4o+I1GxqvUejAXEtI8/ZT9s=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ME4El1++u+en/sYMSuhpDcOuaOVly7eRN48aSirMUXAdHkJNrL1rBSHpHbB/To20a WtFmCml5p+Q8zPVwnBvURA+WvqP2VTsLX3OIpS/RdxOs143jaxft8m6tnQsgbPDrKx GCzN/qx894PqFW6DKkzyagBo3aS0rp3Ca901zwTRAumUmvkw/rFnZbYOF0Jj1HDUwJ 0LYFL5D+TJ7nprkdz2dhFp3+Uki333IIsf3ITAZwJutg6btF0jVrjTBtRx5IG9HABK pRj/joYvBUsst1K+1sjKWqdZrJZ0qC6Ec215voOikHBT6SSIrYTfqlwNtYeKIebm4B Tv/foKS9dspCQ== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id D02EE67A99; Mon, 15 Sep 2025 04:47:45 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Mon, 15 Sep 2025 04:46:51 -0600 Message-ID: <20250915044657.10.I74dbe55eb065be1782737c8b2b86558e53e0292a@changeid> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250915104705.937780-1-sjg@u-boot.org> References: <20250915104705.937780-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: QW37TXFGM6JXWM54LHOOTBEXPCMGV3BO X-Message-ID-Hash: QW37TXFGM6JXWM54LHOOTBEXPCMGV3BO X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Heinrich Schuchardt , Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 10/17] usb: Add a USB mouse driver List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass 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 --- drivers/input/Kconfig | 8 + drivers/input/Makefile | 1 + drivers/input/usb_mouse.c | 353 ++++++++++++++++++++++++++++++++++++++ include/usb.h | 1 + 4 files changed, 363 insertions(+) create mode 100644 drivers/input/usb_mouse.c diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 101257f9767..e98471c7c2e 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -110,3 +110,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 8c5a395a540..797284ad8f8 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_MOUSE) += mouse-uclass.o ifdef CONFIG_MOUSE obj-$(CONFIG_SANDBOX) += sandbox_mouse.o endif +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..74f093a4b5a --- /dev/null +++ b/drivers/input/usb_mouse.c @@ -0,0 +1,353 @@ +// 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 2025 Simon Glass + */ + +#define LOG_CATEGORY UCLASS_MOUSE + +#include +#include +#include +#include +#include +#include + +enum { + RPT_BUTTON, + RPT_XREL, + RPT_YREL, + RPT_SCROLLY, +}; + +struct usb_mouse_priv { + unsigned long intpipe; + int intpktsize; + int intinterval; + unsigned long 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("cmn", -EINVAL); + + iface = &udev->config.if_desc[ifnum]; + + log_debug("USB device: class=%d, subclass=%d, protocol=%d\n", + iface->desc.bInterfaceClass, iface->desc.bInterfaceSubClass, + iface->desc.bInterfaceProtocol); + + if (iface->desc.bInterfaceClass != USB_CLASS_HID) + return log_msg_ret("cmc", -EINVAL); + + if (iface->desc.bInterfaceSubClass != USB_SUB_HID_BOOT && + iface->desc.bInterfaceSubClass != 0) + return log_msg_ret("cms", -EINVAL); + + /* Accept any HID device with subclass 0 or boot protocol */ + if (iface->desc.bInterfaceSubClass == 0) { + log_debug("Accepting HID device subclass 0 (tablet/other)\n"); + /* TODO: check endpoints for all devices */ + } else { + /* For boot-protocol devices, check for mouse protocol */ + if (iface->desc.bInterfaceProtocol != USB_PROT_HID_MOUSE) + return log_msg_ret("cmp", -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("cmi", -EINVAL); + + if ((ep->bmAttributes & 3) != 3) + return log_msg_ret("cma", -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("ump", 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): ignoring\n", + udev->descriptor.idVendor, + udev->descriptor.idProduct, ret); + /* + * don't abort - QEMU emulation may not support initial state + * read + */ + } + log_debug("USB mouse OK\n"); + + 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[] = { + { + /* Standard USB HID boot mouse */ + .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, + }, + { + /* + * Generic HID device (includes tablets and other pointing + * devices) + */ + .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0, /* None/generic */ + }, + { } +}; + +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