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
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.

View File

@@ -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

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
*/
#define USB_KBD_BOOT_REPORT_SIZE 8
#define USB_MOUSE_BOOT_REPORT_SIZE 8
/*
* usb_init() - initialize the USB Controllers