diff --git a/sys/dev/usb/usb_subr.c b/sys/dev/usb/usb_subr.c index 26c10545e92e..6b11b448a34c 100644 --- a/sys/dev/usb/usb_subr.c +++ b/sys/dev/usb/usb_subr.c @@ -1,1499 +1,1460 @@ /* $NetBSD: usb_subr.c,v 1.99 2002/07/11 21:14:34 augustss Exp $ */ /* Also already have from NetBSD: * $NetBSD: usb_subr.c,v 1.102 2003/01/01 16:21:50 augustss Exp $ * $NetBSD: usb_subr.c,v 1.103 2003/01/10 11:19:13 augustss Exp $ * $NetBSD: usb_subr.c,v 1.111 2004/03/15 10:35:04 augustss Exp $ * $NetBSD: usb_subr.c,v 1.114 2004/06/23 02:30:52 mycroft Exp $ * $NetBSD: usb_subr.c,v 1.115 2004/06/23 05:23:19 mycroft Exp $ * $NetBSD: usb_subr.c,v 1.116 2004/06/23 06:27:54 mycroft Exp $ + * $NetBSD: usb_subr.c,v 1.119 2004/10/23 13:26:33 augustss Exp $ */ #include __FBSDID("$FreeBSD$"); /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "opt_usb.h" #include #include #include #include #if defined(__NetBSD__) || defined(__OpenBSD__) #include #include #elif defined(__FreeBSD__) #include #include #endif #include #include #include #include #include #include #include "usbdevs.h" #include #if defined(__FreeBSD__) #include #define delay(d) DELAY(d) #endif #ifdef USB_DEBUG #define DPRINTF(x) if (usbdebug) logprintf x #define DPRINTFN(n,x) if (usbdebug>(n)) logprintf x extern int usbdebug; #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif Static usbd_status usbd_set_config(usbd_device_handle, int); Static void usbd_devinfo_vp(usbd_device_handle, char *, char *, int); -Static char *usbd_get_string(usbd_device_handle, int, char *); Static int usbd_getnewaddr(usbd_bus_handle bus); #if defined(__NetBSD__) Static int usbd_print(void *aux, const char *pnp); Static int usbd_submatch(device_ptr_t, struct cfdata *cf, void *); #elif defined(__OpenBSD__) Static int usbd_print(void *aux, const char *pnp); Static int usbd_submatch(device_ptr_t, void *, void *); #endif Static void usbd_free_iface_data(usbd_device_handle dev, int ifcno); Static void usbd_kill_pipe(usbd_pipe_handle); Static usbd_status usbd_probe_and_attach(device_ptr_t parent, usbd_device_handle dev, int port, int addr); Static u_int32_t usb_cookie_no = 0; #ifdef USBVERBOSE typedef u_int16_t usb_vendor_id_t; typedef u_int16_t usb_product_id_t; /* * Descriptions of of known vendors and devices ("products"). */ struct usb_knowndev { usb_vendor_id_t vendor; usb_product_id_t product; int flags; char *vendorname, *productname; }; #define USB_KNOWNDEV_NOPROD 0x01 /* match on vendor only */ #include "usbdevs_data.h" #endif /* USBVERBOSE */ Static const char * const usbd_error_strs[] = { "NORMAL_COMPLETION", "IN_PROGRESS", "PENDING_REQUESTS", "NOT_STARTED", "INVAL", "NOMEM", "CANCELLED", "BAD_ADDRESS", "IN_USE", "NO_ADDR", "SET_ADDR_FAILED", "NO_POWER", "TOO_DEEP", "IOERROR", "NOT_CONFIGURED", "TIMEOUT", "SHORT_XFER", "STALLED", "INTERRUPTED", "XXX", }; const char * usbd_errstr(usbd_status err) { static char buffer[5]; if (err < USBD_ERROR_MAX) { return usbd_error_strs[err]; } else { snprintf(buffer, sizeof buffer, "%d", err); return buffer; } } usbd_status usbd_get_string_desc(usbd_device_handle dev, int sindex, int langid, usb_string_descriptor_t *sdesc, int *sizep) { usb_device_request_t req; usbd_status err; int actlen; req.bmRequestType = UT_READ_DEVICE; req.bRequest = UR_GET_DESCRIPTOR; USETW2(req.wValue, UDESC_STRING, sindex); USETW(req.wIndex, langid); USETW(req.wLength, 2); /* only size byte first */ err = usbd_do_request_flags(dev, &req, sdesc, USBD_SHORT_XFER_OK, &actlen, USBD_DEFAULT_TIMEOUT); if (err) return (err); if (actlen < 2) return (USBD_SHORT_XFER); USETW(req.wLength, sdesc->bLength); /* the whole string */ err = usbd_do_request_flags(dev, &req, sdesc, USBD_SHORT_XFER_OK, &actlen, USBD_DEFAULT_TIMEOUT); if (err) return (err); if (actlen != sdesc->bLength) { DPRINTFN(-1, ("usbd_get_string_desc: expected %d, got %d\n", sdesc->bLength, actlen)); } *sizep = actlen; return (USBD_NORMAL_COMPLETION); } -char * -usbd_get_string(usbd_device_handle dev, int si, char *buf) -{ - int swap = dev->quirks->uq_flags & UQ_SWAP_UNICODE; - usb_string_descriptor_t us; - char *s; - int i, n; - u_int16_t c; - usbd_status err; - int size; - - if (si == 0) - return (0); - if (dev->quirks->uq_flags & UQ_NO_STRINGS) - return (0); - if (dev->langid == USBD_NOLANG) { - /* Set up default language */ - err = usbd_get_string_desc(dev, USB_LANGUAGE_TABLE, 0, &us, - &size); - if (err || size < 4) { - dev->langid = 0; /* Well, just pick something then */ - } else { - /* Pick the first language as the default. */ - dev->langid = UGETW(us.bString[0]); - } - } - err = usbd_get_string_desc(dev, si, dev->langid, &us, &size); - if (err) - return (0); - s = buf; - n = size / 2 - 1; - for (i = 0; i < n; i++) { - c = UGETW(us.bString[i]); - /* Convert from Unicode, handle buggy strings. */ - if ((c & 0xff00) == 0) - *s++ = c; - else if ((c & 0x00ff) == 0 && swap) - *s++ = c >> 8; - else - *s++ = '?'; - } - *s++ = 0; - return (buf); -} - Static void usbd_trim_spaces(char *p) { char *q, *e; if (p == NULL) return; q = e = p; while (*q == ' ') /* skip leading spaces */ q++; while ((*p = *q++)) /* copy string */ if (*p++ != ' ') /* remember last non-space */ e = p; *e = 0; /* kill trailing spaces */ } Static void usbd_devinfo_vp(usbd_device_handle dev, char *v, char *p, int usedev) { usb_device_descriptor_t *udd = &dev->ddesc; char *vendor = 0, *product = 0; #ifdef USBVERBOSE const struct usb_knowndev *kdp; #endif if (dev == NULL) { v[0] = p[0] = '\0'; return; } if (usedev) { - vendor = usbd_get_string(dev, udd->iManufacturer, v); + if (usbd_get_string(dev, udd->iManufacturer, v)) + vendor = NULL; + else + vendor = v; usbd_trim_spaces(vendor); - product = usbd_get_string(dev, udd->iProduct, p); + if (usbd_get_string(dev, udd->iProduct, p)) + product = NULL; + else + product = p; usbd_trim_spaces(product); if (vendor && !*vendor) vendor = NULL; if (product && !*product) product = NULL; } else { vendor = NULL; product = NULL; } #ifdef USBVERBOSE if (vendor == NULL || product == NULL) { for(kdp = usb_knowndevs; kdp->vendorname != NULL; kdp++) { if (kdp->vendor == UGETW(udd->idVendor) && (kdp->product == UGETW(udd->idProduct) || (kdp->flags & USB_KNOWNDEV_NOPROD) != 0)) break; } if (kdp->vendorname != NULL) { if (vendor == NULL) vendor = kdp->vendorname; if (product == NULL) product = (kdp->flags & USB_KNOWNDEV_NOPROD) == 0 ? kdp->productname : NULL; } } #endif if (vendor != NULL && *vendor) strcpy(v, vendor); else sprintf(v, "vendor 0x%04x", UGETW(udd->idVendor)); if (product != NULL && *product) strcpy(p, product); else sprintf(p, "product 0x%04x", UGETW(udd->idProduct)); } int usbd_printBCD(char *cp, int bcd) { return (sprintf(cp, "%x.%02x", bcd >> 8, bcd & 0xff)); } void usbd_devinfo(usbd_device_handle dev, int showclass, char *cp) { usb_device_descriptor_t *udd = &dev->ddesc; usbd_interface_handle iface; char vendor[USB_MAX_STRING_LEN]; char product[USB_MAX_STRING_LEN]; int bcdDevice, bcdUSB; usb_interface_descriptor_t *id; usbd_devinfo_vp(dev, vendor, product, 1); cp += sprintf(cp, "%s %s", vendor, product); if (showclass & USBD_SHOW_DEVICE_CLASS) cp += sprintf(cp, ", class %d/%d", udd->bDeviceClass, udd->bDeviceSubClass); bcdUSB = UGETW(udd->bcdUSB); bcdDevice = UGETW(udd->bcdDevice); cp += sprintf(cp, ", rev "); cp += usbd_printBCD(cp, bcdUSB); *cp++ = '/'; cp += usbd_printBCD(cp, bcdDevice); cp += sprintf(cp, ", addr %d", dev->address); if (showclass & USBD_SHOW_INTERFACE_CLASS) { /* fetch the interface handle for the first interface */ (void)usbd_device2interface_handle(dev, 0, &iface); id = usbd_get_interface_descriptor(iface); cp += sprintf(cp, ", iclass %d/%d", id->bInterfaceClass, id->bInterfaceSubClass); } *cp = 0; } /* Delay for a certain number of ms */ void usb_delay_ms(usbd_bus_handle bus, u_int ms) { /* Wait at least two clock ticks so we know the time has passed. */ if (bus->use_polling || cold) delay((ms+1) * 1000); else tsleep(&ms, PRIBIO, "usbdly", (ms*hz+999)/1000 + 1); } /* Delay given a device handle. */ void usbd_delay_ms(usbd_device_handle dev, u_int ms) { usb_delay_ms(dev->bus, ms); } usbd_status usbd_reset_port(usbd_device_handle dev, int port, usb_port_status_t *ps) { usb_device_request_t req; usbd_status err; int n; req.bmRequestType = UT_WRITE_CLASS_OTHER; req.bRequest = UR_SET_FEATURE; USETW(req.wValue, UHF_PORT_RESET); USETW(req.wIndex, port); USETW(req.wLength, 0); err = usbd_do_request(dev, &req, 0); DPRINTFN(1,("usbd_reset_port: port %d reset done, error=%s\n", port, usbd_errstr(err))); if (err) return (err); n = 10; do { /* Wait for device to recover from reset. */ usbd_delay_ms(dev, USB_PORT_RESET_DELAY); err = usbd_get_port_status(dev, port, ps); if (err) { DPRINTF(("usbd_reset_port: get status failed %d\n", err)); return (err); } /* If the device disappeared, just give up. */ if (!(UGETW(ps->wPortStatus) & UPS_CURRENT_CONNECT_STATUS)) return (USBD_NORMAL_COMPLETION); } while ((UGETW(ps->wPortChange) & UPS_C_PORT_RESET) == 0 && --n > 0); if (n == 0) return (USBD_TIMEOUT); err = usbd_clear_port_feature(dev, port, UHF_C_PORT_RESET); #ifdef USB_DEBUG if (err) DPRINTF(("usbd_reset_port: clear port feature failed %d\n", err)); #endif /* Wait for the device to recover from reset. */ usbd_delay_ms(dev, USB_PORT_RESET_RECOVERY); return (err); } usb_interface_descriptor_t * usbd_find_idesc(usb_config_descriptor_t *cd, int ifaceidx, int altidx) { char *p = (char *)cd; char *end = p + UGETW(cd->wTotalLength); usb_interface_descriptor_t *d; int curidx, lastidx, curaidx = 0; for (curidx = lastidx = -1; p < end; ) { d = (usb_interface_descriptor_t *)p; DPRINTFN(4,("usbd_find_idesc: idx=%d(%d) altidx=%d(%d) len=%d " "type=%d\n", ifaceidx, curidx, altidx, curaidx, d->bLength, d->bDescriptorType)); if (d->bLength == 0) /* bad descriptor */ break; p += d->bLength; if (p <= end && d->bDescriptorType == UDESC_INTERFACE) { if (d->bInterfaceNumber != lastidx) { lastidx = d->bInterfaceNumber; curidx++; curaidx = 0; } else curaidx++; if (ifaceidx == curidx && altidx == curaidx) return (d); } } return (NULL); } usb_endpoint_descriptor_t * usbd_find_edesc(usb_config_descriptor_t *cd, int ifaceidx, int altidx, int endptidx) { char *p = (char *)cd; char *end = p + UGETW(cd->wTotalLength); usb_interface_descriptor_t *d; usb_endpoint_descriptor_t *e; int curidx; d = usbd_find_idesc(cd, ifaceidx, altidx); if (d == NULL) return (NULL); if (endptidx >= d->bNumEndpoints) /* quick exit */ return (NULL); curidx = -1; for (p = (char *)d + d->bLength; p < end; ) { e = (usb_endpoint_descriptor_t *)p; if (e->bLength == 0) /* bad descriptor */ break; p += e->bLength; if (p <= end && e->bDescriptorType == UDESC_INTERFACE) return (NULL); if (p <= end && e->bDescriptorType == UDESC_ENDPOINT) { curidx++; if (curidx == endptidx) return (e); } } return (NULL); } usbd_status usbd_fill_iface_data(usbd_device_handle dev, int ifaceidx, int altidx) { usbd_interface_handle ifc = &dev->ifaces[ifaceidx]; usb_interface_descriptor_t *idesc; char *p, *end; int endpt, nendpt; DPRINTFN(4,("usbd_fill_iface_data: ifaceidx=%d altidx=%d\n", ifaceidx, altidx)); idesc = usbd_find_idesc(dev->cdesc, ifaceidx, altidx); if (idesc == NULL) return (USBD_INVAL); ifc->device = dev; ifc->idesc = idesc; ifc->index = ifaceidx; ifc->altindex = altidx; nendpt = ifc->idesc->bNumEndpoints; DPRINTFN(4,("usbd_fill_iface_data: found idesc nendpt=%d\n", nendpt)); if (nendpt != 0) { ifc->endpoints = malloc(nendpt * sizeof(struct usbd_endpoint), M_USB, M_NOWAIT); if (ifc->endpoints == NULL) return (USBD_NOMEM); } else ifc->endpoints = NULL; ifc->priv = NULL; p = (char *)ifc->idesc + ifc->idesc->bLength; end = (char *)dev->cdesc + UGETW(dev->cdesc->wTotalLength); #define ed ((usb_endpoint_descriptor_t *)p) for (endpt = 0; endpt < nendpt; endpt++) { DPRINTFN(10,("usbd_fill_iface_data: endpt=%d\n", endpt)); for (; p < end; p += ed->bLength) { DPRINTFN(10,("usbd_fill_iface_data: p=%p end=%p " "len=%d type=%d\n", p, end, ed->bLength, ed->bDescriptorType)); if (p + ed->bLength <= end && ed->bLength != 0 && ed->bDescriptorType == UDESC_ENDPOINT) goto found; if (ed->bLength == 0 || ed->bDescriptorType == UDESC_INTERFACE) break; } /* passed end, or bad desc */ printf("usbd_fill_iface_data: bad descriptor(s): %s\n", ed->bLength == 0 ? "0 length" : ed->bDescriptorType == UDESC_INTERFACE ? "iface desc": "out of data"); goto bad; found: ifc->endpoints[endpt].edesc = ed; if (dev->speed == USB_SPEED_HIGH) { u_int mps; /* Control and bulk endpoints have max packet limits. */ switch (UE_GET_XFERTYPE(ed->bmAttributes)) { case UE_CONTROL: mps = USB_2_MAX_CTRL_PACKET; goto check; case UE_BULK: mps = USB_2_MAX_BULK_PACKET; check: if (UGETW(ed->wMaxPacketSize) != mps) { USETW(ed->wMaxPacketSize, mps); #ifdef DIAGNOSTIC printf("usbd_fill_iface_data: bad max " "packet size\n"); #endif } break; default: break; } } ifc->endpoints[endpt].refcnt = 0; p += ed->bLength; } #undef ed LIST_INIT(&ifc->pipes); return (USBD_NORMAL_COMPLETION); bad: if (ifc->endpoints != NULL) { free(ifc->endpoints, M_USB); ifc->endpoints = NULL; } return (USBD_INVAL); } void usbd_free_iface_data(usbd_device_handle dev, int ifcno) { usbd_interface_handle ifc = &dev->ifaces[ifcno]; if (ifc->endpoints) free(ifc->endpoints, M_USB); } Static usbd_status usbd_set_config(usbd_device_handle dev, int conf) { usb_device_request_t req; req.bmRequestType = UT_WRITE_DEVICE; req.bRequest = UR_SET_CONFIG; USETW(req.wValue, conf); USETW(req.wIndex, 0); USETW(req.wLength, 0); return (usbd_do_request(dev, &req, 0)); } usbd_status usbd_set_config_no(usbd_device_handle dev, int no, int msg) { int index; usb_config_descriptor_t cd; usbd_status err; if (no == USB_UNCONFIG_NO) return (usbd_set_config_index(dev, USB_UNCONFIG_INDEX, msg)); DPRINTFN(5,("usbd_set_config_no: %d\n", no)); /* Figure out what config index to use. */ for (index = 0; index < dev->ddesc.bNumConfigurations; index++) { err = usbd_get_config_desc(dev, index, &cd); if (err) return (err); if (cd.bConfigurationValue == no) return (usbd_set_config_index(dev, index, msg)); } return (USBD_INVAL); } usbd_status usbd_set_config_index(usbd_device_handle dev, int index, int msg) { usb_status_t ds; usb_config_descriptor_t cd, *cdp; usbd_status err; int i, ifcidx, nifc, len, selfpowered, power; DPRINTFN(5,("usbd_set_config_index: dev=%p index=%d\n", dev, index)); if (dev->config != USB_UNCONFIG_NO) { nifc = dev->cdesc->bNumInterface; /* Check that all interfaces are idle */ for (ifcidx = 0; ifcidx < nifc; ifcidx++) { if (LIST_EMPTY(&dev->ifaces[ifcidx].pipes)) continue; DPRINTF(("usbd_set_config_index: open pipes exist\n")); return (USBD_IN_USE); } DPRINTF(("usbd_set_config_index: free old config\n")); /* Free all configuration data structures. */ for (ifcidx = 0; ifcidx < nifc; ifcidx++) usbd_free_iface_data(dev, ifcidx); free(dev->ifaces, M_USB); free(dev->cdesc, M_USB); dev->ifaces = NULL; dev->cdesc = NULL; dev->config = USB_UNCONFIG_NO; } if (index == USB_UNCONFIG_INDEX) { /* We are unconfiguring the device, so leave unallocated. */ DPRINTF(("usbd_set_config_index: set config 0\n")); err = usbd_set_config(dev, USB_UNCONFIG_NO); if (err) DPRINTF(("usbd_set_config_index: setting config=0 " "failed, error=%s\n", usbd_errstr(err))); return (err); } /* Get the short descriptor. */ err = usbd_get_config_desc(dev, index, &cd); if (err) return (err); len = UGETW(cd.wTotalLength); cdp = malloc(len, M_USB, M_NOWAIT); if (cdp == NULL) return (USBD_NOMEM); /* Get the full descriptor. Try a few times for slow devices. */ for (i = 0; i < 3; i++) { err = usbd_get_desc(dev, UDESC_CONFIG, index, len, cdp); if (!err) break; usbd_delay_ms(dev, 200); } if (err) goto bad; if (cdp->bDescriptorType != UDESC_CONFIG) { DPRINTFN(-1,("usbd_set_config_index: bad desc %d\n", cdp->bDescriptorType)); err = USBD_INVAL; goto bad; } /* Figure out if the device is self or bus powered. */ selfpowered = 0; if (!(dev->quirks->uq_flags & UQ_BUS_POWERED) && (cdp->bmAttributes & UC_SELF_POWERED)) { /* May be self powered. */ if (cdp->bmAttributes & UC_BUS_POWERED) { /* Must ask device. */ if (dev->quirks->uq_flags & UQ_POWER_CLAIM) { /* * Hub claims to be self powered, but isn't. * It seems that the power status can be * determined by the hub characteristics. */ usb_hub_descriptor_t hd; usb_device_request_t req; req.bmRequestType = UT_READ_CLASS_DEVICE; req.bRequest = UR_GET_DESCRIPTOR; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, USB_HUB_DESCRIPTOR_SIZE); err = usbd_do_request(dev, &req, &hd); if (!err && (UGETW(hd.wHubCharacteristics) & UHD_PWR_INDIVIDUAL)) selfpowered = 1; DPRINTF(("usbd_set_config_index: charac=0x%04x" ", error=%s\n", UGETW(hd.wHubCharacteristics), usbd_errstr(err))); } else { err = usbd_get_device_status(dev, &ds); if (!err && (UGETW(ds.wStatus) & UDS_SELF_POWERED)) selfpowered = 1; DPRINTF(("usbd_set_config_index: status=0x%04x" ", error=%s\n", UGETW(ds.wStatus), usbd_errstr(err))); } } else selfpowered = 1; } DPRINTF(("usbd_set_config_index: (addr %d) cno=%d attr=0x%02x, " "selfpowered=%d, power=%d\n", cdp->bConfigurationValue, dev->address, cdp->bmAttributes, selfpowered, cdp->bMaxPower * 2)); /* Check if we have enough power. */ #ifdef USB_DEBUG if (dev->powersrc == NULL) { DPRINTF(("usbd_set_config_index: No power source?\n")); return (USBD_IOERROR); } #endif power = cdp->bMaxPower * 2; if (power > dev->powersrc->power) { DPRINTF(("power exceeded %d %d\n", power,dev->powersrc->power)); /* XXX print nicer message. */ if (msg) printf("%s: device addr %d (config %d) exceeds power " "budget, %d mA > %d mA\n", USBDEVNAME(dev->bus->bdev), dev->address, cdp->bConfigurationValue, power, dev->powersrc->power); err = USBD_NO_POWER; goto bad; } dev->power = power; dev->self_powered = selfpowered; /* Set the actual configuration value. */ DPRINTF(("usbd_set_config_index: set config %d\n", cdp->bConfigurationValue)); err = usbd_set_config(dev, cdp->bConfigurationValue); if (err) { DPRINTF(("usbd_set_config_index: setting config=%d failed, " "error=%s\n", cdp->bConfigurationValue, usbd_errstr(err))); goto bad; } /* Allocate and fill interface data. */ nifc = cdp->bNumInterface; dev->ifaces = malloc(nifc * sizeof(struct usbd_interface), M_USB, M_NOWAIT); if (dev->ifaces == NULL) { err = USBD_NOMEM; goto bad; } DPRINTFN(5,("usbd_set_config_index: dev=%p cdesc=%p\n", dev, cdp)); dev->cdesc = cdp; dev->config = cdp->bConfigurationValue; for (ifcidx = 0; ifcidx < nifc; ifcidx++) { err = usbd_fill_iface_data(dev, ifcidx, 0); if (err) { while (--ifcidx >= 0) usbd_free_iface_data(dev, ifcidx); goto bad; } } return (USBD_NORMAL_COMPLETION); bad: free(cdp, M_USB); return (err); } /* XXX add function for alternate settings */ usbd_status usbd_setup_pipe(usbd_device_handle dev, usbd_interface_handle iface, struct usbd_endpoint *ep, int ival, usbd_pipe_handle *pipe) { usbd_pipe_handle p; usbd_status err; DPRINTFN(1,("usbd_setup_pipe: dev=%p iface=%p ep=%p pipe=%p\n", dev, iface, ep, pipe)); p = malloc(dev->bus->pipe_size, M_USB, M_NOWAIT); if (p == NULL) return (USBD_NOMEM); p->device = dev; p->iface = iface; p->endpoint = ep; ep->refcnt++; p->refcnt = 1; p->intrxfer = 0; p->running = 0; p->aborting = 0; p->repeat = 0; p->interval = ival; SIMPLEQ_INIT(&p->queue); err = dev->bus->methods->open_pipe(p); if (err) { DPRINTFN(-1,("usbd_setup_pipe: endpoint=0x%x failed, error=" "%s\n", ep->edesc->bEndpointAddress, usbd_errstr(err))); free(p, M_USB); return (err); } /* Clear any stall and make sure DATA0 toggle will be used next. */ if (UE_GET_ADDR(ep->edesc->bEndpointAddress) != USB_CONTROL_ENDPOINT) { err = usbd_clear_endpoint_stall(p); /* * Some devices reject this command, so ignore a STALL. * Some device just time out on this command, so ignore * that too. */ if (err && err != USBD_STALLED && err != USBD_TIMEOUT) { printf("usbd_setup_pipe: failed to start " "endpoint, %s\n", usbd_errstr(err)); return (err); } } *pipe = p; return (USBD_NORMAL_COMPLETION); } /* Abort the device control pipe. */ void usbd_kill_pipe(usbd_pipe_handle pipe) { usbd_abort_pipe(pipe); pipe->methods->close(pipe); pipe->endpoint->refcnt--; free(pipe, M_USB); } int usbd_getnewaddr(usbd_bus_handle bus) { int addr; for (addr = 1; addr < USB_MAX_DEVICES; addr++) if (bus->devices[addr] == 0) return (addr); return (-1); } usbd_status usbd_probe_and_attach(device_ptr_t parent, usbd_device_handle dev, int port, int addr) { struct usb_attach_arg uaa; usb_device_descriptor_t *dd = &dev->ddesc; int found, i, confi, nifaces; usbd_status err; device_ptr_t dv; device_ptr_t *tmpdv; usbd_interface_handle ifaces[256]; /* 256 is the absolute max */ #if defined(__FreeBSD__) /* XXX FreeBSD may leak resources on failure cases -- fixme */ device_t bdev; struct usb_attach_arg *uaap; bdev = device_add_child(parent, NULL, -1); if (!bdev) { device_printf(parent, "Device creation failed\n"); return (USBD_INVAL); } device_quiet(bdev); uaap = malloc(sizeof(uaa), M_USB, M_NOWAIT); if (uaap == NULL) { return (USBD_INVAL); } device_set_ivars(bdev, uaap); #endif uaa.device = dev; uaa.iface = NULL; uaa.ifaces = NULL; uaa.nifaces = 0; uaa.usegeneric = 0; uaa.port = port; uaa.configno = UHUB_UNK_CONFIGURATION; uaa.ifaceno = UHUB_UNK_INTERFACE; uaa.vendor = UGETW(dd->idVendor); uaa.product = UGETW(dd->idProduct); uaa.release = UGETW(dd->bcdDevice); /* First try with device specific drivers. */ DPRINTF(("usbd_probe_and_attach: trying device specific drivers\n")); dev->ifacenums = NULL; dev->subdevs = malloc(2 * sizeof dv, M_USB, M_NOWAIT); if (dev->subdevs == NULL) return (USBD_NOMEM); dev->subdevs[0] = bdev; dev->subdevs[1] = 0; *uaap = uaa; dv = USB_DO_ATTACH(dev, bdev, parent, &uaa, usbd_print, usbd_submatch); if (dv) { return (USBD_NORMAL_COMPLETION); } /* * Free subdevs so we can reallocate it larger for the number of * interfaces */ tmpdv = dev->subdevs; dev->subdevs = NULL; free(tmpdv, M_USB); DPRINTF(("usbd_probe_and_attach: no device specific driver found\n")); DPRINTF(("usbd_probe_and_attach: looping over %d configurations\n", dd->bNumConfigurations)); /* Next try with interface drivers. */ for (confi = 0; confi < dd->bNumConfigurations; confi++) { DPRINTFN(1,("usbd_probe_and_attach: trying config idx=%d\n", confi)); err = usbd_set_config_index(dev, confi, 1); if (err) { #ifdef USB_DEBUG DPRINTF(("%s: port %d, set config at addr %d failed, " "error=%s\n", USBDEVPTRNAME(parent), port, addr, usbd_errstr(err))); #else printf("%s: port %d, set config at addr %d failed\n", USBDEVPTRNAME(parent), port, addr); #endif return (err); } nifaces = dev->cdesc->bNumInterface; uaa.configno = dev->cdesc->bConfigurationValue; for (i = 0; i < nifaces; i++) ifaces[i] = &dev->ifaces[i]; uaa.ifaces = ifaces; uaa.nifaces = nifaces; dev->subdevs = malloc((nifaces+1) * sizeof dv, M_USB,M_NOWAIT); if (dev->subdevs == NULL) { return (USBD_NOMEM); } dev->ifacenums = malloc((nifaces) * sizeof(*dev->ifacenums), M_USB,M_NOWAIT); if (dev->ifacenums == NULL) { return (USBD_NOMEM); } found = 0; for (i = 0; i < nifaces; i++) { if (ifaces[i] == NULL) continue; /* interface already claimed */ uaa.iface = ifaces[i]; uaa.ifaceno = ifaces[i]->idesc->bInterfaceNumber; dev->subdevs[found] = bdev; dev->subdevs[found + 1] = 0; dev->ifacenums[found] = i; *uaap = uaa; dv = USB_DO_ATTACH(dev, bdev, parent, &uaa, usbd_print, usbd_submatch); if (dv != NULL) { ifaces[i] = 0; /* consumed */ found++; #if defined(__FreeBSD__) /* create another child for the next iface */ bdev = device_add_child(parent, NULL, -1); if (!bdev) { device_printf(parent, "Device add failed\n"); return (USBD_NORMAL_COMPLETION); } uaap = malloc(sizeof(uaa), M_USB, M_NOWAIT); if (uaap == NULL) { return (USBD_NOMEM); } device_set_ivars(bdev, uaap); device_quiet(bdev); #endif } else { dev->subdevs[found] = 0; } } if (found != 0) { #if defined(__FreeBSD__) /* remove the last created child. It is unused */ device_delete_child(parent, bdev); /* free(uaap, M_USB); */ /* May be needed? xxx */ #endif return (USBD_NORMAL_COMPLETION); } tmpdv = dev->subdevs; dev->subdevs = NULL; free(tmpdv, M_USB); free(dev->ifacenums, M_USB); dev->ifacenums = NULL; } /* No interfaces were attached in any of the configurations. */ if (dd->bNumConfigurations > 1) /* don't change if only 1 config */ usbd_set_config_index(dev, 0, 0); DPRINTF(("usbd_probe_and_attach: no interface drivers found\n")); /* Finally try the generic driver. */ uaa.iface = NULL; uaa.usegeneric = 1; uaa.configno = UHUB_UNK_CONFIGURATION; uaa.ifaceno = UHUB_UNK_INTERFACE; dev->subdevs = malloc(2 * sizeof dv, M_USB, M_NOWAIT); if (dev->subdevs == 0) { return (USBD_NOMEM); } dev->subdevs[0] = bdev; dev->subdevs[1] = 0; *uaap = uaa; dv = USB_DO_ATTACH(dev, bdev, parent, &uaa, usbd_print, usbd_submatch); if (dv != NULL) { return (USBD_NORMAL_COMPLETION); } /* * The generic attach failed, but leave the device as it is. * We just did not find any drivers, that's all. The device is * fully operational and not harming anyone. */ DPRINTF(("usbd_probe_and_attach: generic attach failed\n")); return (USBD_NORMAL_COMPLETION); } /* * Called when a new device has been put in the powered state, * but not yet in the addressed state. * Get initial descriptor, set the address, get full descriptor, * and attach a driver. */ usbd_status usbd_new_device(device_ptr_t parent, usbd_bus_handle bus, int depth, int speed, int port, struct usbd_port *up) { usbd_device_handle dev, adev; struct usbd_device *hub; usb_device_descriptor_t *dd; usb_port_status_t ps; usbd_status err; int addr; int i; int p; DPRINTF(("usbd_new_device bus=%p port=%d depth=%d speed=%d\n", bus, port, depth, speed)); addr = usbd_getnewaddr(bus); if (addr < 0) { printf("%s: No free USB addresses, new device ignored.\n", USBDEVNAME(bus->bdev)); return (USBD_NO_ADDR); } dev = malloc(sizeof *dev, M_USB, M_NOWAIT|M_ZERO); if (dev == NULL) return (USBD_NOMEM); dev->bus = bus; /* Set up default endpoint handle. */ dev->def_ep.edesc = &dev->def_ep_desc; /* Set up default endpoint descriptor. */ dev->def_ep_desc.bLength = USB_ENDPOINT_DESCRIPTOR_SIZE; dev->def_ep_desc.bDescriptorType = UDESC_ENDPOINT; dev->def_ep_desc.bEndpointAddress = USB_CONTROL_ENDPOINT; dev->def_ep_desc.bmAttributes = UE_CONTROL; USETW(dev->def_ep_desc.wMaxPacketSize, USB_MAX_IPACKET); dev->def_ep_desc.bInterval = 0; dev->quirks = &usbd_no_quirk; dev->address = USB_START_ADDR; dev->ddesc.bMaxPacketSize = 0; dev->depth = depth; dev->powersrc = up; dev->myhub = up->parent; up->device = dev; /* Locate port on upstream high speed hub */ for (adev = dev, hub = up->parent; hub != NULL && hub->speed != USB_SPEED_HIGH; adev = hub, hub = hub->myhub) ; if (hub) { for (p = 0; p < hub->hub->hubdesc.bNbrPorts; p++) { if (hub->hub->ports[p].device == adev) { dev->myhsport = &hub->hub->ports[p]; goto found; } } panic("usbd_new_device: cannot find HS port\n"); found: DPRINTFN(1,("usbd_new_device: high speed port %d\n", p)); } else { dev->myhsport = NULL; } dev->speed = speed; dev->langid = USBD_NOLANG; dev->cookie.cookie = ++usb_cookie_no; /* Establish the default pipe. */ err = usbd_setup_pipe(dev, 0, &dev->def_ep, USBD_DEFAULT_INTERVAL, &dev->default_pipe); if (err) { usbd_remove_device(dev, up); return (err); } /* Set the address. Do this early; some devices need that. */ /* Try a few times in case the device is slow (i.e. outside specs.) */ DPRINTFN(5,("usbd_new_device: setting device address=%d\n", addr)); for (i = 0; i < 15; i++) { err = usbd_set_address(dev, addr); if (!err) break; usbd_delay_ms(dev, 200); if ((i & 3) == 3) { DPRINTFN(-1,("usb_new_device: set address %d " "failed - trying a port reset\n", addr)); usbd_reset_port(up->parent, port, &ps); } } if (err) { DPRINTFN(-1,("usb_new_device: set address %d failed\n", addr)); err = USBD_SET_ADDR_FAILED; usbd_remove_device(dev, up); return (err); } /* Allow device time to set new address */ usbd_delay_ms(dev, USB_SET_ADDRESS_SETTLE); dev->address = addr; /* New device address now */ bus->devices[addr] = dev; dd = &dev->ddesc; /* Get the first 8 bytes of the device descriptor. */ err = usbd_get_desc(dev, UDESC_DEVICE, 0, USB_MAX_IPACKET, dd); if (err) { DPRINTFN(-1, ("usbd_new_device: addr=%d, getting first desc " "failed\n", addr)); usbd_remove_device(dev, up); return (err); } if (speed == USB_SPEED_HIGH) { /* Max packet size must be 64 (sec 5.5.3). */ if (dd->bMaxPacketSize != USB_2_MAX_CTRL_PACKET) { #ifdef DIAGNOSTIC printf("usbd_new_device: addr=%d bad max packet size\n", addr); #endif dd->bMaxPacketSize = USB_2_MAX_CTRL_PACKET; } } DPRINTF(("usbd_new_device: adding unit addr=%d, rev=%02x, class=%d, " "subclass=%d, protocol=%d, maxpacket=%d, len=%d, speed=%d\n", addr,UGETW(dd->bcdUSB), dd->bDeviceClass, dd->bDeviceSubClass, dd->bDeviceProtocol, dd->bMaxPacketSize, dd->bLength, dev->speed)); if (dd->bDescriptorType != UDESC_DEVICE) { /* Illegal device descriptor */ DPRINTFN(-1,("usbd_new_device: illegal descriptor %d\n", dd->bDescriptorType)); usbd_remove_device(dev, up); return (USBD_INVAL); } if (dd->bLength < USB_DEVICE_DESCRIPTOR_SIZE) { DPRINTFN(-1,("usbd_new_device: bad length %d\n", dd->bLength)); usbd_remove_device(dev, up); return (USBD_INVAL); } USETW(dev->def_ep_desc.wMaxPacketSize, dd->bMaxPacketSize); err = usbd_reload_device_desc(dev); if (err) { DPRINTFN(-1, ("usbd_new_device: addr=%d, getting full desc " "failed\n", addr)); usbd_remove_device(dev, up); return (err); } /* Assume 100mA bus powered for now. Changed when configured. */ dev->power = USB_MIN_POWER; dev->self_powered = 0; DPRINTF(("usbd_new_device: new dev (addr %d), dev=%p, parent=%p\n", addr, dev, parent)); err = usbd_probe_and_attach(parent, dev, port, addr); if (err) { usbd_remove_device(dev, up); return (err); } usbd_add_dev_event(USB_EVENT_DEVICE_ATTACH, dev); return (USBD_NORMAL_COMPLETION); } usbd_status usbd_reload_device_desc(usbd_device_handle dev) { usbd_status err; int i; /* Get the full device descriptor. */ for (i = 0; i < 3; ++i) { err = usbd_get_device_desc(dev, &dev->ddesc); if (!err) break; usbd_delay_ms(dev, 200); } if (err) return (err); /* Figure out what's wrong with this device. */ dev->quirks = usbd_find_quirk(&dev->ddesc); return (USBD_NORMAL_COMPLETION); } void usbd_remove_device(usbd_device_handle dev, struct usbd_port *up) { DPRINTF(("usbd_remove_device: %p\n", dev)); if (dev->default_pipe != NULL) usbd_kill_pipe(dev->default_pipe); up->device = NULL; dev->bus->devices[dev->address] = NULL; free(dev, M_USB); } #if defined(__NetBSD__) || defined(__OpenBSD__) int usbd_print(void *aux, const char *pnp) { struct usb_attach_arg *uaa = aux; char devinfo[1024]; DPRINTFN(15, ("usbd_print dev=%p\n", uaa->device)); if (pnp) { if (!uaa->usegeneric) return (QUIET); usbd_devinfo(uaa->device, 1, devinfo); printf("%s, %s", devinfo, pnp); } if (uaa->port != 0) printf(" port %d", uaa->port); if (uaa->configno != UHUB_UNK_CONFIGURATION) printf(" configuration %d", uaa->configno); if (uaa->ifaceno != UHUB_UNK_INTERFACE) printf(" interface %d", uaa->ifaceno); #if 0 /* * It gets very crowded with these locators on the attach line. * They are not really needed since they are printed in the clear * by each driver. */ if (uaa->vendor != UHUB_UNK_VENDOR) printf(" vendor 0x%04x", uaa->vendor); if (uaa->product != UHUB_UNK_PRODUCT) printf(" product 0x%04x", uaa->product); if (uaa->release != UHUB_UNK_RELEASE) printf(" release 0x%04x", uaa->release); #endif return (UNCONF); } #if defined(__NetBSD__) int usbd_submatch(struct device *parent, struct cfdata *cf, void *aux) { #elif defined(__OpenBSD__) int usbd_submatch(struct device *parent, void *match, void *aux) { struct cfdata *cf = match; #endif struct usb_attach_arg *uaa = aux; DPRINTFN(5,("usbd_submatch port=%d,%d configno=%d,%d " "ifaceno=%d,%d vendor=%d,%d product=%d,%d release=%d,%d\n", uaa->port, cf->uhubcf_port, uaa->configno, cf->uhubcf_configuration, uaa->ifaceno, cf->uhubcf_interface, uaa->vendor, cf->uhubcf_vendor, uaa->product, cf->uhubcf_product, uaa->release, cf->uhubcf_release)); if (uaa->port != 0 && /* root hub has port 0, it should match */ ((uaa->port != 0 && cf->uhubcf_port != UHUB_UNK_PORT && cf->uhubcf_port != uaa->port) || (uaa->configno != UHUB_UNK_CONFIGURATION && cf->uhubcf_configuration != UHUB_UNK_CONFIGURATION && cf->uhubcf_configuration != uaa->configno) || (uaa->ifaceno != UHUB_UNK_INTERFACE && cf->uhubcf_interface != UHUB_UNK_INTERFACE && cf->uhubcf_interface != uaa->ifaceno) || (uaa->vendor != UHUB_UNK_VENDOR && cf->uhubcf_vendor != UHUB_UNK_VENDOR && cf->uhubcf_vendor != uaa->vendor) || (uaa->product != UHUB_UNK_PRODUCT && cf->uhubcf_product != UHUB_UNK_PRODUCT && cf->uhubcf_product != uaa->product) || (uaa->release != UHUB_UNK_RELEASE && cf->uhubcf_release != UHUB_UNK_RELEASE && cf->uhubcf_release != uaa->release) ) ) return 0; if (cf->uhubcf_vendor != UHUB_UNK_VENDOR && cf->uhubcf_vendor == uaa->vendor && cf->uhubcf_product != UHUB_UNK_PRODUCT && cf->uhubcf_product == uaa->product) { /* We have a vendor&product locator match */ if (cf->uhubcf_release != UHUB_UNK_RELEASE && cf->uhubcf_release == uaa->release) uaa->matchlvl = UMATCH_VENDOR_PRODUCT_REV; else uaa->matchlvl = UMATCH_VENDOR_PRODUCT; } else uaa->matchlvl = 0; return ((*cf->cf_attach->ca_match)(parent, cf, aux)); } #endif void usbd_fill_deviceinfo(usbd_device_handle dev, struct usb_device_info *di, int usedev) { struct usbd_port *p; int i, err, s; di->udi_bus = USBDEVUNIT(dev->bus->bdev); di->udi_addr = dev->address; di->udi_cookie = dev->cookie; usbd_devinfo_vp(dev, di->udi_vendor, di->udi_product, usedev); usbd_printBCD(di->udi_release, UGETW(dev->ddesc.bcdDevice)); di->udi_vendorNo = UGETW(dev->ddesc.idVendor); di->udi_productNo = UGETW(dev->ddesc.idProduct); di->udi_releaseNo = UGETW(dev->ddesc.bcdDevice); di->udi_class = dev->ddesc.bDeviceClass; di->udi_subclass = dev->ddesc.bDeviceSubClass; di->udi_protocol = dev->ddesc.bDeviceProtocol; di->udi_config = dev->config; di->udi_power = dev->self_powered ? 0 : dev->power; di->udi_speed = dev->speed; if (dev->subdevs != NULL) { for (i = 0; dev->subdevs[i] && i < USB_MAX_DEVNAMES; i++) { if (device_is_attached(dev->subdevs[i])) strlcpy(di->udi_devnames[i], USBDEVPTRNAME(dev->subdevs[i]), USB_MAX_DEVNAMELEN); else di->udi_devnames[i][0] = 0; } } else { i = 0; } for (/*i is set */; i < USB_MAX_DEVNAMES; i++) di->udi_devnames[i][0] = 0; /* empty */ if (dev->hub) { for (i = 0; i < sizeof(di->udi_ports) / sizeof(di->udi_ports[0]) && i < dev->hub->hubdesc.bNbrPorts; i++) { p = &dev->hub->ports[i]; if (p->device) err = p->device->address; else { s = UGETW(p->status.wPortStatus); if (s & UPS_PORT_ENABLED) err = USB_PORT_ENABLED; else if (s & UPS_SUSPEND) err = USB_PORT_SUSPENDED; else if (s & UPS_PORT_POWER) err = USB_PORT_POWERED; else err = USB_PORT_DISABLED; } di->udi_ports[i] = err; } di->udi_nports = dev->hub->hubdesc.bNbrPorts; } else di->udi_nports = 0; } void usb_free_device(usbd_device_handle dev) { int ifcidx, nifc; if (dev->default_pipe != NULL) usbd_kill_pipe(dev->default_pipe); if (dev->ifaces != NULL) { nifc = dev->cdesc->bNumInterface; for (ifcidx = 0; ifcidx < nifc; ifcidx++) usbd_free_iface_data(dev, ifcidx); free(dev->ifaces, M_USB); } if (dev->cdesc != NULL) free(dev->cdesc, M_USB); if (dev->subdevs != NULL) free(dev->subdevs, M_USB); if (dev->ifacenums != NULL) free(dev->ifacenums, M_USB); free(dev, M_USB); } /* * The general mechanism for detaching drivers works as follows: Each * driver is responsible for maintaining a reference count on the * number of outstanding references to its softc (e.g. from * processing hanging in a read or write). The detach method of the * driver decrements this counter and flags in the softc that the * driver is dying and then wakes any sleepers. It then sleeps on the * softc. Each place that can sleep must maintain the reference * count. When the reference count drops to -1 (0 is the normal value * of the reference count) the a wakeup on the softc is performed * signaling to the detach waiter that all references are gone. */ /* * Called from process context when we discover that a port has * been disconnected. */ void usb_disconnect_port(struct usbd_port *up, device_ptr_t parent) { usbd_device_handle dev = up->device; const char *hubname = USBDEVPTRNAME(parent); int i; DPRINTFN(3,("uhub_disconnect: up=%p dev=%p port=%d\n", up, dev, up->portno)); #ifdef DIAGNOSTIC if (dev == NULL) { printf("usb_disconnect_port: no device\n"); return; } #endif if (dev->subdevs != NULL) { DPRINTFN(3,("usb_disconnect_port: disconnect subdevs\n")); for (i = 0; dev->subdevs[i]; i++) { printf("%s: at %s", USBDEVPTRNAME(dev->subdevs[i]), hubname); if (up->portno != 0) printf(" port %d", up->portno); printf(" (addr %d) disconnected\n", dev->address); config_detach(dev->subdevs[i], DETACH_FORCE); dev->subdevs[i] = NULL; } } usbd_add_dev_event(USB_EVENT_DEVICE_DETACH, dev); dev->bus->devices[dev->address] = NULL; up->device = NULL; usb_free_device(dev); } #ifdef __OpenBSD__ void *usb_realloc(void *p, u_int size, int pool, int flags) { void *q; q = malloc(size, pool, flags); if (q == NULL) return (NULL); bcopy(p, q, size); free(p, pool); return (q); } #endif diff --git a/sys/dev/usb/usbdi.c b/sys/dev/usb/usbdi.c index 9a571dc1b106..adefde3057d9 100644 --- a/sys/dev/usb/usbdi.c +++ b/sys/dev/usb/usbdi.c @@ -1,1166 +1,1248 @@ -/* $NetBSD: usbdi.c,v 1.104 2004/07/17 20:16:13 mycroft Exp $ */ +/* $NetBSD: usbdi.c,v 1.106 2004/10/24 12:52:40 augustss Exp $ */ #include __FBSDID("$FreeBSD$"); /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #if defined(__NetBSD__) || defined(__OpenBSD__) +#include #include #elif defined(__FreeBSD__) #include #include #include "usb_if.h" #if defined(DIAGNOSTIC) && defined(__i386__) #include #endif #endif #include #include #include #include #include #include #include #include +#include #if defined(__FreeBSD__) #include "usb_if.h" #include #define delay(d) DELAY(d) #endif #ifdef USB_DEBUG #define DPRINTF(x) if (usbdebug) logprintf x #define DPRINTFN(n,x) if (usbdebug>(n)) logprintf x extern int usbdebug; #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif Static usbd_status usbd_ar_pipe(usbd_pipe_handle pipe); Static void usbd_do_request_async_cb (usbd_xfer_handle, usbd_private_handle, usbd_status); Static void usbd_start_next(usbd_pipe_handle pipe); Static usbd_status usbd_open_pipe_ival (usbd_interface_handle, u_int8_t, u_int8_t, usbd_pipe_handle *, int); Static int usbd_xfer_isread(usbd_xfer_handle xfer); Static int usbd_nbuses = 0; void usbd_init(void) { usbd_nbuses++; } void usbd_finish(void) { --usbd_nbuses; } static __inline int usbd_xfer_isread(usbd_xfer_handle xfer) { if (xfer->rqflags & URQ_REQUEST) return (xfer->request.bmRequestType & UT_READ); else return (xfer->pipe->endpoint->edesc->bEndpointAddress & UE_DIR_IN); } #ifdef USB_DEBUG void usbd_dump_iface(struct usbd_interface *iface) { printf("usbd_dump_iface: iface=%p\n", iface); if (iface == NULL) return; printf(" device=%p idesc=%p index=%d altindex=%d priv=%p\n", iface->device, iface->idesc, iface->index, iface->altindex, iface->priv); } void usbd_dump_device(struct usbd_device *dev) { printf("usbd_dump_device: dev=%p\n", dev); if (dev == NULL) return; printf(" bus=%p default_pipe=%p\n", dev->bus, dev->default_pipe); printf(" address=%d config=%d depth=%d speed=%d self_powered=%d " "power=%d langid=%d\n", dev->address, dev->config, dev->depth, dev->speed, dev->self_powered, dev->power, dev->langid); } void usbd_dump_endpoint(struct usbd_endpoint *endp) { printf("usbd_dump_endpoint: endp=%p\n", endp); if (endp == NULL) return; printf(" edesc=%p refcnt=%d\n", endp->edesc, endp->refcnt); if (endp->edesc) printf(" bEndpointAddress=0x%02x\n", endp->edesc->bEndpointAddress); } void usbd_dump_queue(usbd_pipe_handle pipe) { usbd_xfer_handle xfer; printf("usbd_dump_queue: pipe=%p\n", pipe); SIMPLEQ_FOREACH(xfer, &pipe->queue, next) { printf(" xfer=%p\n", xfer); } } void usbd_dump_pipe(usbd_pipe_handle pipe) { printf("usbd_dump_pipe: pipe=%p\n", pipe); if (pipe == NULL) return; usbd_dump_iface(pipe->iface); usbd_dump_device(pipe->device); usbd_dump_endpoint(pipe->endpoint); printf(" (usbd_dump_pipe:)\n refcnt=%d running=%d aborting=%d\n", pipe->refcnt, pipe->running, pipe->aborting); printf(" intrxfer=%p, repeat=%d, interval=%d\n", pipe->intrxfer, pipe->repeat, pipe->interval); } #endif usbd_status usbd_open_pipe(usbd_interface_handle iface, u_int8_t address, u_int8_t flags, usbd_pipe_handle *pipe) { return (usbd_open_pipe_ival(iface, address, flags, pipe, USBD_DEFAULT_INTERVAL)); } usbd_status usbd_open_pipe_ival(usbd_interface_handle iface, u_int8_t address, u_int8_t flags, usbd_pipe_handle *pipe, int ival) { usbd_pipe_handle p; struct usbd_endpoint *ep; usbd_status err; int i; DPRINTFN(3,("usbd_open_pipe: iface=%p address=0x%x flags=0x%x\n", iface, address, flags)); for (i = 0; i < iface->idesc->bNumEndpoints; i++) { ep = &iface->endpoints[i]; if (ep->edesc == NULL) return (USBD_IOERROR); if (ep->edesc->bEndpointAddress == address) goto found; } return (USBD_BAD_ADDRESS); found: if ((flags & USBD_EXCLUSIVE_USE) && ep->refcnt != 0) return (USBD_IN_USE); err = usbd_setup_pipe(iface->device, iface, ep, ival, &p); if (err) return (err); LIST_INSERT_HEAD(&iface->pipes, p, next); *pipe = p; return (USBD_NORMAL_COMPLETION); } usbd_status usbd_open_pipe_intr(usbd_interface_handle iface, u_int8_t address, u_int8_t flags, usbd_pipe_handle *pipe, usbd_private_handle priv, void *buffer, u_int32_t len, usbd_callback cb, int ival) { usbd_status err; usbd_xfer_handle xfer; usbd_pipe_handle ipipe; DPRINTFN(3,("usbd_open_pipe_intr: address=0x%x flags=0x%x len=%d\n", address, flags, len)); err = usbd_open_pipe_ival(iface, address, USBD_EXCLUSIVE_USE, &ipipe, ival); if (err) return (err); xfer = usbd_alloc_xfer(iface->device); if (xfer == NULL) { err = USBD_NOMEM; goto bad1; } usbd_setup_xfer(xfer, ipipe, priv, buffer, len, flags, USBD_NO_TIMEOUT, cb); ipipe->intrxfer = xfer; ipipe->repeat = 1; err = usbd_transfer(xfer); *pipe = ipipe; if (err != USBD_IN_PROGRESS && err) goto bad2; return (USBD_NORMAL_COMPLETION); bad2: ipipe->intrxfer = NULL; ipipe->repeat = 0; usbd_free_xfer(xfer); bad1: usbd_close_pipe(ipipe); return (err); } usbd_status usbd_close_pipe(usbd_pipe_handle pipe) { #ifdef DIAGNOSTIC if (pipe == NULL) { printf("usbd_close_pipe: pipe==NULL\n"); return (USBD_NORMAL_COMPLETION); } #endif if (--pipe->refcnt != 0) return (USBD_NORMAL_COMPLETION); if (! SIMPLEQ_EMPTY(&pipe->queue)) return (USBD_PENDING_REQUESTS); LIST_REMOVE(pipe, next); pipe->endpoint->refcnt--; pipe->methods->close(pipe); if (pipe->intrxfer != NULL) usbd_free_xfer(pipe->intrxfer); free(pipe, M_USB); return (USBD_NORMAL_COMPLETION); } usbd_status usbd_transfer(usbd_xfer_handle xfer) { usbd_pipe_handle pipe = xfer->pipe; usb_dma_t *dmap = &xfer->dmabuf; usbd_status err; u_int size; int s; DPRINTFN(5,("usbd_transfer: xfer=%p, flags=%d, pipe=%p, running=%d\n", xfer, xfer->flags, pipe, pipe->running)); #ifdef USB_DEBUG if (usbdebug > 5) usbd_dump_queue(pipe); #endif xfer->done = 0; if (pipe->aborting) return (USBD_CANCELLED); size = xfer->length; /* If there is no buffer, allocate one. */ if (!(xfer->rqflags & URQ_DEV_DMABUF) && size != 0) { struct usbd_bus *bus = pipe->device->bus; #ifdef DIAGNOSTIC if (xfer->rqflags & URQ_AUTO_DMABUF) printf("usbd_transfer: has old buffer!\n"); #endif err = bus->methods->allocm(bus, dmap, size); if (err) return (err); xfer->rqflags |= URQ_AUTO_DMABUF; } /* Copy data if going out. */ if (!(xfer->flags & USBD_NO_COPY) && size != 0 && !usbd_xfer_isread(xfer)) memcpy(KERNADDR(dmap, 0), xfer->buffer, size); err = pipe->methods->transfer(xfer); if (err != USBD_IN_PROGRESS && err) { /* The transfer has not been queued, so free buffer. */ if (xfer->rqflags & URQ_AUTO_DMABUF) { struct usbd_bus *bus = pipe->device->bus; bus->methods->freem(bus, &xfer->dmabuf); xfer->rqflags &= ~URQ_AUTO_DMABUF; } } if (!(xfer->flags & USBD_SYNCHRONOUS)) return (err); /* Sync transfer, wait for completion. */ if (err != USBD_IN_PROGRESS) return (err); s = splusb(); if (!xfer->done) { if (pipe->device->bus->use_polling) panic("usbd_transfer: not done"); tsleep(xfer, PRIBIO, "usbsyn", 0); } splx(s); return (xfer->status); } /* Like usbd_transfer(), but waits for completion. */ usbd_status usbd_sync_transfer(usbd_xfer_handle xfer) { xfer->flags |= USBD_SYNCHRONOUS; return (usbd_transfer(xfer)); } void * usbd_alloc_buffer(usbd_xfer_handle xfer, u_int32_t size) { struct usbd_bus *bus = xfer->device->bus; usbd_status err; #ifdef DIAGNOSTIC if (xfer->rqflags & (URQ_DEV_DMABUF | URQ_AUTO_DMABUF)) printf("usbd_alloc_buffer: xfer already has a buffer\n"); #endif err = bus->methods->allocm(bus, &xfer->dmabuf, size); if (err) return (NULL); xfer->rqflags |= URQ_DEV_DMABUF; return (KERNADDR(&xfer->dmabuf, 0)); } void usbd_free_buffer(usbd_xfer_handle xfer) { #ifdef DIAGNOSTIC if (!(xfer->rqflags & (URQ_DEV_DMABUF | URQ_AUTO_DMABUF))) { printf("usbd_free_buffer: no buffer\n"); return; } #endif xfer->rqflags &= ~(URQ_DEV_DMABUF | URQ_AUTO_DMABUF); xfer->device->bus->methods->freem(xfer->device->bus, &xfer->dmabuf); } void * usbd_get_buffer(usbd_xfer_handle xfer) { if (!(xfer->rqflags & URQ_DEV_DMABUF)) return (0); return (KERNADDR(&xfer->dmabuf, 0)); } usbd_xfer_handle usbd_alloc_xfer(usbd_device_handle dev) { usbd_xfer_handle xfer; xfer = dev->bus->methods->allocx(dev->bus); if (xfer == NULL) return (NULL); xfer->device = dev; usb_callout_init(xfer->timeout_handle); DPRINTFN(5,("usbd_alloc_xfer() = %p\n", xfer)); return (xfer); } usbd_status usbd_free_xfer(usbd_xfer_handle xfer) { DPRINTFN(5,("usbd_free_xfer: %p\n", xfer)); if (xfer->rqflags & (URQ_DEV_DMABUF | URQ_AUTO_DMABUF)) usbd_free_buffer(xfer); #if defined(__NetBSD__) && defined(DIAGNOSTIC) if (callout_pending(&xfer->timeout_handle)) { callout_stop(&xfer->timeout_handle); printf("usbd_free_xfer: timout_handle pending"); } #endif xfer->device->bus->methods->freex(xfer->device->bus, xfer); return (USBD_NORMAL_COMPLETION); } void usbd_setup_xfer(usbd_xfer_handle xfer, usbd_pipe_handle pipe, usbd_private_handle priv, void *buffer, u_int32_t length, u_int16_t flags, u_int32_t timeout, usbd_callback callback) { xfer->pipe = pipe; xfer->priv = priv; xfer->buffer = buffer; xfer->length = length; xfer->actlen = 0; xfer->flags = flags; xfer->timeout = timeout; xfer->status = USBD_NOT_STARTED; xfer->callback = callback; xfer->rqflags &= ~URQ_REQUEST; xfer->nframes = 0; } void usbd_setup_default_xfer(usbd_xfer_handle xfer, usbd_device_handle dev, usbd_private_handle priv, u_int32_t timeout, usb_device_request_t *req, void *buffer, u_int32_t length, u_int16_t flags, usbd_callback callback) { xfer->pipe = dev->default_pipe; xfer->priv = priv; xfer->buffer = buffer; xfer->length = length; xfer->actlen = 0; xfer->flags = flags; xfer->timeout = timeout; xfer->status = USBD_NOT_STARTED; xfer->callback = callback; xfer->request = *req; xfer->rqflags |= URQ_REQUEST; xfer->nframes = 0; } void usbd_setup_isoc_xfer(usbd_xfer_handle xfer, usbd_pipe_handle pipe, usbd_private_handle priv, u_int16_t *frlengths, u_int32_t nframes, u_int16_t flags, usbd_callback callback) { xfer->pipe = pipe; xfer->priv = priv; xfer->buffer = 0; xfer->length = 0; xfer->actlen = 0; xfer->flags = flags; xfer->timeout = USBD_NO_TIMEOUT; xfer->status = USBD_NOT_STARTED; xfer->callback = callback; xfer->rqflags &= ~URQ_REQUEST; xfer->frlengths = frlengths; xfer->nframes = nframes; } void usbd_get_xfer_status(usbd_xfer_handle xfer, usbd_private_handle *priv, void **buffer, u_int32_t *count, usbd_status *status) { if (priv != NULL) *priv = xfer->priv; if (buffer != NULL) *buffer = xfer->buffer; if (count != NULL) *count = xfer->actlen; if (status != NULL) *status = xfer->status; } usb_config_descriptor_t * usbd_get_config_descriptor(usbd_device_handle dev) { #ifdef DIAGNOSTIC if (dev == NULL) { printf("usbd_get_config_descriptor: dev == NULL\n"); return (NULL); } #endif return (dev->cdesc); } int usbd_get_speed(usbd_device_handle dev) { return (dev->speed); } usb_interface_descriptor_t * usbd_get_interface_descriptor(usbd_interface_handle iface) { #ifdef DIAGNOSTIC if (iface == NULL) { printf("usbd_get_interface_descriptor: dev == NULL\n"); return (NULL); } #endif return (iface->idesc); } usb_device_descriptor_t * usbd_get_device_descriptor(usbd_device_handle dev) { return (&dev->ddesc); } usb_endpoint_descriptor_t * usbd_interface2endpoint_descriptor(usbd_interface_handle iface, u_int8_t index) { if (index >= iface->idesc->bNumEndpoints) return (0); return (iface->endpoints[index].edesc); } usbd_status usbd_abort_pipe(usbd_pipe_handle pipe) { usbd_status err; int s; #ifdef DIAGNOSTIC if (pipe == NULL) { printf("usbd_close_pipe: pipe==NULL\n"); return (USBD_NORMAL_COMPLETION); } #endif s = splusb(); err = usbd_ar_pipe(pipe); splx(s); return (err); } usbd_status usbd_abort_default_pipe(usbd_device_handle dev) { return (usbd_abort_pipe(dev->default_pipe)); } usbd_status usbd_clear_endpoint_stall(usbd_pipe_handle pipe) { usbd_device_handle dev = pipe->device; usb_device_request_t req; usbd_status err; DPRINTFN(8, ("usbd_clear_endpoint_stall\n")); /* * Clearing en endpoint stall resets the endpoint toggle, so * do the same to the HC toggle. */ pipe->methods->cleartoggle(pipe); req.bmRequestType = UT_WRITE_ENDPOINT; req.bRequest = UR_CLEAR_FEATURE; USETW(req.wValue, UF_ENDPOINT_HALT); USETW(req.wIndex, pipe->endpoint->edesc->bEndpointAddress); USETW(req.wLength, 0); err = usbd_do_request(dev, &req, 0); #if 0 XXX should we do this? if (!err) { pipe->state = USBD_PIPE_ACTIVE; /* XXX activate pipe */ } #endif return (err); } usbd_status usbd_clear_endpoint_stall_async(usbd_pipe_handle pipe) { usbd_device_handle dev = pipe->device; usb_device_request_t req; usbd_status err; pipe->methods->cleartoggle(pipe); req.bmRequestType = UT_WRITE_ENDPOINT; req.bRequest = UR_CLEAR_FEATURE; USETW(req.wValue, UF_ENDPOINT_HALT); USETW(req.wIndex, pipe->endpoint->edesc->bEndpointAddress); USETW(req.wLength, 0); err = usbd_do_request_async(dev, &req, 0); return (err); } void usbd_clear_endpoint_toggle(usbd_pipe_handle pipe) { pipe->methods->cleartoggle(pipe); } usbd_status usbd_endpoint_count(usbd_interface_handle iface, u_int8_t *count) { #ifdef DIAGNOSTIC if (iface == NULL || iface->idesc == NULL) { printf("usbd_endpoint_count: NULL pointer\n"); return (USBD_INVAL); } #endif *count = iface->idesc->bNumEndpoints; return (USBD_NORMAL_COMPLETION); } usbd_status usbd_interface_count(usbd_device_handle dev, u_int8_t *count) { if (dev->cdesc == NULL) return (USBD_NOT_CONFIGURED); *count = dev->cdesc->bNumInterface; return (USBD_NORMAL_COMPLETION); } void usbd_interface2device_handle(usbd_interface_handle iface, usbd_device_handle *dev) { *dev = iface->device; } usbd_status usbd_device2interface_handle(usbd_device_handle dev, u_int8_t ifaceno, usbd_interface_handle *iface) { if (dev->cdesc == NULL) return (USBD_NOT_CONFIGURED); if (ifaceno >= dev->cdesc->bNumInterface) return (USBD_INVAL); *iface = &dev->ifaces[ifaceno]; return (USBD_NORMAL_COMPLETION); } usbd_device_handle usbd_pipe2device_handle(usbd_pipe_handle pipe) { return (pipe->device); } /* XXXX use altno */ usbd_status usbd_set_interface(usbd_interface_handle iface, int altidx) { usb_device_request_t req; usbd_status err; void *endpoints; if (LIST_FIRST(&iface->pipes) != 0) return (USBD_IN_USE); endpoints = iface->endpoints; err = usbd_fill_iface_data(iface->device, iface->index, altidx); if (err) return (err); /* new setting works, we can free old endpoints */ if (endpoints != NULL) free(endpoints, M_USB); #ifdef DIAGNOSTIC if (iface->idesc == NULL) { printf("usbd_set_interface: NULL pointer\n"); return (USBD_INVAL); } #endif req.bmRequestType = UT_WRITE_INTERFACE; req.bRequest = UR_SET_INTERFACE; USETW(req.wValue, iface->idesc->bAlternateSetting); USETW(req.wIndex, iface->idesc->bInterfaceNumber); USETW(req.wLength, 0); return (usbd_do_request(iface->device, &req, 0)); } int usbd_get_no_alts(usb_config_descriptor_t *cdesc, int ifaceno) { char *p = (char *)cdesc; char *end = p + UGETW(cdesc->wTotalLength); usb_interface_descriptor_t *d; int n; for (n = 0; p < end; p += d->bLength) { d = (usb_interface_descriptor_t *)p; if (p + d->bLength <= end && d->bDescriptorType == UDESC_INTERFACE && d->bInterfaceNumber == ifaceno) n++; } return (n); } int usbd_get_interface_altindex(usbd_interface_handle iface) { return (iface->altindex); } usbd_status usbd_get_interface(usbd_interface_handle iface, u_int8_t *aiface) { usb_device_request_t req; req.bmRequestType = UT_READ_INTERFACE; req.bRequest = UR_GET_INTERFACE; USETW(req.wValue, 0); USETW(req.wIndex, iface->idesc->bInterfaceNumber); USETW(req.wLength, 1); return (usbd_do_request(iface->device, &req, aiface)); } /*** Internal routines ***/ /* Dequeue all pipe operations, called at splusb(). */ Static usbd_status usbd_ar_pipe(usbd_pipe_handle pipe) { usbd_xfer_handle xfer; SPLUSBCHECK; DPRINTFN(2,("usbd_ar_pipe: pipe=%p\n", pipe)); #ifdef USB_DEBUG if (usbdebug > 5) usbd_dump_queue(pipe); #endif pipe->repeat = 0; pipe->aborting = 1; while ((xfer = SIMPLEQ_FIRST(&pipe->queue)) != NULL) { DPRINTFN(2,("usbd_ar_pipe: pipe=%p xfer=%p (methods=%p)\n", pipe, xfer, pipe->methods)); /* Make the HC abort it (and invoke the callback). */ pipe->methods->abort(xfer); /* XXX only for non-0 usbd_clear_endpoint_stall(pipe); */ } pipe->aborting = 0; return (USBD_NORMAL_COMPLETION); } /* Called at splusb() */ void usb_transfer_complete(usbd_xfer_handle xfer) { usbd_pipe_handle pipe = xfer->pipe; usb_dma_t *dmap = &xfer->dmabuf; int sync = xfer->flags & USBD_SYNCHRONOUS; int erred = xfer->status == USBD_CANCELLED || xfer->status == USBD_TIMEOUT; int repeat = pipe->repeat; int polling; SPLUSBCHECK; DPRINTFN(5, ("usb_transfer_complete: pipe=%p xfer=%p status=%d " "actlen=%d\n", pipe, xfer, xfer->status, xfer->actlen)); #ifdef DIAGNOSTIC if (xfer->busy_free != XFER_ONQU) { printf("usb_transfer_complete: xfer=%p not busy 0x%08x\n", xfer, xfer->busy_free); return; } #endif #ifdef DIAGNOSTIC if (pipe == NULL) { printf("usbd_transfer_cb: pipe==0, xfer=%p\n", xfer); return; } #endif polling = pipe->device->bus->use_polling; /* XXXX */ if (polling) pipe->running = 0; if (!(xfer->flags & USBD_NO_COPY) && xfer->actlen != 0 && usbd_xfer_isread(xfer)) { #ifdef DIAGNOSTIC if (xfer->actlen > xfer->length) { printf("usb_transfer_complete: actlen > len %d > %d\n", xfer->actlen, xfer->length); xfer->actlen = xfer->length; } #endif memcpy(xfer->buffer, KERNADDR(dmap, 0), xfer->actlen); } /* if we allocated the buffer in usbd_transfer() we free it here. */ if (xfer->rqflags & URQ_AUTO_DMABUF) { if (!repeat) { struct usbd_bus *bus = pipe->device->bus; bus->methods->freem(bus, dmap); xfer->rqflags &= ~URQ_AUTO_DMABUF; } } if (!repeat) { /* Remove request from queue. */ #ifdef DIAGNOSTIC if (xfer != SIMPLEQ_FIRST(&pipe->queue)) printf("usb_transfer_complete: bad dequeue %p != %p\n", xfer, SIMPLEQ_FIRST(&pipe->queue)); xfer->busy_free = XFER_BUSY; #endif SIMPLEQ_REMOVE_HEAD(&pipe->queue, next); } DPRINTFN(5,("usb_transfer_complete: repeat=%d new head=%p\n", repeat, SIMPLEQ_FIRST(&pipe->queue))); /* Count completed transfers. */ ++pipe->device->bus->stats.uds_requests [pipe->endpoint->edesc->bmAttributes & UE_XFERTYPE]; xfer->done = 1; if (!xfer->status && xfer->actlen < xfer->length && !(xfer->flags & USBD_SHORT_XFER_OK)) { DPRINTFN(-1,("usbd_transfer_cb: short transfer %d<%d\n", xfer->actlen, xfer->length)); xfer->status = USBD_SHORT_XFER; } if (xfer->callback) xfer->callback(xfer, xfer->priv, xfer->status); #ifdef DIAGNOSTIC if (pipe->methods->done != NULL) pipe->methods->done(xfer); else printf("usb_transfer_complete: pipe->methods->done == NULL\n"); #else pipe->methods->done(xfer); #endif if (sync && !polling) wakeup(xfer); if (!repeat) { /* XXX should we stop the queue on all errors? */ if (erred && pipe->iface != NULL) /* not control pipe */ pipe->running = 0; else usbd_start_next(pipe); } } usbd_status usb_insert_transfer(usbd_xfer_handle xfer) { usbd_pipe_handle pipe = xfer->pipe; usbd_status err; int s; DPRINTFN(5,("usb_insert_transfer: pipe=%p running=%d timeout=%d\n", pipe, pipe->running, xfer->timeout)); #ifdef DIAGNOSTIC if (xfer->busy_free != XFER_BUSY) { printf("usb_insert_transfer: xfer=%p not busy 0x%08x\n", xfer, xfer->busy_free); return (USBD_INVAL); } xfer->busy_free = XFER_ONQU; #endif s = splusb(); SIMPLEQ_INSERT_TAIL(&pipe->queue, xfer, next); if (pipe->running) err = USBD_IN_PROGRESS; else { pipe->running = 1; err = USBD_NORMAL_COMPLETION; } splx(s); return (err); } /* Called at splusb() */ void usbd_start_next(usbd_pipe_handle pipe) { usbd_xfer_handle xfer; usbd_status err; SPLUSBCHECK; #ifdef DIAGNOSTIC if (pipe == NULL) { printf("usbd_start_next: pipe == NULL\n"); return; } if (pipe->methods == NULL || pipe->methods->start == NULL) { printf("usbd_start_next: pipe=%p no start method\n", pipe); return; } #endif /* Get next request in queue. */ xfer = SIMPLEQ_FIRST(&pipe->queue); DPRINTFN(5, ("usbd_start_next: pipe=%p, xfer=%p\n", pipe, xfer)); if (xfer == NULL) { pipe->running = 0; } else { err = pipe->methods->start(xfer); if (err != USBD_IN_PROGRESS) { printf("usbd_start_next: error=%d\n", err); pipe->running = 0; /* XXX do what? */ } } } usbd_status usbd_do_request(usbd_device_handle dev, usb_device_request_t *req, void *data) { return (usbd_do_request_flags(dev, req, data, 0, 0, USBD_DEFAULT_TIMEOUT)); } usbd_status usbd_do_request_flags(usbd_device_handle dev, usb_device_request_t *req, void *data, u_int16_t flags, int *actlen, u_int32_t timo) { return (usbd_do_request_flags_pipe(dev, dev->default_pipe, req, data, flags, actlen, timo)); } usbd_status usbd_do_request_flags_pipe(usbd_device_handle dev, usbd_pipe_handle pipe, usb_device_request_t *req, void *data, u_int16_t flags, int *actlen, u_int32_t timeout) { usbd_xfer_handle xfer; usbd_status err; #ifdef DIAGNOSTIC #if defined(__i386__) && defined(__FreeBSD__) KASSERT(curthread->td_intr_nesting_level == 0, ("usbd_do_request: in interrupt context")); #endif if (dev->bus->intr_context) { printf("usbd_do_request: not in process context\n"); return (USBD_INVAL); } #endif xfer = usbd_alloc_xfer(dev); if (xfer == NULL) return (USBD_NOMEM); usbd_setup_default_xfer(xfer, dev, 0, timeout, req, data, UGETW(req->wLength), flags, 0); xfer->pipe = pipe; err = usbd_sync_transfer(xfer); #if defined(USB_DEBUG) || defined(DIAGNOSTIC) if (xfer->actlen > xfer->length) DPRINTF(("usbd_do_request: overrun addr=%d type=0x%02x req=0x" "%02x val=%d index=%d rlen=%d length=%d actlen=%d\n", dev->address, xfer->request.bmRequestType, xfer->request.bRequest, UGETW(xfer->request.wValue), UGETW(xfer->request.wIndex), UGETW(xfer->request.wLength), xfer->length, xfer->actlen)); #endif if (actlen != NULL) *actlen = xfer->actlen; if (err == USBD_STALLED) { /* * The control endpoint has stalled. Control endpoints * should not halt, but some may do so anyway so clear * any halt condition. */ usb_device_request_t treq; usb_status_t status; u_int16_t s; usbd_status nerr; treq.bmRequestType = UT_READ_ENDPOINT; treq.bRequest = UR_GET_STATUS; USETW(treq.wValue, 0); USETW(treq.wIndex, 0); USETW(treq.wLength, sizeof(usb_status_t)); usbd_setup_default_xfer(xfer, dev, 0, USBD_DEFAULT_TIMEOUT, &treq, &status,sizeof(usb_status_t), 0, 0); nerr = usbd_sync_transfer(xfer); if (nerr) goto bad; s = UGETW(status.wStatus); DPRINTF(("usbd_do_request: status = 0x%04x\n", s)); if (!(s & UES_HALT)) goto bad; treq.bmRequestType = UT_WRITE_ENDPOINT; treq.bRequest = UR_CLEAR_FEATURE; USETW(treq.wValue, UF_ENDPOINT_HALT); USETW(treq.wIndex, 0); USETW(treq.wLength, 0); usbd_setup_default_xfer(xfer, dev, 0, USBD_DEFAULT_TIMEOUT, &treq, &status, 0, 0, 0); nerr = usbd_sync_transfer(xfer); if (nerr) goto bad; } bad: usbd_free_xfer(xfer); return (err); } void usbd_do_request_async_cb(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { #if defined(USB_DEBUG) || defined(DIAGNOSTIC) if (xfer->actlen > xfer->length) DPRINTF(("usbd_do_request: overrun addr=%d type=0x%02x req=0x" "%02x val=%d index=%d rlen=%d length=%d actlen=%d\n", xfer->pipe->device->address, xfer->request.bmRequestType, xfer->request.bRequest, UGETW(xfer->request.wValue), UGETW(xfer->request.wIndex), UGETW(xfer->request.wLength), xfer->length, xfer->actlen)); #endif usbd_free_xfer(xfer); } /* * Execute a request without waiting for completion. * Can be used from interrupt context. */ usbd_status usbd_do_request_async(usbd_device_handle dev, usb_device_request_t *req, void *data) { usbd_xfer_handle xfer; usbd_status err; xfer = usbd_alloc_xfer(dev); if (xfer == NULL) return (USBD_NOMEM); usbd_setup_default_xfer(xfer, dev, 0, USBD_DEFAULT_TIMEOUT, req, data, UGETW(req->wLength), 0, usbd_do_request_async_cb); err = usbd_transfer(xfer); if (err != USBD_IN_PROGRESS && err) { usbd_free_xfer(xfer); return (err); } return (USBD_NORMAL_COMPLETION); } const struct usbd_quirks * usbd_get_quirks(usbd_device_handle dev) { #ifdef DIAGNOSTIC if (dev == NULL) { printf("usbd_get_quirks: dev == NULL\n"); return 0; } #endif return (dev->quirks); } /* XXX do periodic free() of free list */ /* * Called from keyboard driver when in polling mode. */ void usbd_dopoll(usbd_interface_handle iface) { iface->device->bus->methods->do_poll(iface->device->bus); } void usbd_set_polling(usbd_device_handle dev, int on) { if (on) dev->bus->use_polling++; else dev->bus->use_polling--; /* When polling we need to make sure there is nothing pending to do. */ if (dev->bus->use_polling) dev->bus->methods->soft_intr(dev->bus); } usb_endpoint_descriptor_t * usbd_get_endpoint_descriptor(usbd_interface_handle iface, u_int8_t address) { struct usbd_endpoint *ep; int i; for (i = 0; i < iface->idesc->bNumEndpoints; i++) { ep = &iface->endpoints[i]; if (ep->edesc->bEndpointAddress == address) return (iface->endpoints[i].edesc); } return (0); } /* * usbd_ratecheck() can limit the number of error messages that occurs. * When a device is unplugged it may take up to 0.25s for the hub driver * to notice it. If the driver continuosly tries to do I/O operations * this can generate a large number of messages. */ int usbd_ratecheck(struct timeval *last) { if (last->tv_sec == time_second) return (0); last->tv_sec = time_second; return (1); } /* * Search for a vendor/product pair in an array. The item size is * given as an argument. */ const struct usb_devno * usb_match_device(const struct usb_devno *tbl, u_int nentries, u_int sz, u_int16_t vendor, u_int16_t product) { while (nentries-- > 0) { u_int16_t tproduct = tbl->ud_product; if (tbl->ud_vendor == vendor && (tproduct == product || tproduct == USB_PRODUCT_ANY)) return (tbl); tbl = (const struct usb_devno *)((const char *)tbl + sz); } return (NULL); } + +void +usb_desc_iter_init(usbd_device_handle dev, usbd_desc_iter_t *iter) +{ + const usb_config_descriptor_t *cd = usbd_get_config_descriptor(dev); + + iter->cur = (const uByte *)cd; + iter->end = (const uByte *)cd + UGETW(cd->wTotalLength); +} + +const usb_descriptor_t * +usb_desc_iter_next(usbd_desc_iter_t *iter) +{ + const usb_descriptor_t *desc; + + if (iter->cur + sizeof(usb_descriptor_t) >= iter->end) { + if (iter->cur != iter->end) + printf("usb_desc_iter_next: bad descriptor\n"); + return NULL; + } + desc = (const usb_descriptor_t *)iter->cur; + if (desc->bLength == 0) { + printf("usb_desc_iter_next: descriptor length = 0\n"); + return NULL; + } + iter->cur += desc->bLength; + if (iter->cur > iter->end) { + printf("usb_desc_iter_next: descriptor length too large\n"); + return NULL; + } + return desc; +} + +usbd_status +usbd_get_string(usbd_device_handle dev, int si, char *buf) +{ + int swap = dev->quirks->uq_flags & UQ_SWAP_UNICODE; + usb_string_descriptor_t us; + char *s; + int i, n; + u_int16_t c; + usbd_status err; + int size; + + buf[0] = '\0'; + if (si == 0) + return (USBD_INVAL); + if (dev->quirks->uq_flags & UQ_NO_STRINGS) + return (USBD_STALLED); + if (dev->langid == USBD_NOLANG) { + /* Set up default language */ + err = usbd_get_string_desc(dev, USB_LANGUAGE_TABLE, 0, &us, + &size); + if (err || size < 4) { + DPRINTFN(-1,("usbd_get_string: getting lang failed, using 0\n")); + dev->langid = 0; /* Well, just pick something then */ + } else { + /* Pick the first language as the default. */ + dev->langid = UGETW(us.bString[0]); + } + } + err = usbd_get_string_desc(dev, si, dev->langid, &us, &size); + if (err) + return (err); + s = buf; + n = size / 2 - 1; + for (i = 0; i < n; i++) { + c = UGETW(us.bString[i]); + /* Convert from Unicode, handle buggy strings. */ + if ((c & 0xff00) == 0) + *s++ = c; + else if ((c & 0x00ff) == 0 && swap) + *s++ = c >> 8; + else + *s++ = '?'; + } + *s++ = 0; + return (USBD_NORMAL_COMPLETION); +} + #if defined(__FreeBSD__) int usbd_driver_load(module_t mod, int what, void *arg) { /* XXX should implement something like a function that removes all generic devices */ return (0); } #endif diff --git a/sys/dev/usb/usbdi.h b/sys/dev/usb/usbdi.h index 4e062bb7d3bb..b238b17631b6 100644 --- a/sys/dev/usb/usbdi.h +++ b/sys/dev/usb/usbdi.h @@ -1,278 +1,288 @@ /* $NetBSD: usbdi.h,v 1.64 2004/10/23 13:26:34 augustss Exp $ */ /* $FreeBSD$ */ /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ typedef struct usbd_bus *usbd_bus_handle; typedef struct usbd_device *usbd_device_handle; typedef struct usbd_interface *usbd_interface_handle; typedef struct usbd_pipe *usbd_pipe_handle; typedef struct usbd_xfer *usbd_xfer_handle; typedef void *usbd_private_handle; typedef enum { /* keep in sync with usbd_status_msgs */ USBD_NORMAL_COMPLETION = 0, /* must be 0 */ USBD_IN_PROGRESS, /* 1 */ /* errors */ USBD_PENDING_REQUESTS, /* 2 */ USBD_NOT_STARTED, /* 3 */ USBD_INVAL, /* 4 */ USBD_NOMEM, /* 5 */ USBD_CANCELLED, /* 6 */ USBD_BAD_ADDRESS, /* 7 */ USBD_IN_USE, /* 8 */ USBD_NO_ADDR, /* 9 */ USBD_SET_ADDR_FAILED, /* 10 */ USBD_NO_POWER, /* 11 */ USBD_TOO_DEEP, /* 12 */ USBD_IOERROR, /* 13 */ USBD_NOT_CONFIGURED, /* 14 */ USBD_TIMEOUT, /* 15 */ USBD_SHORT_XFER, /* 16 */ USBD_STALLED, /* 17 */ USBD_INTERRUPTED, /* 18 */ USBD_ERROR_MAX /* must be last */ } usbd_status; typedef void (*usbd_callback)(usbd_xfer_handle, usbd_private_handle, usbd_status); /* Open flags */ #define USBD_EXCLUSIVE_USE 0x01 /* Use default (specified by ep. desc.) interval on interrupt pipe */ #define USBD_DEFAULT_INTERVAL (-1) /* Request flags */ #define USBD_NO_COPY 0x01 /* do not copy data to DMA buffer */ #define USBD_SYNCHRONOUS 0x02 /* wait for completion */ /* in usb.h #define USBD_SHORT_XFER_OK 0x04*/ /* allow short reads */ #define USBD_FORCE_SHORT_XFER 0x08 /* force last short packet on write */ #define USBD_NO_TIMEOUT 0 #define USBD_DEFAULT_TIMEOUT 5000 /* ms = 5 s */ usbd_status usbd_open_pipe(usbd_interface_handle, u_int8_t, u_int8_t, usbd_pipe_handle *); usbd_status usbd_close_pipe(usbd_pipe_handle); usbd_status usbd_transfer(usbd_xfer_handle); usbd_xfer_handle usbd_alloc_xfer(usbd_device_handle); usbd_status usbd_free_xfer(usbd_xfer_handle); void usbd_setup_xfer(usbd_xfer_handle, usbd_pipe_handle, usbd_private_handle, void *, u_int32_t, u_int16_t, u_int32_t, usbd_callback); void usbd_setup_default_xfer(usbd_xfer_handle, usbd_device_handle, usbd_private_handle, u_int32_t, usb_device_request_t *, void *, u_int32_t, u_int16_t, usbd_callback); void usbd_setup_isoc_xfer(usbd_xfer_handle, usbd_pipe_handle, usbd_private_handle, u_int16_t *, u_int32_t, u_int16_t, usbd_callback); void usbd_get_xfer_status(usbd_xfer_handle, usbd_private_handle *, void **, u_int32_t *, usbd_status *); usb_endpoint_descriptor_t *usbd_interface2endpoint_descriptor (usbd_interface_handle, u_int8_t); usbd_status usbd_abort_pipe(usbd_pipe_handle); usbd_status usbd_abort_default_pipe(usbd_device_handle); usbd_status usbd_clear_endpoint_stall(usbd_pipe_handle); usbd_status usbd_clear_endpoint_stall_async(usbd_pipe_handle); void usbd_clear_endpoint_toggle(usbd_pipe_handle); usbd_status usbd_endpoint_count(usbd_interface_handle, u_int8_t *); usbd_status usbd_interface_count(usbd_device_handle, u_int8_t *); void usbd_interface2device_handle(usbd_interface_handle, usbd_device_handle *); usbd_status usbd_device2interface_handle(usbd_device_handle, u_int8_t, usbd_interface_handle *); usbd_device_handle usbd_pipe2device_handle(usbd_pipe_handle); void *usbd_alloc_buffer(usbd_xfer_handle, u_int32_t); void usbd_free_buffer(usbd_xfer_handle); void *usbd_get_buffer(usbd_xfer_handle); usbd_status usbd_sync_transfer(usbd_xfer_handle); usbd_status usbd_open_pipe_intr(usbd_interface_handle, u_int8_t, u_int8_t, usbd_pipe_handle *, usbd_private_handle, void *, u_int32_t, usbd_callback, int); usbd_status usbd_do_request(usbd_device_handle, usb_device_request_t *, void *); usbd_status usbd_do_request_async(usbd_device_handle, usb_device_request_t *, void *); usbd_status usbd_do_request_flags(usbd_device_handle, usb_device_request_t *, void *, u_int16_t, int*, u_int32_t); usbd_status usbd_do_request_flags_pipe(usbd_device_handle, usbd_pipe_handle, usb_device_request_t *, void *, u_int16_t, int *, u_int32_t); usb_interface_descriptor_t *usbd_get_interface_descriptor (usbd_interface_handle); usb_config_descriptor_t *usbd_get_config_descriptor(usbd_device_handle); usb_device_descriptor_t *usbd_get_device_descriptor(usbd_device_handle); int usbd_get_speed(usbd_device_handle); usbd_status usbd_set_interface(usbd_interface_handle, int); int usbd_get_no_alts(usb_config_descriptor_t *, int); usbd_status usbd_get_interface(usbd_interface_handle, u_int8_t *); void usbd_fill_deviceinfo(usbd_device_handle, struct usb_device_info *, int); int usbd_get_interface_altindex(usbd_interface_handle); usb_interface_descriptor_t *usbd_find_idesc(usb_config_descriptor_t *, int, int); usb_endpoint_descriptor_t *usbd_find_edesc(usb_config_descriptor_t *, int, int, int); void usbd_dopoll(usbd_interface_handle); void usbd_set_polling(usbd_device_handle, int); const char *usbd_errstr(usbd_status); void usbd_add_dev_event(int, usbd_device_handle); void usbd_add_drv_event(int, usbd_device_handle, device_ptr_t); void usbd_devinfo(usbd_device_handle, int, char *); const struct usbd_quirks *usbd_get_quirks(usbd_device_handle); usb_endpoint_descriptor_t *usbd_get_endpoint_descriptor (usbd_interface_handle, u_int8_t); usbd_status usbd_reload_device_desc(usbd_device_handle); int usbd_ratecheck(struct timeval *last); +usbd_status usbd_get_string(usbd_device_handle dev, int si, char *buf); + +/* An iterator for descriptors. */ +typedef struct { + const uByte *cur; + const uByte *end; +} usbd_desc_iter_t; +void usb_desc_iter_init(usbd_device_handle dev, usbd_desc_iter_t *iter); +const usb_descriptor_t *usb_desc_iter_next(usbd_desc_iter_t *iter); + /* * The usb_task structs form a queue of things to run in the USB event * thread. Normally this is just device discovery when a connect/disconnect * has been detected. But it may also be used by drivers that need to * perform (short) tasks that must have a process context. */ struct usb_task { TAILQ_ENTRY(usb_task) next; void (*fun)(void *); void *arg; char onqueue; }; void usb_add_task(usbd_device_handle, struct usb_task *); void usb_rem_task(usbd_device_handle, struct usb_task *); #define usb_init_task(t, f, a) ((t)->fun = (f), (t)->arg = (a), (t)->onqueue = 0) struct usb_devno { u_int16_t ud_vendor; u_int16_t ud_product; }; const struct usb_devno *usb_match_device(const struct usb_devno *, u_int, u_int, u_int16_t, u_int16_t); #define usb_lookup(tbl, vendor, product) \ usb_match_device((const struct usb_devno *)(tbl), sizeof (tbl) / sizeof ((tbl)[0]), sizeof ((tbl)[0]), (vendor), (product)) #define USB_PRODUCT_ANY 0xffff /* NetBSD attachment information */ /* Attach data */ struct usb_attach_arg { int port; int configno; int ifaceno; int vendor; int product; int release; int matchlvl; usbd_device_handle device; /* current device */ usbd_interface_handle iface; /* current interface */ int usegeneric; usbd_interface_handle *ifaces; /* all interfaces */ int nifaces; /* number of interfaces */ }; #if defined(__NetBSD__) || defined(__OpenBSD__) /* Match codes. */ /* First five codes is for a whole device. */ #define UMATCH_VENDOR_PRODUCT_REV 14 #define UMATCH_VENDOR_PRODUCT 13 #define UMATCH_VENDOR_DEVCLASS_DEVPROTO 12 #define UMATCH_DEVCLASS_DEVSUBCLASS_DEVPROTO 11 #define UMATCH_DEVCLASS_DEVSUBCLASS 10 /* Next six codes are for interfaces. */ #define UMATCH_VENDOR_PRODUCT_REV_CONF_IFACE 9 #define UMATCH_VENDOR_PRODUCT_CONF_IFACE 8 #define UMATCH_VENDOR_IFACESUBCLASS_IFACEPROTO 7 #define UMATCH_VENDOR_IFACESUBCLASS 6 #define UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO 5 #define UMATCH_IFACECLASS_IFACESUBCLASS 4 #define UMATCH_IFACECLASS 3 #define UMATCH_IFACECLASS_GENERIC 2 /* Generic driver */ #define UMATCH_GENERIC 1 /* No match */ #define UMATCH_NONE 0 #elif defined(__FreeBSD__) /* FreeBSD needs values less than zero */ #define UMATCH_VENDOR_PRODUCT_REV (-10) #define UMATCH_VENDOR_PRODUCT (-20) #define UMATCH_VENDOR_DEVCLASS_DEVPROTO (-30) #define UMATCH_DEVCLASS_DEVSUBCLASS_DEVPROTO (-40) #define UMATCH_DEVCLASS_DEVSUBCLASS (-50) #define UMATCH_VENDOR_PRODUCT_REV_CONF_IFACE (-60) #define UMATCH_VENDOR_PRODUCT_CONF_IFACE (-70) #define UMATCH_VENDOR_IFACESUBCLASS_IFACEPROTO (-80) #define UMATCH_VENDOR_IFACESUBCLASS (-90) #define UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO (-100) #define UMATCH_IFACECLASS_IFACESUBCLASS (-110) #define UMATCH_IFACECLASS (-120) #define UMATCH_IFACECLASS_GENERIC (-130) #define UMATCH_GENERIC (-140) #define UMATCH_NONE (ENXIO) #endif #define USBD_SHOW_DEVICE_CLASS 0x1 #define USBD_SHOW_INTERFACE_CLASS 0x2 #if defined(__FreeBSD__) int usbd_driver_load(module_t mod, int what, void *arg); #endif /* XXX Perhaps USB should have its own levels? */ #ifdef USB_USE_SOFTINTR #ifdef __HAVE_GENERIC_SOFT_INTERRUPTS #define splusb splsoftnet #else #define splusb splsoftclock #endif /* __HAVE_GENERIC_SOFT_INTERRUPTS */ #else #define splusb splbio #endif /* USB_USE_SOFTINTR */ #define splhardusb splbio #define IPL_USB IPL_BIO diff --git a/sys/dev/usb/usbdi_util.c b/sys/dev/usb/usbdi_util.c index e18c6d38e9f9..b275778203dd 100644 --- a/sys/dev/usb/usbdi_util.c +++ b/sys/dev/usb/usbdi_util.c @@ -1,508 +1,544 @@ -/* $NetBSD: usbdi_util.c,v 1.36 2001/11/13 06:24:57 lukem Exp $ */ +/* $NetBSD: usbdi_util.c,v 1.42 2004/12/03 08:53:40 augustss Exp $ */ /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #if defined(__NetBSD__) || defined(__OpenBSD__) #include #include #elif defined(__FreeBSD__) #include #endif #include #include #include #include #ifdef USB_DEBUG #define DPRINTF(x) if (usbdebug) logprintf x #define DPRINTFN(n,x) if (usbdebug>(n)) logprintf x extern int usbdebug; #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif usbd_status usbd_get_desc(usbd_device_handle dev, int type, int index, int len, void *desc) { usb_device_request_t req; DPRINTFN(3,("usbd_get_desc: type=%d, index=%d, len=%d\n", type, index, len)); req.bmRequestType = UT_READ_DEVICE; req.bRequest = UR_GET_DESCRIPTOR; USETW2(req.wValue, type, index); USETW(req.wIndex, 0); USETW(req.wLength, len); return (usbd_do_request(dev, &req, desc)); } usbd_status usbd_get_config_desc(usbd_device_handle dev, int confidx, usb_config_descriptor_t *d) { usbd_status err; DPRINTFN(3,("usbd_get_config_desc: confidx=%d\n", confidx)); err = usbd_get_desc(dev, UDESC_CONFIG, confidx, USB_CONFIG_DESCRIPTOR_SIZE, d); if (err) return (err); if (d->bDescriptorType != UDESC_CONFIG) { DPRINTFN(-1,("usbd_get_config_desc: confidx=%d, bad desc " "len=%d type=%d\n", confidx, d->bLength, d->bDescriptorType)); return (USBD_INVAL); } return (USBD_NORMAL_COMPLETION); } usbd_status usbd_get_config_desc_full(usbd_device_handle dev, int conf, void *d, int size) { DPRINTFN(3,("usbd_get_config_desc_full: conf=%d\n", conf)); return (usbd_get_desc(dev, UDESC_CONFIG, conf, size, d)); } usbd_status usbd_get_device_desc(usbd_device_handle dev, usb_device_descriptor_t *d) { DPRINTFN(3,("usbd_get_device_desc:\n")); return (usbd_get_desc(dev, UDESC_DEVICE, 0, USB_DEVICE_DESCRIPTOR_SIZE, d)); } usbd_status usbd_get_device_status(usbd_device_handle dev, usb_status_t *st) { usb_device_request_t req; req.bmRequestType = UT_READ_DEVICE; req.bRequest = UR_GET_STATUS; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, sizeof(usb_status_t)); return (usbd_do_request(dev, &req, st)); } usbd_status usbd_get_hub_status(usbd_device_handle dev, usb_hub_status_t *st) { usb_device_request_t req; req.bmRequestType = UT_READ_CLASS_DEVICE; req.bRequest = UR_GET_STATUS; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, sizeof(usb_hub_status_t)); return (usbd_do_request(dev, &req, st)); } usbd_status usbd_set_address(usbd_device_handle dev, int addr) { usb_device_request_t req; req.bmRequestType = UT_WRITE_DEVICE; req.bRequest = UR_SET_ADDRESS; USETW(req.wValue, addr); USETW(req.wIndex, 0); USETW(req.wLength, 0); return usbd_do_request(dev, &req, 0); } usbd_status usbd_get_port_status(usbd_device_handle dev, int port, usb_port_status_t *ps) { usb_device_request_t req; req.bmRequestType = UT_READ_CLASS_OTHER; req.bRequest = UR_GET_STATUS; USETW(req.wValue, 0); USETW(req.wIndex, port); USETW(req.wLength, sizeof *ps); return (usbd_do_request(dev, &req, ps)); } usbd_status usbd_clear_hub_feature(usbd_device_handle dev, int sel) { usb_device_request_t req; req.bmRequestType = UT_WRITE_CLASS_DEVICE; req.bRequest = UR_CLEAR_FEATURE; USETW(req.wValue, sel); USETW(req.wIndex, 0); USETW(req.wLength, 0); return (usbd_do_request(dev, &req, 0)); } usbd_status usbd_set_hub_feature(usbd_device_handle dev, int sel) { usb_device_request_t req; req.bmRequestType = UT_WRITE_CLASS_DEVICE; req.bRequest = UR_SET_FEATURE; USETW(req.wValue, sel); USETW(req.wIndex, 0); USETW(req.wLength, 0); return (usbd_do_request(dev, &req, 0)); } usbd_status usbd_clear_port_feature(usbd_device_handle dev, int port, int sel) { usb_device_request_t req; req.bmRequestType = UT_WRITE_CLASS_OTHER; req.bRequest = UR_CLEAR_FEATURE; USETW(req.wValue, sel); USETW(req.wIndex, port); USETW(req.wLength, 0); return (usbd_do_request(dev, &req, 0)); } usbd_status usbd_set_port_feature(usbd_device_handle dev, int port, int sel) { usb_device_request_t req; req.bmRequestType = UT_WRITE_CLASS_OTHER; req.bRequest = UR_SET_FEATURE; USETW(req.wValue, sel); USETW(req.wIndex, port); USETW(req.wLength, 0); return (usbd_do_request(dev, &req, 0)); } +usbd_status +usbd_get_protocol(usbd_interface_handle iface, u_int8_t *report) +{ + usb_interface_descriptor_t *id = usbd_get_interface_descriptor(iface); + usbd_device_handle dev; + usb_device_request_t req; + + DPRINTFN(4, ("usbd_get_protocol: iface=%p, endpt=%d\n", + iface, id->bInterfaceNumber)); + if (id == NULL) + return (USBD_IOERROR); + usbd_interface2device_handle(iface, &dev); + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_PROTOCOL; + USETW(req.wValue, 0); + USETW(req.wIndex, id->bInterfaceNumber); + USETW(req.wLength, 1); + return (usbd_do_request(dev, &req, report)); +} usbd_status usbd_set_protocol(usbd_interface_handle iface, int report) { usb_interface_descriptor_t *id = usbd_get_interface_descriptor(iface); usbd_device_handle dev; usb_device_request_t req; DPRINTFN(4, ("usbd_set_protocol: iface=%p, report=%d, endpt=%d\n", iface, report, id->bInterfaceNumber)); if (id == NULL) return (USBD_IOERROR); usbd_interface2device_handle(iface, &dev); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UR_SET_PROTOCOL; USETW(req.wValue, report); USETW(req.wIndex, id->bInterfaceNumber); USETW(req.wLength, 0); return (usbd_do_request(dev, &req, 0)); } usbd_status usbd_set_report(usbd_interface_handle iface, int type, int id, void *data, int len) { usb_interface_descriptor_t *ifd = usbd_get_interface_descriptor(iface); usbd_device_handle dev; usb_device_request_t req; DPRINTFN(4, ("usbd_set_report: len=%d\n", len)); if (ifd == NULL) return (USBD_IOERROR); usbd_interface2device_handle(iface, &dev); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UR_SET_REPORT; USETW2(req.wValue, type, id); USETW(req.wIndex, ifd->bInterfaceNumber); USETW(req.wLength, len); return (usbd_do_request(dev, &req, data)); } usbd_status usbd_set_report_async(usbd_interface_handle iface, int type, int id, void *data, int len) { usb_interface_descriptor_t *ifd = usbd_get_interface_descriptor(iface); usbd_device_handle dev; usb_device_request_t req; DPRINTFN(4, ("usbd_set_report_async: len=%d\n", len)); if (ifd == NULL) return (USBD_IOERROR); usbd_interface2device_handle(iface, &dev); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UR_SET_REPORT; USETW2(req.wValue, type, id); USETW(req.wIndex, ifd->bInterfaceNumber); USETW(req.wLength, len); return (usbd_do_request_async(dev, &req, data)); } usbd_status usbd_get_report(usbd_interface_handle iface, int type, int id, void *data, int len) { usb_interface_descriptor_t *ifd = usbd_get_interface_descriptor(iface); usbd_device_handle dev; usb_device_request_t req; - DPRINTFN(4, ("usbd_set_report: len=%d\n", len)); - if (id == 0) + DPRINTFN(4, ("usbd_get_report: len=%d\n", len)); + if (ifd == NULL) return (USBD_IOERROR); usbd_interface2device_handle(iface, &dev); req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UR_GET_REPORT; USETW2(req.wValue, type, id); USETW(req.wIndex, ifd->bInterfaceNumber); USETW(req.wLength, len); return (usbd_do_request(dev, &req, data)); } usbd_status usbd_set_idle(usbd_interface_handle iface, int duration, int id) { usb_interface_descriptor_t *ifd = usbd_get_interface_descriptor(iface); usbd_device_handle dev; usb_device_request_t req; DPRINTFN(4, ("usbd_set_idle: %d %d\n", duration, id)); if (ifd == NULL) return (USBD_IOERROR); usbd_interface2device_handle(iface, &dev); req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UR_SET_IDLE; USETW2(req.wValue, duration, id); USETW(req.wIndex, ifd->bInterfaceNumber); USETW(req.wLength, 0); return (usbd_do_request(dev, &req, 0)); } usbd_status usbd_get_report_descriptor(usbd_device_handle dev, int ifcno, int size, void *d) { usb_device_request_t req; req.bmRequestType = UT_READ_INTERFACE; req.bRequest = UR_GET_DESCRIPTOR; USETW2(req.wValue, UDESC_REPORT, 0); /* report id should be 0 */ USETW(req.wIndex, ifcno); USETW(req.wLength, size); return (usbd_do_request(dev, &req, d)); } usb_hid_descriptor_t * usbd_get_hid_descriptor(usbd_interface_handle ifc) { usb_interface_descriptor_t *idesc = usbd_get_interface_descriptor(ifc); usbd_device_handle dev; usb_config_descriptor_t *cdesc; usb_hid_descriptor_t *hd; char *p, *end; if (idesc == NULL) - return (0); + return (NULL); usbd_interface2device_handle(ifc, &dev); cdesc = usbd_get_config_descriptor(dev); p = (char *)idesc + idesc->bLength; end = (char *)cdesc + UGETW(cdesc->wTotalLength); for (; p < end; p += hd->bLength) { hd = (usb_hid_descriptor_t *)p; if (p + hd->bLength <= end && hd->bDescriptorType == UDESC_HID) return (hd); if (hd->bDescriptorType == UDESC_INTERFACE) break; } - return (0); + return (NULL); } usbd_status usbd_read_report_desc(usbd_interface_handle ifc, void **descp, int *sizep, usb_malloc_type mem) { usb_interface_descriptor_t *id; usb_hid_descriptor_t *hid; usbd_device_handle dev; usbd_status err; usbd_interface2device_handle(ifc, &dev); id = usbd_get_interface_descriptor(ifc); if (id == NULL) return (USBD_INVAL); hid = usbd_get_hid_descriptor(ifc); if (hid == NULL) return (USBD_IOERROR); *sizep = UGETW(hid->descrs[0].wDescriptorLength); *descp = malloc(*sizep, mem, M_NOWAIT); if (*descp == NULL) return (USBD_NOMEM); err = usbd_get_report_descriptor(dev, id->bInterfaceNumber, *sizep, *descp); if (err) { free(*descp, mem); *descp = NULL; return (err); } return (USBD_NORMAL_COMPLETION); } usbd_status usbd_get_config(usbd_device_handle dev, u_int8_t *conf) { usb_device_request_t req; req.bmRequestType = UT_READ_DEVICE; req.bRequest = UR_GET_CONFIG; USETW(req.wValue, 0); USETW(req.wIndex, 0); USETW(req.wLength, 1); return (usbd_do_request(dev, &req, conf)); } Static void usbd_bulk_transfer_cb(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status); Static void usbd_bulk_transfer_cb(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { wakeup(xfer); } usbd_status usbd_bulk_transfer(usbd_xfer_handle xfer, usbd_pipe_handle pipe, u_int16_t flags, u_int32_t timeout, void *buf, u_int32_t *size, char *lbl) { usbd_status err; int s, error; usbd_setup_xfer(xfer, pipe, 0, buf, *size, flags, timeout, usbd_bulk_transfer_cb); DPRINTFN(1, ("usbd_bulk_transfer: start transfer %d bytes\n", *size)); s = splusb(); /* don't want callback until tsleep() */ err = usbd_transfer(xfer); if (err != USBD_IN_PROGRESS) { splx(s); return (err); } - error = tsleep(xfer, PZERO | PCATCH, lbl, 0); + error = tsleep((caddr_t)xfer, PZERO | PCATCH, lbl, 0); splx(s); if (error) { DPRINTF(("usbd_bulk_transfer: tsleep=%d\n", error)); usbd_abort_pipe(pipe); return (USBD_INTERRUPTED); } usbd_get_xfer_status(xfer, NULL, NULL, size, &err); DPRINTFN(1,("usbd_bulk_transfer: transferred %d\n", *size)); if (err) { DPRINTF(("usbd_bulk_transfer: error=%d\n", err)); usbd_clear_endpoint_stall(pipe); } return (err); } Static void usbd_intr_transfer_cb(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status); Static void usbd_intr_transfer_cb(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { wakeup(xfer); } usbd_status usbd_intr_transfer(usbd_xfer_handle xfer, usbd_pipe_handle pipe, u_int16_t flags, u_int32_t timeout, void *buf, u_int32_t *size, char *lbl) { usbd_status err; int s, error; usbd_setup_xfer(xfer, pipe, 0, buf, *size, flags, timeout, usbd_intr_transfer_cb); DPRINTFN(1, ("usbd_intr_transfer: start transfer %d bytes\n", *size)); s = splusb(); /* don't want callback until tsleep() */ err = usbd_transfer(xfer); if (err != USBD_IN_PROGRESS) { splx(s); return (err); } error = tsleep(xfer, PZERO | PCATCH, lbl, 0); splx(s); if (error) { DPRINTF(("usbd_intr_transfer: tsleep=%d\n", error)); usbd_abort_pipe(pipe); return (USBD_INTERRUPTED); } usbd_get_xfer_status(xfer, NULL, NULL, size, &err); DPRINTFN(1,("usbd_intr_transfer: transferred %d\n", *size)); if (err) { DPRINTF(("usbd_intr_transfer: error=%d\n", err)); usbd_clear_endpoint_stall(pipe); } return (err); } void usb_detach_wait(device_ptr_t dv) { DPRINTF(("usb_detach_wait: waiting for %s\n", USBDEVPTRNAME(dv))); if (tsleep(dv, PZERO, "usbdet", hz * 60)) printf("usb_detach_wait: %s didn't detach\n", USBDEVPTRNAME(dv)); DPRINTF(("usb_detach_wait: %s done\n", USBDEVPTRNAME(dv))); } void usb_detach_wakeup(device_ptr_t dv) { DPRINTF(("usb_detach_wakeup: for %s\n", USBDEVPTRNAME(dv))); wakeup(dv); } + +const usb_descriptor_t * +usb_find_desc(usbd_device_handle dev, int type, int subtype) +{ + usbd_desc_iter_t iter; + const usb_descriptor_t *desc; + + usb_desc_iter_init(dev, &iter); + for (;;) { + desc = usb_desc_iter_next(&iter); + if (!desc || (desc->bDescriptorType == type && + (subtype == USBD_SUBTYPE_ANY || + subtype == desc->bDescriptorSubtype))) + break; + } + return desc; +} diff --git a/sys/dev/usb/usbdi_util.h b/sys/dev/usb/usbdi_util.h index 51e052f712f8..82ea13b1c303 100644 --- a/sys/dev/usb/usbdi_util.h +++ b/sys/dev/usb/usbdi_util.h @@ -1,90 +1,95 @@ -/* $NetBSD: usbdi_util.h,v 1.29 2004/06/23 02:30:52 mycroft Exp $ */ +/* $NetBSD: usbdi_util.h,v 1.31 2004/12/03 08:53:40 augustss Exp $ */ /* $FreeBSD$ */ /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ usbd_status usbd_get_desc(usbd_device_handle dev, int type, int index, int len, void *desc); usbd_status usbd_get_config_desc(usbd_device_handle, int, usb_config_descriptor_t *); usbd_status usbd_get_config_desc_full(usbd_device_handle, int, void *, int); usbd_status usbd_get_device_desc(usbd_device_handle dev, usb_device_descriptor_t *d); usbd_status usbd_set_address(usbd_device_handle dev, int addr); usbd_status usbd_get_port_status(usbd_device_handle, int, usb_port_status_t *); usbd_status usbd_set_hub_feature(usbd_device_handle dev, int); usbd_status usbd_clear_hub_feature(usbd_device_handle, int); usbd_status usbd_set_port_feature(usbd_device_handle dev, int, int); usbd_status usbd_clear_port_feature(usbd_device_handle, int, int); usbd_status usbd_get_device_status(usbd_device_handle, usb_status_t *); usbd_status usbd_get_hub_status(usbd_device_handle, usb_hub_status_t *); +usbd_status usbd_get_protocol(usbd_interface_handle dev, u_int8_t *report); usbd_status usbd_set_protocol(usbd_interface_handle dev, int report); usbd_status usbd_get_report_descriptor(usbd_device_handle dev, int ifcno, int size, void *d); struct usb_hid_descriptor *usbd_get_hid_descriptor(usbd_interface_handle ifc); usbd_status usbd_set_report(usbd_interface_handle iface, int type, int id, void *data,int len); usbd_status usbd_set_report_async(usbd_interface_handle iface, int type, int id, void *data, int len); usbd_status usbd_get_report(usbd_interface_handle iface, int type, int id, void *data, int len); usbd_status usbd_set_idle(usbd_interface_handle iface, int duration, int id); usbd_status usbd_read_report_desc(usbd_interface_handle ifc, void **descp, int *sizep, usb_malloc_type mem); usbd_status usbd_get_config(usbd_device_handle dev, u_int8_t *conf); usbd_status usbd_get_string_desc(usbd_device_handle dev, int sindex, int langid,usb_string_descriptor_t *sdesc, int *sizep); void usbd_delay_ms(usbd_device_handle, u_int); usbd_status usbd_set_config_no(usbd_device_handle dev, int no, int msg); usbd_status usbd_set_config_index(usbd_device_handle dev, int index, int msg); usbd_status usbd_bulk_transfer(usbd_xfer_handle xfer, usbd_pipe_handle pipe, u_int16_t flags, u_int32_t timeout, void *buf, u_int32_t *size, char *lbl); usbd_status usbd_intr_transfer(usbd_xfer_handle xfer, usbd_pipe_handle pipe, u_int16_t flags, u_int32_t timeout, void *buf, u_int32_t *size, char *lbl); void usb_detach_wait(device_ptr_t); void usb_detach_wakeup(device_ptr_t); +const usb_descriptor_t *usb_find_desc(usbd_device_handle dev, int type, + int subtype); +#define USBD_SUBTYPE_ANY (~0) +