# This is a BitKeeper generated patch for the following project: # Project Name: Linux kernel tree # This patch format is intended for GNU patch command version 2.5 or higher. # This patch includes the following deltas: # ChangeSet 1.304 -> 1.305 # drivers/usb/hcd.h 1.1 -> 1.2 # drivers/usb/hcd.c 1.2 -> 1.3 # # The following is the BitKeeper ChangeSet Log # -------------------------------------------- # 02/04/02 david-b@pacbell.net 1.305 # USB hcd core # # update with the 2.5 version of hcd # -------------------------------------------- # diff -Nru a/drivers/usb/hcd.c b/drivers/usb/hcd.c --- a/drivers/usb/hcd.c Wed Apr 3 10:47:53 2002 +++ b/drivers/usb/hcd.c Wed Apr 3 10:47:53 2002 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001 by David Brownell + * Copyright (c) 2001-2002 by David Brownell * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -31,11 +31,9 @@ #include #include #include +#include #include /* for UTS_SYSNAME */ -#ifndef CONFIG_USB_DEBUG - #define CONFIG_USB_DEBUG /* this is experimental! */ -#endif #ifdef CONFIG_USB_DEBUG #define DEBUG @@ -104,6 +102,9 @@ /*-------------------------------------------------------------------------*/ +#define KERNEL_REL ((LINUX_VERSION_CODE >> 16) & 0x0ff) +#define KERNEL_VER ((LINUX_VERSION_CODE >> 8) & 0x0ff) + /* usb 2.0 root hub device descriptor */ static const u8 usb2_rh_dev_descriptor [18] = { 0x12, /* __u8 bLength; */ @@ -117,7 +118,7 @@ 0x00, 0x00, /* __u16 idVendor; */ 0x00, 0x00, /* __u16 idProduct; */ - 0x40, 0x02, /* __u16 bcdDevice; (v2.4) */ + KERNEL_VER, KERNEL_REL, /* __u16 bcdDevice */ 0x03, /* __u8 iManufacturer; */ 0x02, /* __u8 iProduct; */ @@ -140,7 +141,7 @@ 0x00, 0x00, /* __u16 idVendor; */ 0x00, 0x00, /* __u16 idProduct; */ - 0x40, 0x02, /* __u16 bcdDevice; (v2.4) */ + KERNEL_VER, KERNEL_REL, /* __u16 bcdDevice */ 0x03, /* __u8 iManufacturer; */ 0x02, /* __u8 iProduct; */ @@ -151,9 +152,56 @@ /*-------------------------------------------------------------------------*/ -/* Configuration descriptor for all our root hubs */ +/* Configuration descriptors for our root hubs */ + +static const u8 fs_rh_config_descriptor [] = { + + /* one configuration */ + 0x09, /* __u8 bLength; */ + 0x02, /* __u8 bDescriptorType; Configuration */ + 0x19, 0x00, /* __u16 wTotalLength; */ + 0x01, /* __u8 bNumInterfaces; (1) */ + 0x01, /* __u8 bConfigurationValue; */ + 0x00, /* __u8 iConfiguration; */ + 0x40, /* __u8 bmAttributes; + Bit 7: Bus-powered, + 6: Self-powered, + 5 Remote-wakwup, + 4..0: resvd */ + 0x00, /* __u8 MaxPower; */ + + /* USB 1.1: + * USB 2.0, single TT organization (mandatory): + * one interface, protocol 0 + * + * USB 2.0, multiple TT organization (optional): + * two interfaces, protocols 1 (like single TT) + * and 2 (multiple TT mode) ... config is + * sometimes settable + * NOT IMPLEMENTED + */ + + /* one interface */ + 0x09, /* __u8 if_bLength; */ + 0x04, /* __u8 if_bDescriptorType; Interface */ + 0x00, /* __u8 if_bInterfaceNumber; */ + 0x00, /* __u8 if_bAlternateSetting; */ + 0x01, /* __u8 if_bNumEndpoints; */ + 0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */ + 0x00, /* __u8 if_bInterfaceSubClass; */ + 0x00, /* __u8 if_bInterfaceProtocol; [usb1.1 or single tt] */ + 0x00, /* __u8 if_iInterface; */ + + /* one endpoint (status change endpoint) */ + 0x07, /* __u8 ep_bLength; */ + 0x05, /* __u8 ep_bDescriptorType; Endpoint */ + 0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* __u8 ep_bmAttributes; Interrupt */ + 0x02, 0x00, /* __u16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) */ + 0xff /* __u8 ep_bInterval; (255ms -- usb 2.0 spec) */ +}; -static const u8 rh_config_descriptor [] = { +static const u8 hs_rh_config_descriptor [] = { /* one configuration */ 0x09, /* __u8 bLength; */ @@ -197,7 +245,7 @@ 0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ 0x03, /* __u8 ep_bmAttributes; Interrupt */ 0x02, 0x00, /* __u16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) */ - 0x0c /* __u8 ep_bInterval; (12ms -- usb 2.0 spec) */ + 0x0c /* __u8 ep_bInterval; (256ms -- usb 2.0 spec) */ }; /*-------------------------------------------------------------------------*/ @@ -206,12 +254,12 @@ * helper routine for returning string descriptors in UTF-16LE * input can actually be ISO-8859-1; ASCII is its 7-bit subset */ -static int ascii2utf (char *ascii, u8 *utf, int utfmax) +static int ascii2utf (char *s, u8 *utf, int utfmax) { int retval; - for (retval = 0; *ascii && utfmax > 1; utfmax -= 2, retval += 2) { - *utf++ = *ascii++ & 0x7f; + for (retval = 0; *s && utfmax > 1; utfmax -= 2, retval += 2) { + *utf++ = *s++; *utf++ = 0; } return retval; @@ -230,8 +278,7 @@ */ static int rh_string ( int id, - struct pci_dev *pci_desc, - char *type, + struct usb_hcd *hcd, u8 *data, int len ) { @@ -245,15 +292,16 @@ // serial number } else if (id == 1) { - strcpy (buf, pci_desc->slot_name); + strcpy (buf, hcd->bus_name); // product description } else if (id == 2) { - strcpy (buf, pci_desc->name); + strcpy (buf, hcd->product_desc); // id 3 == vendor description } else if (id == 3) { - sprintf (buf, "%s %s %s", UTS_SYSNAME, UTS_RELEASE, type); + sprintf (buf, "%s %s %s", UTS_SYSNAME, UTS_RELEASE, + hcd->description); // unsupported IDs --> "protocol stall" } else @@ -315,14 +363,17 @@ len = 18; break; case USB_DT_CONFIG << 8: - bufp = rh_config_descriptor; - len = sizeof rh_config_descriptor; + if (hcd->driver->flags & HCD_USB2) { + bufp = hs_rh_config_descriptor; + len = sizeof hs_rh_config_descriptor; + } else { + bufp = fs_rh_config_descriptor; + len = sizeof fs_rh_config_descriptor; + } break; case USB_DT_STRING << 8: urb->actual_length = rh_string ( - wValue & 0xff, - hcd->pdev, - (char *) hcd->description, + wValue & 0xff, hcd, ubuf, wLength); break; default: @@ -412,10 +463,8 @@ init_timer (&hcd->rh_timer); hcd->rh_timer.function = rh_report_status; hcd->rh_timer.data = (unsigned long) urb; - hcd->rh_timer.expires = jiffies - + (HZ * (urb->interval < 30 - ? 30 - : urb->interval)) / 1000; + /* USB 2.0 spec says 256msec; this is close enough */ + hcd->rh_timer.expires = jiffies + HZ/4; add_timer (&hcd->rh_timer); return 0; } @@ -521,6 +570,7 @@ * usb_hcd_pci_probe - initialize PCI-based HCDs * @dev: USB Host Controller being probed * @id: pci hotplug id connecting controller to HCD framework + * Context: !in_interrupt() * * Allocates basic PCI resources for this USB host controller, and * then invokes the start() method for the HCD associated with it @@ -606,7 +656,7 @@ return retval; } } - dev->driver_data = hcd; + pci_set_drvdata(dev, hcd); hcd->driver = driver; hcd->description = driver->description; hcd->pdev = dev; @@ -652,6 +702,7 @@ } hcd->bus = bus; hcd->bus_name = dev->slot_name; + hcd->product_desc = dev->name; bus->hcpriv = (void *) hcd; INIT_LIST_HEAD (&hcd->dev_list); @@ -677,6 +728,7 @@ /** * usb_hcd_pci_remove - shutdown processing for PCI-based HCDs * @dev: USB Host Controller being removed + * Context: !in_interrupt() * * Reverses the effect of usb_hcd_pci_probe(), first invoking * the HCD's stop() method. It is always called from a thread @@ -689,7 +741,7 @@ struct usb_hcd *hcd; struct usb_device *hub; - hcd = (struct usb_hcd *) dev->driver_data; + hcd = pci_get_drvdata(dev); if (!hcd) return; info ("remove: %s, state %x", hcd->bus_name, hcd->state); @@ -769,7 +821,7 @@ struct usb_hcd *hcd; int retval; - hcd = (struct usb_hcd *) dev->driver_data; + hcd = pci_get_drvdata(dev); info ("suspend %s to state %d", hcd->bus_name, state); pci_save_state (dev, hcd->pci_state); @@ -798,7 +850,7 @@ struct usb_hcd *hcd; int retval; - hcd = (struct usb_hcd *) dev->driver_data; + hcd = pci_get_drvdata(dev); info ("resume %s", hcd->bus_name); /* guard against multiple resumes (APM bug?) */ @@ -913,8 +965,29 @@ /*-------------------------------------------------------------------------*/ +static void urb_unlink (struct urb *urb) +{ + unsigned long flags; + struct usb_device *dev; + + /* Release any periodic transfer bandwidth */ + if (urb->bandwidth) + usb_release_bandwidth (urb->dev, urb, + usb_pipeisoc (urb->pipe)); + + /* clear all state linking urb to this dev (and hcd) */ + + spin_lock_irqsave (&hcd_data_lock, flags); + list_del_init (&urb->urb_list); + dev = urb->dev; + urb->dev = NULL; + usb_dec_dev_use (dev); + spin_unlock_irqrestore (&hcd_data_lock, flags); +} + + /* may be called in any context with a valid urb->dev usecount */ -/* caller surrenders "ownership" of urb (and chain at urb->next). */ +/* caller surrenders "ownership" of urb */ static int hcd_submit_urb (struct urb *urb) { @@ -922,7 +995,7 @@ struct usb_hcd *hcd; struct hcd_dev *dev; unsigned long flags; - int pipe; + int pipe, temp, max; int mem_flags; if (!urb || urb->hcpriv || !urb->complete) @@ -930,6 +1003,7 @@ urb->status = -EINPROGRESS; urb->actual_length = 0; + urb->bandwidth = 0; INIT_LIST_HEAD (&urb->urb_list); if (!urb->dev || !urb->dev->bus || urb->dev->devnum <= 0) @@ -943,16 +1017,70 @@ if (hcd->state == USB_STATE_QUIESCING || !HCD_IS_RUNNING (hcd->state)) return -ESHUTDOWN; pipe = urb->pipe; + temp = usb_pipetype (urb->pipe); if (usb_endpoint_halted (urb->dev, usb_pipeendpoint (pipe), usb_pipeout (pipe))) return -EPIPE; - // FIXME paging/swapping requests over USB should not use GFP_KERNEL - // and might even need to use GFP_NOIO ... that flag actually needs - // to be passed from the higher level. + /* NOTE: 2.5 passes this value explicitly in submit() */ mem_flags = in_interrupt () ? GFP_ATOMIC : GFP_KERNEL; + /* FIXME there should be a sharable lock protecting us against + * config/altsetting changes and disconnects, kicking in here. + */ + + /* Sanity check, so HCDs can rely on clean data */ + max = usb_maxpacket (urb->dev, pipe, usb_pipeout (pipe)); + if (max <= 0) { + err ("bogus endpoint (bad maxpacket)"); + return -EINVAL; + } + + /* "high bandwidth" mode, 1-3 packets/uframe? */ + if (urb->dev->speed == USB_SPEED_HIGH) { + int mult; + switch (temp) { + case PIPE_ISOCHRONOUS: + case PIPE_INTERRUPT: + mult = 1 + ((max >> 11) & 0x03); + max &= 0x03ff; + max *= mult; + } + } + + /* periodic transfers limit size per frame/uframe */ + switch (temp) { + case PIPE_ISOCHRONOUS: { + int n, len; + + if (urb->number_of_packets <= 0) + return -EINVAL; + for (n = 0; n < urb->number_of_packets; n++) { + len = urb->iso_frame_desc [n].length; + if (len < 0 || len > max) + return -EINVAL; + } + + } + break; + case PIPE_INTERRUPT: + if (urb->transfer_buffer_length > max) + return -EINVAL; + } + + /* the I/O buffer must usually be mapped/unmapped */ + if (urb->transfer_buffer_length < 0) + return -EINVAL; + + if (urb->next) { + warn ("use explicit queuing not urb->next"); + return -EINVAL; + } + #ifdef DEBUG + /* stuff that drivers shouldn't do, but which shouldn't + * cause problems in HCDs if they get it wrong. + */ { unsigned int orig_flags = urb->transfer_flags; unsigned int allowed; @@ -960,7 +1088,7 @@ /* enforce simple/standard policy */ allowed = USB_ASYNC_UNLINK; // affects later unlinks allowed |= USB_NO_FSBR; // only affects UHCI - switch (usb_pipetype (pipe)) { + switch (temp) { case PIPE_CONTROL: allowed |= USB_DISABLE_SPD; break; @@ -977,17 +1105,61 @@ } urb->transfer_flags &= allowed; - /* warn if submitter gave bogus flags */ - if (urb->transfer_flags != orig_flags) - warn ("BOGUS urb flags, %x --> %x", + /* fail if submitter gave bogus flags */ + if (urb->transfer_flags != orig_flags) { + err ("BOGUS urb flags, %x --> %x", orig_flags, urb->transfer_flags); + return -EINVAL; + } } #endif /* - * FIXME: alloc periodic bandwidth here, for interrupt and iso? - * Need to look at the ring submit mechanism for iso tds ... they - * aren't actually "periodic" in 2.4 kernels. + * Force periodic transfer intervals to be legal values that are + * a power of two (so HCDs don't need to). * + * FIXME want bus->{intr,iso}_sched_horizon values here. Each HC + * supports different values... this uses EHCI/UHCI defaults (and + * EHCI can use smaller non-default values). + */ + switch (temp) { + case PIPE_ISOCHRONOUS: + case PIPE_INTERRUPT: + /* too small? */ + if (urb->interval <= 0) + return -EINVAL; + /* too big? */ + switch (urb->dev->speed) { + case USB_SPEED_HIGH: /* units are microframes */ + // NOTE usb handles 2^15 + if (urb->interval > (1024 * 8)) + urb->interval = 1024 * 8; + temp = 1024 * 8; + break; + case USB_SPEED_FULL: /* units are frames/msec */ + case USB_SPEED_LOW: + if (temp == PIPE_INTERRUPT) { + if (urb->interval > 255) + return -EINVAL; + // NOTE ohci only handles up to 32 + temp = 128; + } else { + if (urb->interval > 1024) + urb->interval = 1024; + // NOTE usb and ohci handle up to 2^15 + temp = 1024; + } + break; + default: + return -EINVAL; + } + /* power of two? */ + while (temp > urb->interval) + temp >>= 1; + urb->interval = temp; + } + + + /* * FIXME: make urb timeouts be generic, keeping the HCD cores * as simple as possible. */ @@ -1014,20 +1186,20 @@ status = -ESHUTDOWN; } spin_unlock_irqrestore (&hcd_data_lock, flags); + if (status) + return status; - if (!status) { - if (urb->dev == hcd->bus->root_hub) - status = rh_urb_enqueue (hcd, urb); - else - status = hcd->driver->urb_enqueue (hcd, urb, mem_flags); - } - if (status) { - if (urb->dev) { - urb->status = status; - usb_hcd_giveback_urb (hcd, urb); - } - } - return 0; + if (urb->dev == hcd->bus->root_hub) + status = rh_urb_enqueue (hcd, urb); + else + status = hcd->driver->urb_enqueue (hcd, urb, mem_flags); + /* urb->dev got nulled if hcd called giveback for us + * NOTE: ref to urb->dev is a race without (2.5) refcounting, + * unless driver only returns status when it didn't giveback + */ + if (status && urb->dev) + urb_unlink (urb); + return status; } /*-------------------------------------------------------------------------*/ @@ -1043,7 +1215,7 @@ struct completion_splice { // modified urb context: /* did we complete? */ - int done; + struct completion done; /* original urb data */ void (*complete)(struct urb *); @@ -1061,7 +1233,8 @@ urb->context = splice->context; urb->complete (urb); - splice->done = 1; + /* then let the synchronous unlink call complete */ + complete (&splice->done); } /* @@ -1081,20 +1254,20 @@ if (!urb) return -EINVAL; - // FIXME: add some explicit records to flag the - // state where the URB is "in periodic completion". - // Workaround is for driver to set the urb status - // to "-EINPROGRESS", so it can get through here - // and unlink from the completion handler. - /* * we contend for urb->status with the hcd core, * which changes it while returning the urb. + * + * Caller guaranteed that the urb pointer hasn't been freed, and + * that it was submitted. But as a rule it can't know whether or + * not it's already been unlinked ... so we respect the reversed + * lock sequence needed for the usb_hcd_giveback_urb() code paths + * (urb lock, then hcd_data_lock) in case some other CPU is now + * unlinking it. */ spin_lock_irqsave (&urb->lock, flags); - if (!urb->hcpriv - || urb->status != -EINPROGRESS - || urb->transfer_flags & USB_TIMEOUT_KILLED) { + spin_lock (&hcd_data_lock); + if (!urb->hcpriv || urb->transfer_flags & USB_TIMEOUT_KILLED) { retval = -EINVAL; goto done; } @@ -1103,6 +1276,8 @@ retval = -ENODEV; goto done; } + + /* giveback clears dev; non-null means it's linked at this level */ dev = urb->dev->hcpriv; hcd = urb->dev->bus->hcpriv; if (!dev || !hcd) { @@ -1110,6 +1285,27 @@ goto done; } + /* For non-periodic transfers, any status except -EINPROGRESS means + * the HCD has already started to unlink this URB from the hardware. + * In that case, there's no more work to do. + * + * For periodic transfers, this is the only way to trigger unlinking + * from the hardware. Since we (currently) overload urb->status to + * tell the driver to unlink, error status might get clobbered ... + * unless that transfer hasn't yet restarted. One such case is when + * the URB gets unlinked from its completion handler. + * + * FIXME use an URB_UNLINKED flag to match URB_TIMEOUT_KILLED + */ + switch (usb_pipetype (urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + if (urb->status != -EINPROGRESS) { + retval = -EINVAL; + goto done; + } + } + /* maybe set up to block on completion notification */ if ((urb->transfer_flags & USB_TIMEOUT_KILLED)) urb->status = -ETIMEDOUT; @@ -1120,7 +1316,7 @@ goto done; } /* synchronous unlink: block till we see the completion */ - splice.done = 0; + init_completion (&splice.done); splice.complete = urb->complete; splice.context = urb->context; urb->complete = unlink_complete; @@ -1130,6 +1326,7 @@ /* asynchronous unlink */ urb->status = -ECONNRESET; } + spin_unlock (&hcd_data_lock); spin_unlock_irqrestore (&urb->lock, flags); if (urb == (struct urb *) hcd->rh_timer.data) { @@ -1145,17 +1342,15 @@ if (!(urb->transfer_flags & (USB_ASYNC_UNLINK|USB_TIMEOUT_KILLED)) && HCD_IS_RUNNING (hcd->state) && !retval) { - while (!splice.done) { - set_current_state (TASK_UNINTERRUPTIBLE); - schedule_timeout ((2/*msec*/ * HZ) / 1000); - dbg ("%s: wait for giveback urb %p", - hcd->bus_name, urb); - } + dbg ("%s: wait for giveback urb %p", + hcd->bus_name, urb); + wait_for_completion (&splice.done); } else if ((urb->transfer_flags & USB_ASYNC_UNLINK) && retval == 0) { return -EINPROGRESS; } goto bye; done: + spin_unlock (&hcd_data_lock); spin_unlock_irqrestore (&urb->lock, flags); bye: if (retval) @@ -1223,6 +1418,9 @@ struct usb_hcd *hcd = __hcd; int start = hcd->state; + if (unlikely (hcd->state == USB_STATE_HALT)) /* irq sharing? */ + return; + hcd->driver->irq (hcd); if (hcd->state != start && hcd->state == USB_STATE_HALT) hc_died (hcd); @@ -1234,6 +1432,7 @@ * usb_hcd_giveback_urb - return URB from HCD to device driver * @hcd: host controller returning the URB * @urb: urb being returned to the USB device driver. + * Context: in_interrupt() * * This hands the URB from HCD to its USB device driver, using its * completion function. The HCD has freed all per-urb resources @@ -1246,34 +1445,13 @@ * and will be reissued. They should just call their completion handlers * until the urb is returned to the device driver by unlinking. * - * In common cases, urb->next will be submitted before the completion - * function gets called. That's not done if the URB includes error - * status (including unlinking). + * NOTE that no urb->next processing is done, even for isochronous URBs. + * ISO streaming functionality can be achieved by having completion handlers + * re-queue URBs. Such explicit queuing doesn't discard error reports. */ void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb) { - unsigned long flags; - struct usb_device *dev; - - /* Release periodic transfer bandwidth */ - if (urb->bandwidth) { - switch (usb_pipetype (urb->pipe)) { - case PIPE_INTERRUPT: - usb_release_bandwidth (urb->dev, urb, 0); - break; - case PIPE_ISOCHRONOUS: - usb_release_bandwidth (urb->dev, urb, 1); - break; - } - } - - /* clear all state linking urb to this dev (and hcd) */ - - spin_lock_irqsave (&hcd_data_lock, flags); - list_del_init (&urb->urb_list); - dev = urb->dev; - urb->dev = NULL; - spin_unlock_irqrestore (&hcd_data_lock, flags); + urb_unlink (urb); // NOTE: a generic device/urb monitoring hook would go here. // hcd_monitor_hook(MONITOR_URB_FINISH, urb, dev) @@ -1282,26 +1460,10 @@ // hcd_monitor_hook(MONITOR_URB_UPDATE, urb, dev) if (urb->status) - dbg ("giveback urb %p status %d", urb, urb->status); - - /* if no error, make sure urb->next progresses */ - else if (urb->next) { - int status; - - status = usb_submit_urb (urb->next); - if (status) { - dbg ("urb %p chain fail, %d", urb->next, status); - urb->next->status = -ENOTCONN; - } - - /* HCDs never modify the urb->next chain, and only use it here, - * so that if urb->complete sees an URB there with -ENOTCONN, - * it knows the driver chained it but it couldn't be submitted. - */ - } + dbg ("giveback urb %p status %d len %d", + urb, urb->status, urb->actual_length); /* pass ownership to the completion handler */ - usb_dec_dev_use (dev); urb->complete (urb); } EXPORT_SYMBOL (usb_hcd_giveback_urb); diff -Nru a/drivers/usb/hcd.h b/drivers/usb/hcd.h --- a/drivers/usb/hcd.h Wed Apr 3 10:47:53 2002 +++ b/drivers/usb/hcd.h Wed Apr 3 10:47:53 2002 @@ -37,7 +37,7 @@ struct list_head hcd_list; const char *bus_name; - + const char *product_desc; const char *description; /* "ehci-hcd" etc */ struct timer_list rh_timer; /* drives root hub */