usb: Add a USB mouse driver

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.

Change-Id: I74dbe55eb065be1782737c8b2b86558e53e0292a
Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass
2020-02-13 13:42:34 -07:00
parent 899722153f
commit 9b9fcc8c1e
4 changed files with 363 additions and 0 deletions

View File

@@ -110,3 +110,11 @@ config MOUSE
graphics boot menus and the like. The driver can provide mouse graphics boot menus and the like. The driver can provide mouse
events based on user interaction and these can be used to control events based on user interaction and these can be used to control
U-Boot's operation. 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.

View File

@@ -20,3 +20,4 @@ obj-$(CONFIG_MOUSE) += mouse-uclass.o
ifdef CONFIG_MOUSE ifdef CONFIG_MOUSE
obj-$(CONFIG_SANDBOX) += sandbox_mouse.o obj-$(CONFIG_SANDBOX) += sandbox_mouse.o
endif endif
obj-$(CONFIG_USB_MOUSE) += usb_mouse.o

353
drivers/input/usb_mouse.c Normal file
View File

@@ -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 <sjg@chromium.org>
*/
#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 {
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);

View File

@@ -255,6 +255,7 @@ int usb_host_eth_scan(int mode);
* Appendix B of HID Device Class Definition 1.11 * Appendix B of HID Device Class Definition 1.11
*/ */
#define USB_KBD_BOOT_REPORT_SIZE 8 #define USB_KBD_BOOT_REPORT_SIZE 8
#define USB_MOUSE_BOOT_REPORT_SIZE 8
/* /*
* usb_init() - initialize the USB Controllers * usb_init() - initialize the USB Controllers