summaryrefslogtreecommitdiff
path: root/sys/dev/usb/usb_subr.c
diff options
context:
space:
mode:
authorFederico G. Schwindt <fgsch@cvs.openbsd.org>1999-08-13 05:28:06 +0000
committerFederico G. Schwindt <fgsch@cvs.openbsd.org>1999-08-13 05:28:06 +0000
commit98a352c6b38d6f2d3c0b32dc777ce44a46a694a3 (patch)
tree8a3031f109ed4ed8a451d0397e4ac38737a835d3 /sys/dev/usb/usb_subr.c
parent0928f76f2da53933e3e26d4d88b8b04022b753ad (diff)
From NetBSD; USB support.
Diffstat (limited to 'sys/dev/usb/usb_subr.c')
-rw-r--r--sys/dev/usb/usb_subr.c1203
1 files changed, 1203 insertions, 0 deletions
diff --git a/sys/dev/usb/usb_subr.c b/sys/dev/usb/usb_subr.c
new file mode 100644
index 00000000000..79779fef325
--- /dev/null
+++ b/sys/dev/usb/usb_subr.c
@@ -0,0 +1,1203 @@
+/* $OpenBSD: usb_subr.c,v 1.1 1999/08/13 05:28:04 fgsch Exp $ */
+/* $NetBSD: usb_subr.c,v 1.34 1999/06/30 06:44:23 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 (augustss@carlstedt.se) 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 <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#if defined(__NetBSD__) || defined(__OpenBSD__)
+#include <sys/device.h>
+#elif defined(__FreeBSD__)
+#include <sys/module.h>
+#include <sys/bus.h>
+#endif
+#include <sys/proc.h>
+#include <sys/select.h>
+
+#include <dev/usb/usb.h>
+
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbdivar.h>
+#include <dev/usb/usbdevs.h>
+#include <dev/usb/usb_quirks.h>
+
+#if defined(__FreeBSD__)
+#include <machine/clock.h>
+#define delay(d) DELAY(d)
+#endif
+
+#ifdef USB_DEBUG
+#define DPRINTF(x) if (usbdebug) printf x
+#define DPRINTFN(n,x) if (usbdebug>(n)) printf x
+extern int usbdebug;
+#else
+#define DPRINTF(x)
+#define DPRINTFN(n,x)
+#endif
+
+static usbd_status usbd_set_config __P((usbd_device_handle, int));
+char *usbd_get_string __P((usbd_device_handle, int, char *));
+int usbd_getnewaddr __P((usbd_bus_handle bus));
+#if defined(__NetBSD__)
+int usbd_print __P((void *aux, const char *pnp));
+int usbd_submatch __P((bdevice *, struct cfdata *cf, void *));
+#elif defined(__OpenBSD__)
+int usbd_print __P((void *aux, const char *pnp));
+int usbd_submatch __P((bdevice *, void *, void *));
+#endif
+void usbd_free_iface_data __P((usbd_device_handle dev, int ifcno));
+void usbd_kill_pipe __P((usbd_pipe_handle));
+usbd_status usbd_probe_and_attach
+ __P((bdevice *parent, usbd_device_handle dev, int port, int addr));
+
+
+#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 <dev/usb/usbdevs_data.h>
+#endif /* USBVERBOSE */
+
+#ifdef USB_DEBUG
+char *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",
+};
+#endif
+
+usbd_status
+usbd_get_string_desc(dev, sindex, langid, sdesc)
+ usbd_device_handle dev;
+ int sindex;
+ int langid;
+ usb_string_descriptor_t *sdesc;
+{
+ usb_device_request_t req;
+ usbd_status r;
+
+ req.bmRequestType = UT_READ_DEVICE;
+ req.bRequest = UR_GET_DESCRIPTOR;
+ USETW2(req.wValue, UDESC_STRING, sindex);
+ USETW(req.wIndex, langid);
+ USETW(req.wLength, 1); /* only size byte first */
+ r = usbd_do_request(dev, &req, sdesc);
+ if (r != USBD_NORMAL_COMPLETION)
+ return (r);
+ USETW(req.wLength, sdesc->bLength); /* the whole string */
+ return (usbd_do_request(dev, &req, sdesc));
+}
+
+char *
+usbd_get_string(dev, si, buf)
+ 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 r;
+
+ if (si == 0)
+ return (0);
+ if (dev->quirks->uq_flags & UQ_NO_STRINGS)
+ return (0);
+ if (dev->langid == USBD_NOLANG) {
+ /* Set up default language */
+ r = usbd_get_string_desc(dev, USB_LANGUAGE_TABLE, 0, &us);
+ if (r != USBD_NORMAL_COMPLETION || us.bLength < 4) {
+ dev->langid = 0; /* Well, just pick English then */
+ } else {
+ /* Pick the first language as the default. */
+ dev->langid = UGETW(us.bString[0]);
+ }
+ }
+ r = usbd_get_string_desc(dev, si, dev->langid, &us);
+ if (r != USBD_NORMAL_COMPLETION)
+ return (0);
+ s = buf;
+ n = us.bLength / 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;
+}
+
+void
+usbd_devinfo_vp(dev, v, p)
+ usbd_device_handle dev;
+ char *v, *p;
+{
+ usb_device_descriptor_t *udd = &dev->ddesc;
+ char *vendor = 0, *product = 0;
+#ifdef USBVERBOSE
+ struct usb_knowndev *kdp;
+#endif
+
+ vendor = usbd_get_string(dev, udd->iManufacturer, v);
+ product = usbd_get_string(dev, udd->iProduct, p);
+#ifdef USBVERBOSE
+ if (!vendor) {
+ 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)
+ vendor = product = NULL;
+ else {
+ vendor = kdp->vendorname;
+ product = (kdp->flags & USB_KNOWNDEV_NOPROD) == 0 ?
+ kdp->productname : NULL;
+ }
+ }
+#endif
+ if (vendor)
+ strcpy(v, vendor);
+ else
+ sprintf(v, "vendor 0x%04x", UGETW(udd->idVendor));
+ if (product)
+ strcpy(p, product);
+ else
+ sprintf(p, "product 0x%04x", UGETW(udd->idProduct));
+}
+
+int
+usbd_printBCD(cp, bcd)
+ char *cp;
+ int bcd;
+{
+ return (sprintf(cp, "%x.%02x", bcd >> 8, bcd & 0xff));
+}
+
+void
+usbd_devinfo(dev, showclass, cp)
+ usbd_device_handle dev;
+ int showclass;
+ char *cp;
+{
+ usb_device_descriptor_t *udd = &dev->ddesc;
+ char vendor[USB_MAX_STRING_LEN];
+ char product[USB_MAX_STRING_LEN];
+ int bcdDevice, bcdUSB;
+
+ usbd_devinfo_vp(dev, vendor, product);
+ cp += sprintf(cp, "%s %s", vendor, product);
+ if (showclass)
+ 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);
+ *cp = 0;
+}
+
+/* Delay for a certain number of ms */
+void
+usb_delay_ms(bus, 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)
+ delay((ms+1) * 1000);
+ else
+ tsleep(&ms, PRIBIO, "usbdly", (ms*hz+999)/1000 + 1);
+}
+
+/* Delay given a device handle. */
+void
+usbd_delay_ms(dev, ms)
+ usbd_device_handle dev;
+ u_int ms;
+{
+ usb_delay_ms(dev->bus, ms);
+}
+
+usbd_status
+usbd_reset_port(dev, port, ps)
+ usbd_device_handle dev;
+ int port;
+ usb_port_status_t *ps;
+{
+ usb_device_request_t req;
+ usbd_status r;
+ 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);
+ r = usbd_do_request(dev, &req, 0);
+ DPRINTFN(1,("usbd_reset_port: port %d reset done, error=%d(%s)\n",
+ port, r, usbd_error_strs[r]));
+ if (r != USBD_NORMAL_COMPLETION)
+ return (r);
+ n = 10;
+ do {
+ /* Wait for device to recover from reset. */
+ usbd_delay_ms(dev, USB_PORT_RESET_DELAY);
+ r = usbd_get_port_status(dev, port, ps);
+ if (r != USBD_NORMAL_COMPLETION) {
+ DPRINTF(("usbd_reset_port: get status failed %d\n",r));
+ return (r);
+ }
+ } while ((UGETW(ps->wPortChange) & UPS_C_PORT_RESET) == 0 && --n > 0);
+ if (n == 0) {
+ printf("usbd_reset_port: timeout\n");
+ return (USBD_IOERROR);
+ }
+ r = usbd_clear_port_feature(dev, port, UHF_C_PORT_RESET);
+#ifdef USB_DEBUG
+ if (r != USBD_NORMAL_COMPLETION)
+ DPRINTF(("usbd_reset_port: clear port feature failed %d\n",r));
+#endif
+
+ /* Wait for the device to recover from reset. */
+ usbd_delay_ms(dev, USB_PORT_RESET_RECOVERY);
+ return (r);
+}
+
+usb_interface_descriptor_t *
+usbd_find_idesc(cd, ifaceidx, altidx)
+ 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 (0);
+}
+
+usb_endpoint_descriptor_t *
+usbd_find_edesc(cd, ifaceidx, altidx, endptidx)
+ 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)
+ return (0);
+ if (endptidx >= d->bNumEndpoints) /* quick exit */
+ return (0);
+
+ 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 (0);
+ if (p <= end && e->bDescriptorType == UDESC_ENDPOINT) {
+ curidx++;
+ if (curidx == endptidx)
+ return (e);
+ }
+ }
+ return (0);
+}
+
+usbd_status
+usbd_fill_iface_data(dev, ifaceidx, altidx)
+ usbd_device_handle dev;
+ int ifaceidx;
+ int altidx;
+{
+ usbd_interface_handle ifc = &dev->ifaces[ifaceidx];
+ char *p, *end;
+ int endpt, nendpt;
+
+ DPRINTFN(4,("usbd_fill_iface_data: ifaceidx=%d altidx=%d\n",
+ ifaceidx, altidx));
+ ifc->device = dev;
+ ifc->idesc = usbd_find_idesc(dev->cdesc, ifaceidx, altidx);
+ if (ifc->idesc == 0)
+ return (USBD_INVAL);
+ ifc->index = ifaceidx;
+ ifc->altindex = altidx;
+ nendpt = ifc->idesc->bNumEndpoints;
+ DPRINTFN(10,("usbd_fill_iface_data: found idesc n=%d\n", nendpt));
+ if (nendpt != 0) {
+ ifc->endpoints = malloc(nendpt * sizeof(struct usbd_endpoint),
+ M_USB, M_NOWAIT);
+ if (ifc->endpoints == 0)
+ return (USBD_NOMEM);
+ } else
+ ifc->endpoints = 0;
+ ifc->priv = 0;
+ 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) {
+ ed = (usb_endpoint_descriptor_t *)p;
+ 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->bDescriptorType == UDESC_INTERFACE ||
+ ed->bLength == 0)
+ break;
+ }
+ /* passed end, or bad desc */
+ goto bad;
+ found:
+ ifc->endpoints[endpt].edesc = ed;
+ ifc->endpoints[endpt].refcnt = 0;
+ ifc->endpoints[endpt].toggle = 0;
+ p += ed->bLength;
+ }
+#undef ed
+ LIST_INIT(&ifc->pipes);
+ return (USBD_NORMAL_COMPLETION);
+
+ bad:
+ free(ifc->endpoints, M_USB);
+ return (USBD_INVAL);
+}
+
+void
+usbd_free_iface_data(dev, ifcno)
+ 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(dev, conf)
+ 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(dev, no, msg)
+ usbd_device_handle dev;
+ int no;
+ int msg;
+{
+ int index;
+ usb_config_descriptor_t cd;
+ usbd_status r;
+
+ DPRINTFN(5,("usbd_set_config_no: %d\n", no));
+ /* Figure out what config index to use. */
+ for (index = 0; index < dev->ddesc.bNumConfigurations; index++) {
+ r = usbd_get_config_desc(dev, index, &cd);
+ if (r != USBD_NORMAL_COMPLETION)
+ return (r);
+ if (cd.bConfigurationValue == no)
+ return (usbd_set_config_index(dev, index, msg));
+ }
+ return (USBD_INVAL);
+}
+
+usbd_status
+usbd_set_config_index(dev, index, msg)
+ usbd_device_handle dev;
+ int index;
+ int msg;
+{
+ usb_status_t ds;
+ usb_config_descriptor_t cd, *cdp;
+ usbd_status r;
+ int ifcidx, nifc, len, selfpowered, power;
+
+ DPRINTFN(5,("usbd_set_config_index: dev=%p index=%d\n", dev, index));
+
+ /* XXX check that all interfaces are idle */
+ if (dev->config != 0) {
+ DPRINTF(("usbd_set_config_index: free old config\n"));
+ /* Free all configuration data structures. */
+ nifc = dev->cdesc->bNumInterface;
+ for (ifcidx = 0; ifcidx < nifc; ifcidx++)
+ usbd_free_iface_data(dev, ifcidx);
+ free(dev->ifaces, M_USB);
+ free(dev->cdesc, M_USB);
+ dev->ifaces = 0;
+ dev->cdesc = 0;
+ dev->config = 0;
+ }
+
+ /* Figure out what config number to use. */
+ r = usbd_get_config_desc(dev, index, &cd);
+ if (r != USBD_NORMAL_COMPLETION)
+ return (r);
+ len = UGETW(cd.wTotalLength);
+ cdp = malloc(len, M_USB, M_NOWAIT);
+ if (cdp == 0)
+ return (USBD_NOMEM);
+ r = usbd_get_desc(dev, UDESC_CONFIG, index, len, cdp);
+ if (r != USBD_NORMAL_COMPLETION)
+ goto bad;
+ if (cdp->bDescriptorType != UDESC_CONFIG) {
+ DPRINTFN(-1,("usbd_set_config_index: bad desc %d\n",
+ cdp->bDescriptorType));
+ r = USBD_INVAL;
+ goto bad;
+ }
+ selfpowered = 0;
+ if (cdp->bmAttributes & UC_SELF_POWERED) {
+ /* May be self powered. */
+ if (cdp->bmAttributes & UC_BUS_POWERED) {
+ /* Must ask device. */
+ r = usbd_get_device_status(dev, &ds);
+ if (r == USBD_NORMAL_COMPLETION &&
+ (UGETW(ds.wStatus) & UDS_SELF_POWERED))
+ selfpowered = 1;
+ DPRINTF(("usbd_set_config_index: status=0x%04x, "
+ "error=%d(%s)\n",
+ UGETW(ds.wStatus), r, usbd_error_strs[r]));
+ } else
+ selfpowered = 1;
+ }
+ DPRINTF(("usbd_set_config_index: (addr %d) attr=0x%02x, "
+ "selfpowered=%d, power=%d\n",
+ dev->address, cdp->bmAttributes,
+ selfpowered, cdp->bMaxPower * 2));
+#ifdef USB_DEBUG
+ if (!dev->powersrc) {
+ printf("usbd_set_config_index: No power source?\n");
+ return (USBD_IOERROR);
+ }
+#endif
+ power = cdp->bMaxPower * 2;
+ if (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);
+ r = USBD_NO_POWER;
+ goto bad;
+ }
+ dev->power = power;
+ dev->self_powered = selfpowered;
+
+ DPRINTF(("usbd_set_config_index: set config %d\n",
+ cdp->bConfigurationValue));
+ r = usbd_set_config(dev, cdp->bConfigurationValue);
+ if (r != USBD_NORMAL_COMPLETION) {
+ DPRINTF(("usbd_set_config_index: setting config=%d failed, "
+ "error=%d(%s)\n",
+ cdp->bConfigurationValue, r, usbd_error_strs[r]));
+ goto bad;
+ }
+ DPRINTF(("usbd_set_config_index: setting new config %d\n",
+ cdp->bConfigurationValue));
+ nifc = cdp->bNumInterface;
+ dev->ifaces = malloc(nifc * sizeof(struct usbd_interface),
+ M_USB, M_NOWAIT);
+ if (dev->ifaces == 0) {
+ r = 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++) {
+ r = usbd_fill_iface_data(dev, ifcidx, 0);
+ if (r != USBD_NORMAL_COMPLETION) {
+ while (--ifcidx >= 0)
+ usbd_free_iface_data(dev, ifcidx);
+ goto bad;
+ }
+ }
+
+ return (USBD_NORMAL_COMPLETION);
+
+ bad:
+ free(cdp, M_USB);
+ return (r);
+}
+
+/* XXX add function for alternate settings */
+
+usbd_status
+usbd_setup_pipe(dev, iface, ep, pipe)
+ usbd_device_handle dev;
+ usbd_interface_handle iface;
+ struct usbd_endpoint *ep;
+ usbd_pipe_handle *pipe;
+{
+ usbd_pipe_handle p;
+ usbd_status r;
+
+ 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 == 0)
+ return (USBD_NOMEM);
+ p->device = dev;
+ p->iface = iface;
+ p->endpoint = ep;
+ ep->refcnt++;
+ p->refcnt = 1;
+ p->intrreqh = 0;
+ p->running = 0;
+ p->repeat = 0;
+ SIMPLEQ_INIT(&p->queue);
+ r = dev->bus->open_pipe(p);
+ if (r != USBD_NORMAL_COMPLETION) {
+ DPRINTFN(-1,("usbd_setup_pipe: endpoint=0x%x failed, error=%d"
+ "(%s)\n",
+ ep->edesc->bEndpointAddress, r, usbd_error_strs[r]));
+ free(p, M_USB);
+ return (r);
+ }
+ *pipe = p;
+ return (USBD_NORMAL_COMPLETION);
+}
+
+/* Abort the device control pipe. */
+void
+usbd_kill_pipe(pipe)
+ usbd_pipe_handle pipe;
+{
+ pipe->methods->close(pipe);
+ pipe->endpoint->refcnt--;
+ free(pipe, M_USB);
+}
+
+int
+usbd_getnewaddr(bus)
+ 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(parent, dev, port, addr)
+ bdevice *parent;
+ usbd_device_handle dev;
+ int port;
+ int addr;
+{
+ struct usb_attach_arg uaa;
+ usb_device_descriptor_t *dd = &dev->ddesc;
+ int r, found, i, confi, nifaces;
+ struct device *dv;
+ usbd_interface_handle ifaces[256]; /* 256 is the absolute max */
+
+#if defined(__FreeBSD__)
+ /*
+ * XXX uaa is a static var. Not a problem as it _should_ be used only
+ * during probe and attach. Should be changed however.
+ */
+ bdevice bdev;
+ bdev = device_add_child(*parent, NULL, -1, &uaa);
+ if (!bdev) {
+ printf("%s: Device creation failed\n", USBDEVNAME(dev->bus->bdev));
+ return (USBD_INVAL);
+ }
+#endif
+
+ uaa.device = dev;
+ uaa.iface = 0;
+ uaa.ifaces = 0;
+ 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. */
+ dv = USB_DO_ATTACH(dev, bdev, parent, &uaa, usbd_print, usbd_submatch);
+ if (dv) {
+ dev->subdevs = malloc(2 * sizeof dv, M_USB, M_NOWAIT);
+ if (dev->subdevs == 0)
+ return (USBD_NOMEM);
+ dev->subdevs[0] = dv;
+ dev->subdevs[1] = 0;
+ return (USBD_NORMAL_COMPLETION);
+ }
+
+ DPRINTF(("usbd_probe_and_attach: no device specific driver found\n"));
+
+ /* Next try with interface drivers. */
+ for (confi = 0; confi < dd->bNumConfigurations; confi++) {
+ DPRINTFN(1,("usbd_probe_and_attach: trying config idx=%d\n",
+ confi));
+ r = usbd_set_config_index(dev, confi, 1);
+ if (r != USBD_NORMAL_COMPLETION) {
+#ifdef USB_DEBUG
+ DPRINTF(("%s: port %d, set config at addr %d failed, "
+ "error=%d(%s)\n", USBDEVNAME(*parent), port,
+ addr, r, usbd_error_strs[r]));
+#else
+ printf("%s: port %d, set config at addr %d failed\n",
+ USBDEVNAME(*parent), port, addr);
+#endif
+#if defined(__FreeBSD__)
+ device_delete_child(*parent, bdev);
+#endif
+ return (r);
+ }
+ 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 == 0)
+ return (USBD_NOMEM);
+ for (found = i = 0; i < nifaces; i++) {
+ if (!ifaces[i])
+ continue; /* interface already claimed */
+ uaa.iface = ifaces[i];
+ uaa.ifaceno = ifaces[i]->idesc->bInterfaceNumber;
+ dv = USB_DO_ATTACH(dev, bdev, parent, &uaa, usbd_print,
+ usbd_submatch);
+ if (dv) {
+ dev->subdevs[found++] = dv;
+ dev->subdevs[found] = 0;
+ ifaces[i] = 0; /* consumed */
+ }
+ }
+ if (found != 0)
+ return (USBD_NORMAL_COMPLETION);
+ free(dev->subdevs, M_USB);
+ dev->subdevs = 0;
+ }
+ /* 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 = 0;
+ uaa.usegeneric = 1;
+ uaa.configno = UHUB_UNK_CONFIGURATION;
+ uaa.ifaceno = UHUB_UNK_INTERFACE;
+ uaa.vendor = UHUB_UNK_VENDOR;
+ uaa.product = UHUB_UNK_PRODUCT;
+ uaa.release = UHUB_UNK_RELEASE;
+ dv = USB_DO_ATTACH(dev, bdev, parent, &uaa, usbd_print, usbd_submatch);
+ if (dv) {
+ dev->subdevs = malloc(2 * sizeof dv, M_USB, M_NOWAIT);
+ if (dev->subdevs == 0)
+ return (USBD_NOMEM);
+ dev->subdevs[0] = dv;
+ dev->subdevs[1] = 0;
+ 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"));
+#if defined(__FreeBSD__)
+/*
+ * XXX should we delete the child again? Left for now to avoid dangling
+ * references.
+ device_delete_child(*parent, bdev);
+*/
+#endif
+ 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(parent, bus, depth, lowspeed, port, up)
+ bdevice *parent;
+ usbd_bus_handle bus;
+ int depth;
+ int lowspeed;
+ int port;
+ struct usbd_port *up;
+{
+ usbd_device_handle dev;
+ usb_device_descriptor_t *dd;
+ usbd_status r;
+ int addr;
+ int i;
+
+ DPRINTF(("usbd_new_device bus=%p depth=%d lowspeed=%d\n",
+ bus, depth, lowspeed));
+ 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);
+ if (dev == 0)
+ return (USBD_NOMEM);
+ memset(dev, 0, sizeof(*dev));
+
+ 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->lowspeed = lowspeed != 0;
+ dev->depth = depth;
+ dev->powersrc = up;
+ dev->langid = USBD_NOLANG;
+
+ /* Establish the the default pipe. */
+ r = usbd_setup_pipe(dev, 0, &dev->def_ep, &dev->default_pipe);
+ if (r != USBD_NORMAL_COMPLETION) {
+ usbd_remove_device(dev, up);
+ return (r);
+ }
+
+ up->device = dev;
+ dd = &dev->ddesc;
+ /* Try a few times in case the device is slow (i.e. outside specs.) */
+ for (i = 0; i < 5; i++) {
+ /* Get the first 8 bytes of the device descriptor. */
+ r = usbd_get_desc(dev, UDESC_DEVICE, 0, USB_MAX_IPACKET, dd);
+ if (r == USBD_NORMAL_COMPLETION)
+ break;
+ usbd_delay_ms(dev, 200);
+ }
+ if (r != USBD_NORMAL_COMPLETION) {
+ DPRINTFN(-1, ("usbd_new_device: addr=%d, getting first desc "
+ "failed\n",
+ addr));
+ usbd_remove_device(dev, up);
+ return (r);
+ }
+
+ 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);
+ }
+
+ DPRINTF(("usbd_new_device: adding unit addr=%d, rev=%02x, class=%d, "
+ "subclass=%d, protocol=%d, maxpacket=%d, ls=%d\n",
+ addr,UGETW(dd->bcdUSB), dd->bDeviceClass, dd->bDeviceSubClass,
+ dd->bDeviceProtocol, dd->bMaxPacketSize, dev->lowspeed));
+
+ USETW(dev->def_ep_desc.wMaxPacketSize, dd->bMaxPacketSize);
+
+ /* Get the full device descriptor. */
+ r = usbd_get_device_desc(dev, dd);
+ if (r != USBD_NORMAL_COMPLETION) {
+ DPRINTFN(-1, ("usbd_new_device: addr=%d, getting full desc "
+ "failed\n", addr));
+ usbd_remove_device(dev, up);
+ return (r);
+ }
+
+ /* Figure out what's wrong with this device. */
+ dev->quirks = usbd_find_quirk(dd);
+
+ /* Set the address */
+ r = usbd_set_address(dev, addr);
+ if (r != USBD_NORMAL_COMPLETION) {
+ DPRINTFN(-1,("usb_new_device: set address %d failed\n",addr));
+ r = USBD_SET_ADDR_FAILED;
+ usbd_remove_device(dev, up);
+ return (r);
+ }
+ /* 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;
+
+ /* 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));
+
+ r = usbd_probe_and_attach(parent, dev, port, addr);
+ if (r != USBD_NORMAL_COMPLETION) {
+ usbd_remove_device(dev, up);
+ return (r);
+ }
+
+ return (USBD_NORMAL_COMPLETION);
+}
+
+void
+usbd_remove_device(dev, up)
+ usbd_device_handle dev;
+ struct usbd_port *up;
+{
+ DPRINTF(("usbd_remove_device: %p\n", dev));
+
+ if (dev->default_pipe)
+ usbd_kill_pipe(dev->default_pipe);
+ up->device = 0;
+ dev->bus->devices[dev->address] = 0;
+
+ free(dev, M_USB);
+}
+
+#if defined(__NetBSD__) || defined(__OpenBSD__)
+int
+usbd_print(aux, pnp)
+ 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(parent, cf, aux)
+ struct device *parent;
+ struct cfdata *cf;
+ void *aux;
+{
+#elif defined(__OpenBSD__)
+int
+usbd_submatch(parent, match, aux)
+ struct device *parent;
+ void *match;
+ void *aux;
+{
+ struct cfdata *cf = match;
+#endif
+ struct usb_attach_arg *uaa = aux;
+
+ if ((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;
+ return ((*cf->cf_attach->ca_match)(parent, cf, aux));
+}
+
+#elif defined(__FreeBSD__)
+static void
+usbd_bus_print_child(device_t bus, device_t dev)
+{
+ /* FIXME print the device address and the configuration used
+ */
+}
+#endif
+
+usbd_status
+usb_insert_transfer(reqh)
+ usbd_request_handle reqh;
+{
+ usbd_pipe_handle pipe = reqh->pipe;
+
+ SIMPLEQ_INSERT_TAIL(&pipe->queue, reqh, next);
+ if (pipe->running)
+ return (USBD_IN_PROGRESS);
+ pipe->running = 1;
+ return (USBD_NORMAL_COMPLETION);
+}
+
+void
+usb_start_next(pipe)
+ usbd_pipe_handle pipe;
+{
+ usbd_request_handle reqh;
+ usbd_status r;
+
+ DPRINTFN(10, ("usb_start_next: pipe=%p\n", pipe));
+
+#ifdef DIAGNOSTIC
+ if (!pipe) {
+ printf("usb_start_next: pipe == 0\n");
+ return;
+ }
+ if (!pipe->methods || !pipe->methods->start) {
+ printf("usb_start_next: no start method\n");
+ return;
+ }
+ if (SIMPLEQ_FIRST(&pipe->queue) == 0) {
+ printf("usb_start_next: empty\n");
+ return;
+ }
+#endif
+
+ /* First remove remove old */
+ SIMPLEQ_REMOVE_HEAD(&pipe->queue, SIMPLEQ_FIRST(&pipe->queue), next);
+ reqh = SIMPLEQ_FIRST(&pipe->queue);
+ DPRINTFN(5, ("usb_start_next: start reqh=%p\n", reqh));
+ if (!reqh)
+ pipe->running = 0;
+ else {
+ r = pipe->methods->start(reqh);
+ if (r != USBD_IN_PROGRESS) {
+ printf("usb_start_next: error=%d\n", r);
+ pipe->running = 0;
+ /* XXX do what? */
+ }
+ }
+}
+
+void
+usbd_fill_deviceinfo(dev, di)
+ usbd_device_handle dev;
+ struct usb_device_info *di;
+{
+ struct usbd_port *p;
+ int i, r, s;
+
+ di->config = dev->config;
+ usbd_devinfo_vp(dev, di->vendor, di->product);
+ usbd_printBCD(di->release, UGETW(dev->ddesc.bcdDevice));
+ di->vendorNo = UGETW(dev->ddesc.idVendor);
+ di->productNo = UGETW(dev->ddesc.idProduct);
+ di->class = dev->ddesc.bDeviceClass;
+ di->power = dev->self_powered ? 0 : dev->power;
+ di->lowspeed = dev->lowspeed;
+ di->addr = dev->address;
+ if (dev->hub) {
+ for (i = 0;
+ i < sizeof(di->ports) / sizeof(di->ports[0]) &&
+ i < dev->hub->hubdesc.bNbrPorts;
+ i++) {
+ p = &dev->hub->ports[i];
+ if (p->device)
+ r = p->device->address;
+ else {
+ s = UGETW(p->status.wPortStatus);
+ if (s & UPS_PORT_ENABLED)
+ r = USB_PORT_ENABLED;
+ else if (s & UPS_SUSPEND)
+ r = USB_PORT_SUSPENDED;
+ else if (s & UPS_PORT_POWER)
+ r = USB_PORT_POWERED;
+ else
+ r = USB_PORT_DISABLED;
+ }
+ di->ports[i] = r;
+ }
+ di->nports = dev->hub->hubdesc.bNbrPorts;
+ } else
+ di->nports = 0;
+}
+
+void
+usb_free_device(dev)
+ usbd_device_handle dev;
+{
+ int ifcidx, nifc;
+
+ if (dev->default_pipe)
+ usbd_kill_pipe(dev->default_pipe);
+ if (dev->ifaces) {
+ nifc = dev->cdesc->bNumInterface;
+ for (ifcidx = 0; ifcidx < nifc; ifcidx++)
+ usbd_free_iface_data(dev, ifcidx);
+ free(dev->ifaces, M_USB);
+ }
+ if (dev->cdesc)
+ free(dev->cdesc, M_USB);
+ if (dev->subdevs)
+ free(dev->subdevs, M_USB);
+ free(dev, M_USB);
+}