From: Greg KH To: marcelo@conectiva.com.br Cc: linux-usb-devel@lists.sourceforge.net Subject: [PATCH 1 of 6] USB 2.0 support Hi, Here's a patch against 2.4.19-pre1 that adds USB 2.0 support. The 2.0 driver and work was done by David Brownell. The hub driver also has a patch from Martin Diehl that makes the USB core work the way the USB spec says it should work :) thanks, greg k-h diff -Nru a/Documentation/Configure.help b/Documentation/Configure.help --- a/Documentation/Configure.help Mon Feb 25 16:54:36 2002 +++ b/Documentation/Configure.help Mon Feb 25 16:54:36 2002 @@ -12702,6 +12702,30 @@ If you have an MGE Ellipse UPS, or you see timeouts in HID transactions, say Y; otherwise say N. +EHCI (USB 2.0) support +CONFIG_USB_EHCI_HCD + The Enhanced Host Controller Interface (EHCI) is standard for USB 2.0 + "high speed" (480 Mbit/sec, 60 Mbyte/sec) host controller hardware. + If your USB host controller supports USB 2.0, you will likely want to + configure this Host Controller Driver. At this writing, the primary + implementation of EHCI is a chip from NEC, widely available in add-on + PCI cards, but implementations are in the works from other vendors + including Intel and Philips. Motherboard support is appearing. + + EHCI controllers are packaged with "companion" host controllers (OHCI + or UHCI) to handle USB 1.1 devices connected to root hub ports. Ports + will connect to EHCI if it the device is high speed, otherwise they + connect to a companion controller. If you configure EHCI, you should + probably configure the OHCI (for NEC and some other vendors) USB Host + Controller Driver too. + + You may want to read . + + This code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called ehci-hcd.o. If you want to compile it as a + module, say M here and read . + UHCI (Intel PIIX4, VIA, ...) support CONFIG_USB_UHCI The Universal Host Controller Interface is a standard by Intel for diff -Nru a/Documentation/usb/ehci.txt b/Documentation/usb/ehci.txt --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/Documentation/usb/ehci.txt Mon Feb 25 16:54:37 2002 @@ -0,0 +1,164 @@ +18-Dec-2001 + +The EHCI driver is used to talk to high speed USB 2.0 devices using +USB 2.0-capable host controller hardware. The USB 2.0 standard is +compatible with the USB 1.1 standard. It defines three transfer speeds: + + - "High Speed" 480 Mbit/sec (60 MByte/sec) + - "Full Speed" 12 Mbit/sec (1.5 MByte/sec) + - "Low Speed" 1.5 Mbit/sec + +USB 1.1 only addressed full speed and low speed. High speed devices +can be used on USB 1.1 systems, but they slow down to USB 1.1 speeds. + +USB 1.1 devices may also be used on USB 2.0 systems. When plugged +into an EHCI controller, they are given to a USB 1.1 "companion" +controller, which is a OHCI or UHCI controller as normally used with +such devices. When USB 1.1 devices plug into USB 2.0 hubs, they +interact with the EHCI controller through a "Transaction Translator" +(TT) in the hub, which turns low or full speed transactions into +high speed "split transactions" that don't waste transfer bandwidth. + +At this writing, high speed devices are finally beginning to appear. +While usb-storage devices have been available for some time (working +quite speedily on the 2.4 version of this driver), hubs have only +very recently become available. + +Note that USB 2.0 support involves more than just EHCI. It requires +other changes to the Linux-USB core APIs, including the hub driver, +but those changes haven't needed to really change the basic "usbcore" +APIs exposed to USB device drivers. + +- David Brownell + + + +FUNCTIONALITY + +This driver is regularly tested on x86 hardware, and has also been +used on PPC hardware so big/little endianneess issues should be gone. +It's believed to do all the right PCI magic so that I/O works even on +systems with interesting DMA mapping issues. + +At this writing the driver should comfortably handle all control and bulk +transfers, including requests to USB 1.1 devices through transaction +translators (TTs) in USB 2.0 hubs. However, there some situations where +the hub driver needs to clear TT error state, which it doesn't yet do. + +Interrupt transfer support is newly functional and not yet as robust as +control and bulk traffic. As yet there is no support for split transaction +scheduling for interrupt transfers, which means among other things that +connecting USB 1.1 hubs, keyboards, and mice to USB 2.0 hubs won't work. +Connect them to USB 1.1 hubs, or to a root hub. + +Isochronous (ISO) transfer support is not yet working. No production +high speed devices are available which would need it (though high quality +webcams are in the works!). Note that split transaction support for ISO +transfers can't share much code with the code for high speed ISO transfers, +since EHCI represents these with a different data structure. + +The EHCI root hub code should hand off USB 1.1 devices to its companion +controller. This driver doesn't need to know anything about those +drivers; a OHCI or UHCI driver that works already doesn't need to change +just because the EHCI driver is also present. + +There are some issues with power management; suspend/resume doesn't +behave quite right at the moment. + + +USE BY + +Assuming you have an EHCI controller (on a PCI card or motherboard) +and have compiled this driver as a module, load this like: + + # modprobe ehci-hcd + +and remove it by: + + # rmmod ehci-hcd + +You should also have a driver for a "companion controller", such as +"ohci-hcd", "usb-ohci", "usb-uhci", or "uhci". In case of any trouble +with the EHCI driver, remove its module and then the driver for that +companion controller will take over (at lower speed) all the devices +that were previously handled by the EHCI driver. + +Module parameters (pass to "modprobe") include: + + log2_irq_thresh (default 0): + Log2 of default interrupt delay, in microframes. The default + value is 0, indicating 1 microframe (125 usec). Maximum value + is 6, indicating 2^6 = 64 microframes. This controls how often + the EHCI controller can issue interrupts. + +The EHCI interrupt handler just acknowledges interrupts and schedules +a tasklet to handle whatever needs handling. That keeps latencies low, +no matter how often interrupts are issued. + +Device drivers shouldn't care whether they're running over EHCI or not, +but they may want to check for "usb_device->speed == USB_SPEED_HIGH". +High speed devices can do things that full speed (or low speed) ones +can't, such as "high bandwidth" periodic (interrupt or ISO) transfers. + + +PERFORMANCE + +USB 2.0 throughput is gated by two main factors: how fast the host +controller can process requests, and how fast devices can respond to +them. The 480 Mbit/sec "raw transfer rate" is obeyed by all devices, +but aggregate throughput is also affected by issues like delays between +individual high speed packets, driver intelligence, and of course the +overall system load. Latency is also a performance concern. + +Bulk transfers are most often used where throughput is an issue. It's +good to keep in mind that bulk transfers are always in 512 byte packets, +and at most 13 of those fit into one USB 2.0 microframe. Eight USB 2.0 +microframes fit in a USB 1.1 frame; a microframe is 1 msec/8 = 125 usec. + +Hardware Performance + +At this writing, individual USB 2.0 devices tend to max out at around +20 MByte/sec transfer rates. This is of course subject to change; +and some devices now go faster, while others go slower. + +The NEC implementation of EHCI seems to have a hardware bottleneck +at around 28 MByte/sec aggregate transfer rate. While this is clearly +enough for a single device at 20 MByte/sec, putting three such devices +onto one bus does not get you 60 MByte/sec. The issue appears to be +that the controller hardware won't do concurrent USB and PCI access, +so that it's only trying six (or maybe seven) USB transactions each +microframe rather than thirteen. (Seems like a reasonable trade off +for a product that beat all the others to market by over a year!) +It's expected that newer implementations will better this, throwing +more silicon real estate at the problem so that new motherboard chip +sets will get closer to that 60 MByte/sec target. + +There's a minimum latency of one microframe (125 usec) for the host +to receive interrupts from the EHCI controller indicating completion +of requests. That latency is tunable; there's a module option. By +default ehci-hcd driver uses the minimum latency, which means that if +you issue a control or bulk request you can often expect to learn that +it completed in less than 250 usec (depending on transfer size). + +Software Performance + +To get even 20 MByte/sec transfer rates, Linux-USB device drivers will +need to keep the EHCI queue full. That means issuing large requests, +or using bulk queuing if a series of small requests needs to be issued. +When drivers don't do that, their performance results will show it. + +In typical situations, a usb_bulk_msg() loop writing out 4 KB chunks is +going to waste more than half the USB 2.0 bandwidth. Delays between the +I/O completion and the driver issuing the next request will take longer +than the I/O. If that same loop used 16 KB chunks, it'd be better; a +sequence of 128 KB chunks would waste a lot less. + +But rather than depending on such large I/O buffers to make synchronous +I/O be efficient, it's better to just queue all several (bulk) requests +to the HC, and wait for them all to complete (or be canceled on error). +Such URB queuing should work with all the USB 1.1 HC drivers too. + +TBD: Interrupt and ISO transfer performance issues. Those periodic +transfers are fully scheduled, so the main issue is likely to be how +to trigger "high bandwidth" modes. + diff -Nru a/drivers/usb/hcd/Config.in b/drivers/usb/hcd/Config.in --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/Config.in Mon Feb 25 16:54:37 2002 @@ -0,0 +1,7 @@ +# +# USB Host Controller Drivers +# +dep_tristate ' EHCI HCD (USB 2.0) support (EXPERIMENTAL)' CONFIG_USB_EHCI_HCD $CONFIG_USB $CONFIG_EXPERIMENTAL +# dep_tristate ' OHCI HCD support (EXPERIMENTAL)' CONFIG_USB_OHCI_HCD $CONFIG_USB $CONFIG_EXPERIMENTAL +# dep_tristate ' UHCI HCD (most Intel and VIA) support (EXPERIMENTAL)' CONFIG_USB_UHCI_HCD $CONFIG_USB $CONFIG_EXPERIMENTAL + diff -Nru a/drivers/usb/hcd/Makefile b/drivers/usb/hcd/Makefile --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/Makefile Mon Feb 25 16:54:37 2002 @@ -0,0 +1,27 @@ +# +# Makefile for USB Host Controller Driver +# framework and drivers +# + +O_TARGET := + +obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o +# obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o +# obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o + +# Extract lists of the multi-part drivers. +# The 'int-*' lists are the intermediate files used to build the multi's. +multi-y := $(filter $(list-multi), $(obj-y)) +multi-m := $(filter $(list-multi), $(obj-m)) +int-y := $(sort $(foreach m, $(multi-y), $($(basename $(m))-objs))) +int-m := $(sort $(foreach m, $(multi-m), $($(basename $(m))-objs))) + +# Take multi-part drivers out of obj-y and put components in. +obj-y := $(filter-out $(list-multi), $(obj-y)) $(int-y) + +# Translate to Rules.make lists. +OX_OBJS := $(obj-y) +MX_OBJS := $(obj-m) +MIX_OBJS := $(int-m) + +include $(TOPDIR)/Rules.make diff -Nru a/drivers/usb/hcd/ehci-dbg.c b/drivers/usb/hcd/ehci-dbg.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ehci-dbg.c Mon Feb 25 16:54:37 2002 @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2001 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +#ifdef EHCI_VERBOSE_DEBUG +# define vdbg dbg +#else + static inline void vdbg (char *fmt, ...) { } +#endif + +#ifdef DEBUG + +/* check the values in the HCSPARAMS register - host controller structural parameters */ +/* see EHCI 0.95 Spec, Table 2-4 for each value */ +static void dbg_hcs_params (struct ehci_hcd *ehci, char *label) +{ + u32 params = readl (&ehci->caps->hcs_params); + + dbg ("%s hcs_params 0x%x dbg=%d%s cc=%d pcc=%d%s%s ports=%d", + label, params, + HCS_DEBUG_PORT (params), + HCS_INDICATOR (params) ? " ind" : "", + HCS_N_CC (params), + HCS_N_PCC (params), + HCS_PORTROUTED (params) ? "" : " ordered", + HCS_PPC (params) ? "" : " !ppc", + HCS_N_PORTS (params) + ); + /* Port routing, per EHCI 0.95 Spec, Section 2.2.5 */ + if (HCS_PORTROUTED (params)) { + int i; + char buf [46], tmp [7], byte; + + buf[0] = 0; + for (i = 0; i < HCS_N_PORTS (params); i++) { + byte = readb (&ehci->caps->portroute[(i>>1)]); + sprintf(tmp, "%d ", + ((i & 0x1) ? ((byte)&0xf) : ((byte>>4)&0xf))); + strcat(buf, tmp); + } + dbg ("%s: %s portroute %s", + ehci->hcd.bus_name, label, + buf); + } +} +#else + +static inline void dbg_hcs_params (struct ehci_hcd *ehci, char *label) {} + +#endif + +#ifdef DEBUG + +/* check the values in the HCCPARAMS register - host controller capability parameters */ +/* see EHCI 0.95 Spec, Table 2-5 for each value */ +static void dbg_hcc_params (struct ehci_hcd *ehci, char *label) +{ + u32 params = readl (&ehci->caps->hcc_params); + + if (HCC_EXT_CAPS (params)) { + // EHCI 0.96 ... could interpret these (legacy?) + dbg ("%s extended capabilities at pci %d", + label, HCC_EXT_CAPS (params)); + } + if (HCC_ISOC_CACHE (params)) { + dbg ("%s hcc_params 0x%04x caching frame %s%s%s", + label, params, + HCC_PGM_FRAMELISTLEN (params) ? "256/512/1024" : "1024", + HCC_CANPARK (params) ? " park" : "", + HCC_64BIT_ADDR (params) ? " 64 bit addr" : ""); + } else { + dbg ("%s hcc_params 0x%04x caching %d uframes %s%s%s", + label, + params, + HCC_ISOC_THRES (params), + HCC_PGM_FRAMELISTLEN (params) ? "256/512/1024" : "1024", + HCC_CANPARK (params) ? " park" : "", + HCC_64BIT_ADDR (params) ? " 64 bit addr" : ""); + } +} +#else + +static inline void dbg_hcc_params (struct ehci_hcd *ehci, char *label) {} + +#endif + +#ifdef DEBUG + +#if 0 +static void dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + dbg ("%s %p info1 %x info2 %x hw_curr %x qtd_next %x", label, + qh, qh->hw_info1, qh->hw_info2, + qh->hw_current, qh->hw_qtd_next); + dbg (" alt+errs= %x, token= %x, page0= %x, page1= %x", + qh->hw_alt_next, qh->hw_token, + qh->hw_buf [0], qh->hw_buf [1]); + if (qh->hw_buf [2]) { + dbg (" page2= %x, page3= %x, page4= %x", + qh->hw_buf [2], qh->hw_buf [3], + qh->hw_buf [4]); + } +} +#endif + +static const char *const fls_strings [] = + { "1024", "512", "256", "??" }; + +#else +#if 0 +static inline void dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) {} +#endif +#endif /* DEBUG */ + +/* functions have the "wrong" filename when they're output... */ + +#define dbg_status(ehci, label, status) \ + dbg ("%s status 0x%x%s%s%s%s%s%s%s%s%s%s", \ + label, status, \ + (status & STS_ASS) ? " Async" : "", \ + (status & STS_PSS) ? " Periodic" : "", \ + (status & STS_RECL) ? " Recl" : "", \ + (status & STS_HALT) ? " Halt" : "", \ + (status & STS_IAA) ? " IAA" : "", \ + (status & STS_FATAL) ? " FATAL" : "", \ + (status & STS_FLR) ? " FLR" : "", \ + (status & STS_PCD) ? " PCD" : "", \ + (status & STS_ERR) ? " ERR" : "", \ + (status & STS_INT) ? " INT" : "" \ + ) + +#define dbg_cmd(ehci, label, command) \ + dbg ("%s %x cmd %s=%d ithresh=%d%s%s%s%s period=%s%s %s", \ + label, command, \ + (command & CMD_PARK) ? "park" : "(park)", \ + CMD_PARK_CNT (command), \ + (command >> 16) & 0x3f, \ + (command & CMD_LRESET) ? " LReset" : "", \ + (command & CMD_IAAD) ? " IAAD" : "", \ + (command & CMD_ASE) ? " Async" : "", \ + (command & CMD_PSE) ? " Periodic" : "", \ + fls_strings [(command >> 2) & 0x3], \ + (command & CMD_RESET) ? " Reset" : "", \ + (command & CMD_RUN) ? "RUN" : "HALT" \ + ) + +#define dbg_port(hcd, label, port, status) \ + dbg ("%s port %d status 0x%x%s%s speed=%d%s%s%s%s%s%s%s%s%s", \ + label, port, status, \ + (status & PORT_OWNER) ? " OWNER" : "", \ + (status & PORT_POWER) ? " POWER" : "", \ + (status >> 10) & 3, \ + (status & PORT_RESET) ? " RESET" : "", \ + (status & PORT_SUSPEND) ? " SUSPEND" : "", \ + (status & PORT_RESUME) ? " RESUME" : "", \ + (status & PORT_OCC) ? " OCC" : "", \ + (status & PORT_OC) ? " OC" : "", \ + (status & PORT_PEC) ? " PEC" : "", \ + (status & PORT_PE) ? " PE" : "", \ + (status & PORT_CSC) ? " CSC" : "", \ + (status & PORT_CONNECT) ? " CONNECT" : "" \ + ) + diff -Nru a/drivers/usb/hcd/ehci-hcd.c b/drivers/usb/hcd/ehci-hcd.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ehci-hcd.c Mon Feb 25 16:54:37 2002 @@ -0,0 +1,761 @@ +/* + * Copyright (c) 2000-2001 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef CONFIG_USB_DEBUG + #define CONFIG_USB_DEBUG /* this is still experimental! */ +#endif + +#ifdef CONFIG_USB_DEBUG + #define DEBUG +#else + #undef DEBUG +#endif + +#include +#include "../hcd.h" + +#include +#include +#include +#include + +//#undef KERN_DEBUG +//#define KERN_DEBUG "" + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI hc_driver implementation ... experimental, incomplete. + * Based on the 0.96 register interface specification. + * + * There are lots of things to help out with here ... notably + * everything "periodic", and of course testing with all sorts + * of usb 2.0 devices and configurations. + * + * USB 2.0 shows up in upcoming www.pcmcia.org technology. + * First was PCMCIA, like ISA; then CardBus, which is PCI. + * Next comes "CardBay", using USB 2.0 signals. + * + * Contains additional contributions by: + * Brad Hards + * Rory Bolt + * ... + * + * HISTORY: + * 2002-01-14 Minor cleanup; version synch. + * 2002-01-08 Fix roothub handoff of FS/LS to companion controllers. + * 2002-01-04 Control/Bulk queuing behaves. + * 2001-12-12 Initial patch version for Linux 2.5.1 kernel. + */ + +#define DRIVER_VERSION "$Revision: 0.26 $" +#define DRIVER_AUTHOR "David Brownell" +#define DRIVER_DESC "USB 2.0 'Enhanced' Host Controller (EHCI) Driver" + + +// #define EHCI_VERBOSE_DEBUG +// #define have_iso + +#ifdef CONFIG_DEBUG_SLAB +# define EHCI_SLAB_FLAGS (SLAB_POISON) +#else +# define EHCI_SLAB_FLAGS 0 +#endif + +/* magic numbers that can affect system performance */ +#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define EHCI_TUNE_RL_HS 0 /* nak throttle; see 4.9 */ +#define EHCI_TUNE_RL_TT 0 +#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define EHCI_TUNE_MULT_TT 1 + +/* Initial IRQ latency: lower than default */ +static int log2_irq_thresh = 0; // 0 to 6 +MODULE_PARM (log2_irq_thresh, "i"); +MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); + +#define INTR_MASK (STS_IAA | STS_FATAL | STS_ERR | STS_INT) + +/*-------------------------------------------------------------------------*/ + +#include "ehci.h" +#include "ehci-dbg.c" + +/*-------------------------------------------------------------------------*/ + +/* + * hc states include: unknown, halted, ready, running + * transitional states are messy just now + * trying to avoid "running" unless urbs are active + * a "ready" hc can be finishing prefetched work + */ + +/* halt a non-running controller */ +static void ehci_reset (struct ehci_hcd *ehci) +{ + u32 command = readl (&ehci->regs->command); + + command |= CMD_RESET; + dbg_cmd (ehci, "reset", command); + writel (command, &ehci->regs->command); + while (readl (&ehci->regs->command) & CMD_RESET) + continue; + ehci->hcd.state = USB_STATE_HALT; +} + +/* idle the controller (from running) */ +static void ehci_ready (struct ehci_hcd *ehci) +{ + u32 command; + +#ifdef DEBUG + if (!HCD_IS_RUNNING (ehci->hcd.state)) + BUG (); +#endif + + while (!(readl (&ehci->regs->status) & (STS_ASS | STS_PSS))) + udelay (100); + command = readl (&ehci->regs->command); + command &= ~(CMD_ASE | CMD_IAAD | CMD_PSE); + writel (command, &ehci->regs->command); + + // hardware can take 16 microframes to turn off ... + ehci->hcd.state = USB_STATE_READY; +} + +/*-------------------------------------------------------------------------*/ + +#include "ehci-hub.c" +#include "ehci-mem.c" +#include "ehci-q.c" +#include "ehci-sched.c" + +/*-------------------------------------------------------------------------*/ + +static void ehci_tasklet (unsigned long param); + +/* called by khubd or root hub init threads */ + +static int ehci_start (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 temp; + struct usb_device *udev; + int retval; + u32 hcc_params; + u8 tempbyte; + + // FIXME: given EHCI 0.96 or later, and a controller with + // the USBLEGSUP/USBLEGCTLSTS extended capability, make sure + // the BIOS doesn't still own this controller. + + spin_lock_init (&ehci->lock); + + ehci->caps = (struct ehci_caps *) hcd->regs; + ehci->regs = (struct ehci_regs *) (hcd->regs + ehci->caps->length); + dbg_hcs_params (ehci, "ehci_start"); + dbg_hcc_params (ehci, "ehci_start"); + + /* + * hw default: 1K periodic list heads, one per frame. + * periodic_size can shrink by USBCMD update if hcc_params allows. + */ + ehci->periodic_size = DEFAULT_I_TDPS; + if ((retval = ehci_mem_init (ehci, EHCI_SLAB_FLAGS | SLAB_KERNEL)) < 0) + return retval; + hcc_params = readl (&ehci->caps->hcc_params); + + /* controllers may cache some of the periodic schedule ... */ + if (HCC_ISOC_CACHE (hcc_params)) // full frame cache + ehci->i_thresh = 8; + else // N microframes cached + ehci->i_thresh = 2 + HCC_ISOC_THRES (hcc_params); + + ehci->async = 0; + ehci->reclaim = 0; + ehci->next_frame = -1; + + /* controller state: unknown --> reset */ + + /* EHCI spec section 4.1 */ + ehci_reset (ehci); + writel (INTR_MASK, &ehci->regs->intr_enable); + writel (ehci->periodic_dma, &ehci->regs->frame_list); + + /* + * hcc_params controls whether ehci->regs->segment must (!!!) + * be used; it constrains QH/ITD/SITD and QTD locations. + * By default, pci_alloc_consistent() won't hand out addresses + * above 4GB (via pdev->dma_mask) so we know this value. + * + * NOTE: that pdev->dma_mask setting means that all DMA mappings + * for I/O buffers will have the same restriction, though it's + * neither necessary nor desirable in that case. + */ + if (HCC_64BIT_ADDR (hcc_params)) { + writel (0, &ehci->regs->segment); + info ("using segment 0 for 64bit DMA addresses ..."); + } + + /* clear interrupt enables, set irq latency */ + temp = readl (&ehci->regs->command) & 0xff; + if (log2_irq_thresh < 0 || log2_irq_thresh > 6) + log2_irq_thresh = 0; + temp |= 1 << (16 + log2_irq_thresh); + // keeping default periodic framelist size + temp &= ~(CMD_IAAD | CMD_ASE | CMD_PSE), + writel (temp, &ehci->regs->command); + dbg_cmd (ehci, "init", temp); + + /* set async sleep time = 10 us ... ? */ + + ehci->tasklet.func = ehci_tasklet; + ehci->tasklet.data = (unsigned long) ehci; + + /* wire up the root hub */ + hcd->bus->root_hub = udev = usb_alloc_dev (NULL, hcd->bus); + if (!udev) { +done2: + ehci_mem_cleanup (ehci); + return -ENOMEM; + } + + /* + * Start, enabling full USB 2.0 functionality ... usb 1.1 devices + * are explicitly handed to companion controller(s), so no TT is + * involved with the root hub. + */ + ehci->hcd.state = USB_STATE_READY; + writel (FLAG_CF, &ehci->regs->configured_flag); + readl (&ehci->regs->command); /* unblock posted write */ + + /* PCI Serial Bus Release Number is at 0x60 offset */ + pci_read_config_byte(hcd->pdev, 0x60, &tempbyte); + temp = readw (&ehci->caps->hci_version); + info ("USB %x.%x support enabled, EHCI rev %x.%2x", + ((tempbyte & 0xf0)>>4), + (tempbyte & 0x0f), + temp >> 8, + temp & 0xff); + + /* + * From here on, khubd concurrently accesses the root + * hub; drivers will be talking to enumerated devices. + * + * Before this point the HC was idle/ready. After, khubd + * and device drivers may start it running. + */ + usb_connect (udev); + udev->speed = USB_SPEED_HIGH; + if (usb_new_device (udev) != 0) { + if (hcd->state == USB_STATE_RUNNING) + ehci_ready (ehci); + while (readl (&ehci->regs->status) & (STS_ASS | STS_PSS)) + udelay (100); + ehci_reset (ehci); + // usb_disconnect (udev); + hcd->bus->root_hub = 0; + usb_free_dev (udev); + retval = -ENODEV; + goto done2; + } + + return 0; +} + +/* always called by thread; normally rmmod */ + +static void ehci_stop (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + + dbg ("%s: stop", hcd->bus_name); + + if (hcd->state == USB_STATE_RUNNING) + ehci_ready (ehci); + while (readl (&ehci->regs->status) & (STS_ASS | STS_PSS)) + udelay (100); + ehci_reset (ehci); + + // root hub is shut down separately (first, when possible) + scan_async (ehci); + if (ehci->next_frame != -1) + scan_periodic (ehci); + ehci_mem_cleanup (ehci); + + dbg_status (ehci, "ehci_stop completed", readl (&ehci->regs->status)); +} + +static int ehci_get_frame (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + return (readl (&ehci->regs->frame_index) >> 3) % ehci->periodic_size; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM + +/* suspend/resume, section 4.3 */ + +static int ehci_suspend (struct usb_hcd *hcd, u32 state) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 params; + int ports; + int i; + + dbg ("%s: suspend to %d", hcd->bus_name, state); + + params = readl (&ehci->caps->hcs_params); + ports = HCS_N_PORTS (params); + + // FIXME: This assumes what's probably a D3 level suspend... + + // FIXME: usb wakeup events on this bus should resume the machine. + // pci config register PORTWAKECAP controls which ports can do it; + // bios may have initted the register... + + /* suspend each port, then stop the hc */ + for (i = 0; i < ports; i++) { + int temp = readl (&ehci->regs->port_status [i]); + + if ((temp & PORT_PE) == 0 + || (temp & PORT_OWNER) != 0) + continue; +dbg ("%s: suspend port %d", hcd->bus_name, i); + temp |= PORT_SUSPEND; + writel (temp, &ehci->regs->port_status [i]); + } + + if (hcd->state == USB_STATE_RUNNING) + ehci_ready (ehci); + while (readl (&ehci->regs->status) & (STS_ASS | STS_PSS)) + udelay (100); + writel (readl (&ehci->regs->command) & ~CMD_RUN, &ehci->regs->command); + +// save pci FLADJ value + + /* who tells PCI to reduce power consumption? */ + + return 0; +} + +static int ehci_resume (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 params; + int ports; + int i; + + dbg ("%s: resume", hcd->bus_name); + + params = readl (&ehci->caps->hcs_params); + ports = HCS_N_PORTS (params); + + // FIXME: if controller didn't retain state, + // return and let generic code clean it up + // test configured_flag ? + + /* resume HC and each port */ +// restore pci FLADJ value + // khubd and drivers will set HC running, if needed; + hcd->state = USB_STATE_READY; + for (i = 0; i < ports; i++) { + int temp = readl (&ehci->regs->port_status [i]); + + if ((temp & PORT_PE) == 0 + || (temp & PORT_SUSPEND) != 0) + continue; +dbg ("%s: resume port %d", hcd->bus_name, i); + temp |= PORT_RESUME; + writel (temp, &ehci->regs->port_status [i]); + readl (&ehci->regs->command); /* unblock posted writes */ + + wait_ms (20); + temp &= ~PORT_RESUME; + writel (temp, &ehci->regs->port_status [i]); + } + readl (&ehci->regs->command); /* unblock posted writes */ + return 0; +} + +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * tasklet scheduled by some interrupts and other events + * calls driver completion functions ... but not in_irq() + */ +static void ehci_tasklet (unsigned long param) +{ + struct ehci_hcd *ehci = (struct ehci_hcd *) param; + + if (ehci->reclaim_ready) + end_unlink_async (ehci); + scan_async (ehci); + if (ehci->next_frame != -1) + scan_periodic (ehci); + + // FIXME: when nothing is connected to the root hub, + // turn off the RUN bit so the host can enter C3 "sleep" power + // saving mode; make root hub code scan memory less often. +} + +/*-------------------------------------------------------------------------*/ + +static void ehci_irq (struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 status = readl (&ehci->regs->status); + int bh = 0; + + /* clear (just) interrupts */ + status &= INTR_MASK; + writel (status, &ehci->regs->status); + readl (&ehci->regs->command); /* unblock posted write */ + + if (unlikely (hcd->state == USB_STATE_HALT)) /* irq sharing? */ + return; + +#ifdef EHCI_VERBOSE_DEBUG + /* unrequested/ignored: Port Change Detect, Frame List Rollover */ + if (status & INTR_MASK) + dbg_status (ehci, "irq", status); +#endif + + /* INT, ERR, and IAA interrupt rates can be throttled */ + + /* normal [4.15.1.2] or error [4.15.1.1] completion */ + if (likely ((status & (STS_INT|STS_ERR)) != 0)) + bh = 1; + + /* complete the unlinking of some qh [4.15.2.3] */ + if (status & STS_IAA) { + ehci->reclaim_ready = 1; + bh = 1; + } + + /* PCI errors [4.15.2.4] */ + if (unlikely ((status & STS_FATAL) != 0)) { + err ("%s: fatal error, state %x", hcd->bus_name, hcd->state); + ehci_reset (ehci); + // generic layer kills/unlinks all urbs + // then tasklet cleans up the rest + bh = 1; + } + + /* most work doesn't need to be in_irq() */ + if (likely (bh == 1)) + tasklet_schedule (&ehci->tasklet); +} + +/*-------------------------------------------------------------------------*/ + +/* + * non-error returns are a promise to giveback() the urb later + * we drop ownership so next owner (or urb unlink) can get it + * + * urb + dev is in hcd_dev.urb_list + * we're queueing TDs onto software and hardware lists + * + * hcd-specific init for hcpriv hasn't been done yet + * + * NOTE: EHCI queues control and bulk requests transparently, like OHCI. + */ +static int ehci_urb_enqueue ( + struct usb_hcd *hcd, + struct urb *urb, + int mem_flags +) { + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + struct list_head qtd_list; + + urb->transfer_flags &= ~EHCI_STATE_UNLINK; + INIT_LIST_HEAD (&qtd_list); + switch (usb_pipetype (urb->pipe)) { + + case PIPE_CONTROL: + case PIPE_BULK: + if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) + return -ENOMEM; + submit_async (ehci, urb, &qtd_list, mem_flags); + break; + + case PIPE_INTERRUPT: + if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return intr_submit (ehci, urb, &qtd_list, mem_flags); + + case PIPE_ISOCHRONOUS: +#ifdef have_iso + if (urb->dev->speed == USB_SPEED_HIGH) + return itd_submit (ehci, urb); + else + return sitd_submit (ehci, urb); +#else + // FIXME highspeed iso stuff is written but never run/tested. + // and the split iso support isn't even written yet. + dbg ("no iso support yet"); + return -ENOSYS; +#endif /* have_iso */ + + } + return 0; +} + +/* remove from hardware lists + * completions normally happen asynchronously + */ + +static int ehci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + struct ehci_qh *qh = (struct ehci_qh *) urb->hcpriv; + unsigned long flags; + + dbg ("%s urb_dequeue %p qh state %d", + hcd->bus_name, urb, qh->qh_state); + + switch (usb_pipetype (urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + spin_lock_irqsave (&ehci->lock, flags); + if (ehci->reclaim) { +dbg ("dq: reclaim busy, %s", RUN_CONTEXT); + if (in_interrupt ()) { + spin_unlock_irqrestore (&ehci->lock, flags); + return -EAGAIN; + } + while (qh->qh_state == QH_STATE_LINKED + && ehci->reclaim + && ehci->hcd.state != USB_STATE_HALT + ) { + spin_unlock_irqrestore (&ehci->lock, flags); +// yeech ... this could spin for up to two frames! +dbg ("wait for dequeue: state %d, reclaim %p, hcd state %d", + qh->qh_state, ehci->reclaim, ehci->hcd.state +); + udelay (100); + spin_lock_irqsave (&ehci->lock, flags); + } + } + if (qh->qh_state == QH_STATE_LINKED) + start_unlink_async (ehci, qh); + spin_unlock_irqrestore (&ehci->lock, flags); + return 0; + + case PIPE_INTERRUPT: + intr_deschedule (ehci, urb->start_frame, qh, urb->interval); + if (ehci->hcd.state == USB_STATE_HALT) + urb->status = -ESHUTDOWN; + qh_completions (ehci, &qh->qtd_list, 1); + return 0; + + case PIPE_ISOCHRONOUS: + // itd or sitd ... + + // wait till next completion, do it then. + // completion irqs can wait up to 128 msec, + urb->transfer_flags |= EHCI_STATE_UNLINK; + return 0; + } + return -EINVAL; +} + +/*-------------------------------------------------------------------------*/ + +// bulk qh holds the data toggle + +static void ehci_free_config (struct usb_hcd *hcd, struct usb_device *udev) +{ + struct hcd_dev *dev = (struct hcd_dev *)udev->hcpriv; + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + int i; + unsigned long flags; + + /* ASSERT: nobody can be submitting urbs for this any more */ + + dbg ("%s: free_config devnum %d", hcd->bus_name, udev->devnum); + + spin_lock_irqsave (&ehci->lock, flags); + for (i = 0; i < 32; i++) { + if (dev->ep [i]) { + struct ehci_qh *qh; + + // FIXME: this might be an itd/sitd too ... + // or an interrupt urb (not on async list) + // can use "union ehci_shadow" + + qh = (struct ehci_qh *) dev->ep [i]; + vdbg ("free_config, ep 0x%02x qh %p", i, qh); + if (!list_empty (&qh->qtd_list)) { + dbg ("ep 0x%02x qh %p not empty!", i, qh); + BUG (); + } + dev->ep [i] = 0; + + /* wait_ms() won't spin here -- we're a thread */ + while (qh->qh_state == QH_STATE_LINKED + && ehci->reclaim + && ehci->hcd.state != USB_STATE_HALT + ) { + spin_unlock_irqrestore (&ehci->lock, flags); + wait_ms (1); + spin_lock_irqsave (&ehci->lock, flags); + } + if (qh->qh_state == QH_STATE_LINKED) { + start_unlink_async (ehci, qh); + while (qh->qh_state != QH_STATE_IDLE) { + spin_unlock_irqrestore (&ehci->lock, + flags); + wait_ms (1); + spin_lock_irqsave (&ehci->lock, flags); + } + } + qh_unput (ehci, qh); + } + } + + spin_unlock_irqrestore (&ehci->lock, flags); +} + +/*-------------------------------------------------------------------------*/ + +static const char hcd_name [] = "ehci-hcd"; + +static const struct hc_driver ehci_driver = { + description: hcd_name, + + /* + * generic hardware linkage + */ + irq: ehci_irq, + flags: HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + */ + start: ehci_start, +#ifdef CONFIG_PM + suspend: ehci_suspend, + resume: ehci_resume, +#endif + stop: ehci_stop, + + /* + * memory lifecycle (except per-request) + */ + hcd_alloc: ehci_hcd_alloc, + hcd_free: ehci_hcd_free, + + /* + * managing i/o requests and associated device resources + */ + urb_enqueue: ehci_urb_enqueue, + urb_dequeue: ehci_urb_dequeue, + free_config: ehci_free_config, + + /* + * scheduling support + */ + get_frame_number: ehci_get_frame, + + /* + * root hub support + */ + hub_status_data: ehci_hub_status_data, + hub_control: ehci_hub_control, +}; + +/*-------------------------------------------------------------------------*/ + +/* EHCI spec says PCI is required. */ + +/* PCI driver selection metadata; PCI hotplugging uses this */ +static const struct pci_device_id __devinitdata pci_ids [] = { { + + /* handle any USB 2.0 EHCI controller */ + + class: ((PCI_CLASS_SERIAL_USB << 8) | 0x20), + class_mask: ~0, + driver_data: (unsigned long) &ehci_driver, + + /* no matter who makes it */ + vendor: PCI_ANY_ID, + device: PCI_ANY_ID, + subvendor: PCI_ANY_ID, + subdevice: PCI_ANY_ID, + +}, { /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE (pci, pci_ids); + +/* pci driver glue; this is a "new style" PCI driver module */ +static struct pci_driver ehci_pci_driver = { + name: (char *) hcd_name, + id_table: pci_ids, + + probe: usb_hcd_pci_probe, + remove: usb_hcd_pci_remove, + +#ifdef CONFIG_PM + suspend: usb_hcd_pci_suspend, + resume: usb_hcd_pci_resume, +#endif +}; + +#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC + +EXPORT_NO_SYMBOLS; +MODULE_DESCRIPTION (DRIVER_INFO); +MODULE_AUTHOR (DRIVER_AUTHOR); +MODULE_LICENSE ("GPL"); + +static int __init init (void) +{ + dbg (DRIVER_INFO); + dbg ("block sizes: qh %d qtd %d itd %d sitd %d", + sizeof (struct ehci_qh), sizeof (struct ehci_qtd), + sizeof (struct ehci_itd), sizeof (struct ehci_sitd)); + + return pci_module_init (&ehci_pci_driver); +} +module_init (init); + +static void __exit cleanup (void) +{ + pci_unregister_driver (&ehci_pci_driver); +} +module_exit (cleanup); diff -Nru a/drivers/usb/hcd/ehci-hub.c b/drivers/usb/hcd/ehci-hub.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ehci-hub.c Mon Feb 25 16:54:37 2002 @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2001 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Root Hub ... the nonsharable stuff + * + * Registers don't need cpu_to_le32, that happens transparently + */ + +/*-------------------------------------------------------------------------*/ + +static int check_reset_complete ( + struct ehci_hcd *ehci, + int index, + int port_status +) { + if (!(port_status & PORT_CONNECT)) { + ehci->reset_done [index] = 0; + return port_status; + } + + /* if reset finished and it's still not enabled -- handoff */ + if (!(port_status & PORT_PE)) { + dbg ("%s port %d full speed, give to companion, 0x%x", + ehci->hcd.bus_name, index + 1, port_status); + + // what happens if HCS_N_CC(params) == 0 ? + port_status |= PORT_OWNER; + writel (port_status, &ehci->regs->port_status [index]); + + } else + dbg ("%s port %d high speed", ehci->hcd.bus_name, index + 1); + + return port_status; +} + +/*-------------------------------------------------------------------------*/ + + +/* build "status change" packet (one or two bytes) from HC registers */ + +static int +ehci_hub_status_data (struct usb_hcd *hcd, char *buf) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 temp, status = 0; + int ports, i, retval = 1; + unsigned long flags; + + /* init status to no-changes */ + buf [0] = 0; + temp = readl (&ehci->caps->hcs_params); + ports = HCS_N_PORTS (temp); + if (ports > 7) { + buf [1] = 0; + retval++; + } + + /* no hub change reports (bit 0) for now (power, ...) */ + + /* port N changes (bit N)? */ + spin_lock_irqsave (&ehci->lock, flags); + for (i = 0; i < ports; i++) { + temp = readl (&ehci->regs->port_status [i]); + if (temp & PORT_OWNER) { + /* don't report this in GetPortStatus */ + if (temp & PORT_CSC) { + temp &= ~PORT_CSC; + writel (temp, &ehci->regs->port_status [i]); + } + continue; + } + if (!(temp & PORT_CONNECT)) + ehci->reset_done [i] = 0; + if ((temp & (PORT_CSC | PORT_PEC | PORT_OCC)) != 0) { + set_bit (i, buf); + status = STS_PCD; + } + } + spin_unlock_irqrestore (&ehci->lock, flags); + return status ? retval : 0; +} + +/*-------------------------------------------------------------------------*/ + +static void +ehci_hub_descriptor ( + struct ehci_hcd *ehci, + struct usb_hub_descriptor *desc +) { + u32 params = readl (&ehci->caps->hcs_params); + int ports = HCS_N_PORTS (params); + u16 temp; + + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = 0; /* FIXME: f(system power) */ + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset (&desc->bitmap [0], 0, temp); + memset (&desc->bitmap [temp], 0xff, temp); + + temp = 0x0008; /* per-port overcurrent reporting */ + if (HCS_PPC (params)) /* per-port power control */ + temp |= 0x0001; + if (HCS_INDICATOR (params)) /* per-port indicators (LEDs) */ + temp |= 0x0080; + desc->wHubCharacteristics = cpu_to_le16 (temp); +} + +/*-------------------------------------------------------------------------*/ + +static int ehci_hub_control ( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +) { + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 params = readl (&ehci->caps->hcs_params); + int ports = HCS_N_PORTS (params); + u32 temp; + unsigned long flags; + int retval = 0; + + /* + * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. + * HCS_INDICATOR may say we can change LEDs to off/amber/green. + * (track current state ourselves) ... blink for diagnostics, + * power, "this is the one", etc. EHCI spec supports this. + */ + + spin_lock_irqsave (&ehci->lock, flags); + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = readl (&ehci->regs->port_status [wIndex]); + if (temp & PORT_OWNER) + break; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + writel (temp & ~PORT_PE, + &ehci->regs->port_status [wIndex]); + break; + case USB_PORT_FEAT_C_ENABLE: + writel (temp | PORT_PEC, + &ehci->regs->port_status [wIndex]); + break; + case USB_PORT_FEAT_SUSPEND: + case USB_PORT_FEAT_C_SUSPEND: + /* ? */ + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC (params)) + writel (temp & ~PORT_POWER, + &ehci->regs->port_status [wIndex]); + break; + case USB_PORT_FEAT_C_CONNECTION: + writel (temp | PORT_CSC, + &ehci->regs->port_status [wIndex]); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + writel (temp | PORT_OCC, + &ehci->regs->port_status [wIndex]); + break; + case USB_PORT_FEAT_C_RESET: + /* GetPortStatus clears reset */ + break; + default: + goto error; + } + readl (&ehci->regs->command); /* unblock posted write */ + break; + case GetHubDescriptor: + ehci_hub_descriptor (ehci, (struct usb_hub_descriptor *) + buf); + break; + case GetHubStatus: + /* no hub-wide feature/status flags */ + memset (buf, 0, 4); + //cpu_to_le32s ((u32 *) buf); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + memset (buf, 0, 4); + temp = readl (&ehci->regs->port_status [wIndex]); + + // wPortChange bits + if (temp & PORT_CSC) + set_bit (USB_PORT_FEAT_C_CONNECTION, buf); + if (temp & PORT_PEC) + set_bit (USB_PORT_FEAT_C_ENABLE, buf); + // USB_PORT_FEAT_C_SUSPEND + if (temp & PORT_OCC) + set_bit (USB_PORT_FEAT_C_OVER_CURRENT, buf); + + /* whoever resets must GetPortStatus to complete it!! */ + if ((temp & PORT_RESET) + && jiffies > ehci->reset_done [wIndex]) { + set_bit (USB_PORT_FEAT_C_RESET, buf); + + /* force reset to complete */ + writel (temp & ~PORT_RESET, + &ehci->regs->port_status [wIndex]); + do { + temp = readl ( + &ehci->regs->port_status [wIndex]); + udelay (10); + } while (temp & PORT_RESET); + + /* see what we found out */ + temp = check_reset_complete (ehci, wIndex, temp); + } + + // don't show wPortStatus if it's owned by a companion hc + if (!(temp & PORT_OWNER)) { + if (temp & PORT_CONNECT) { + set_bit (USB_PORT_FEAT_CONNECTION, buf); + set_bit (USB_PORT_FEAT_HIGHSPEED, buf); + } + if (temp & PORT_PE) + set_bit (USB_PORT_FEAT_ENABLE, buf); + if (temp & PORT_SUSPEND) + set_bit (USB_PORT_FEAT_SUSPEND, buf); + if (temp & PORT_OC) + set_bit (USB_PORT_FEAT_OVER_CURRENT, buf); + if (temp & PORT_RESET) + set_bit (USB_PORT_FEAT_RESET, buf); + if (temp & PORT_POWER) + set_bit (USB_PORT_FEAT_POWER, buf); + } + +#ifndef EHCI_VERBOSE_DEBUG + if (*(u16*)(buf+2)) /* only if wPortChange is interesting */ +#endif + dbg_port (hcd, "GetStatus", wIndex + 1, temp); + cpu_to_le32s ((u32 *) buf); + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case SetPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = readl (&ehci->regs->port_status [wIndex]); + if (temp & PORT_OWNER) + break; + + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + writel (temp | PORT_SUSPEND, + &ehci->regs->port_status [wIndex]); + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC (params)) + writel (temp | PORT_POWER, + &ehci->regs->port_status [wIndex]); + break; + case USB_PORT_FEAT_RESET: + /* line status bits may report this as low speed */ + if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT + && PORT_USB11 (temp)) { + dbg ("%s port %d low speed, give to companion", + hcd->bus_name, wIndex + 1); + temp |= PORT_OWNER; + } else { + vdbg ("%s port %d reset", + hcd->bus_name, wIndex + 1); + temp |= PORT_RESET; + temp &= ~PORT_PE; + + /* + * caller must wait, then call GetPortStatus + * usb 2.0 spec says 50 ms resets on root + */ + ehci->reset_done [wIndex] = jiffies + + ((50 /* msec */ * HZ) / 1000); + } + writel (temp, &ehci->regs->port_status [wIndex]); + break; + default: + goto error; + } + readl (&ehci->regs->command); /* unblock posted writes */ + break; + + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore (&ehci->lock, flags); + return retval; +} diff -Nru a/drivers/usb/hcd/ehci-mem.c b/drivers/usb/hcd/ehci-mem.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ehci-mem.c Mon Feb 25 16:54:37 2002 @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2001 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* + * There's basically three types of memory: + * - data used only by the HCD ... kmalloc is fine + * - async and periodic schedules, shared by HC and HCD ... these + * need to use pci_pool or pci_alloc_consistent + * - driver buffers, read/written by HC ... single shot DMA mapped + * + * There's also PCI "register" data, which is memory mapped. + * No memory seen by this driver is pagable. + */ + +/*-------------------------------------------------------------------------*/ +/* + * Allocator / cleanup for the per device structure + * Called by hcd init / removal code + */ +static struct usb_hcd *ehci_hcd_alloc (void) +{ + struct ehci_hcd *ehci; + + ehci = (struct ehci_hcd *) + kmalloc (sizeof (struct ehci_hcd), GFP_KERNEL); + if (ehci != 0) { + memset (ehci, 0, sizeof (struct ehci_hcd)); + return &ehci->hcd; + } + return 0; +} + +static void ehci_hcd_free (struct usb_hcd *hcd) +{ + kfree (hcd_to_ehci (hcd)); +} + +/*-------------------------------------------------------------------------*/ + +/* Allocate the key transfer structures from the previously allocated pool */ + +static struct ehci_qtd *ehci_qtd_alloc (struct ehci_hcd *ehci, int flags) +{ + struct ehci_qtd *qtd; + dma_addr_t dma; + + qtd = pci_pool_alloc (ehci->qtd_pool, flags, &dma); + if (qtd != 0) { + memset (qtd, 0, sizeof *qtd); + qtd->qtd_dma = dma; + qtd->hw_next = EHCI_LIST_END; + qtd->hw_alt_next = EHCI_LIST_END; + INIT_LIST_HEAD (&qtd->qtd_list); + } + return qtd; +} + +static inline void ehci_qtd_free (struct ehci_hcd *ehci, struct ehci_qtd *qtd) +{ + pci_pool_free (ehci->qtd_pool, qtd, qtd->qtd_dma); +} + + +static struct ehci_qh *ehci_qh_alloc (struct ehci_hcd *ehci, int flags) +{ + struct ehci_qh *qh; + dma_addr_t dma; + + qh = (struct ehci_qh *) + pci_pool_alloc (ehci->qh_pool, flags, &dma); + if (qh) { + memset (qh, 0, sizeof *qh); + atomic_set (&qh->refcount, 1); + qh->qh_dma = dma; + // INIT_LIST_HEAD (&qh->qh_list); + INIT_LIST_HEAD (&qh->qtd_list); + } + return qh; +} + +/* to share a qh (cpu threads, or hc) */ +static inline struct ehci_qh *qh_put (/* ehci, */ struct ehci_qh *qh) +{ + // dbg ("put %p (%d++)", qh, qh->refcount.counter); + atomic_inc (&qh->refcount); + return qh; +} + +static void qh_unput (struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + // dbg ("unput %p (--%d)", qh, qh->refcount.counter); + if (!atomic_dec_and_test (&qh->refcount)) + return; + /* clean qtds first, and know this is not linked */ + if (!list_empty (&qh->qtd_list) || qh->qh_next.ptr) { + dbg ("unused qh not empty!"); + BUG (); + } + pci_pool_free (ehci->qh_pool, qh, qh->qh_dma); +} + +/*-------------------------------------------------------------------------*/ + +/* The queue heads and transfer descriptors are managed from pools tied + * to each of the "per device" structures. + * This is the initialisation and cleanup code. + */ + +static void ehci_mem_cleanup (struct ehci_hcd *ehci) +{ + /* PCI consistent memory and pools */ + if (ehci->qtd_pool) + pci_pool_destroy (ehci->qtd_pool); + ehci->qtd_pool = 0; + + if (ehci->qh_pool) { + pci_pool_destroy (ehci->qh_pool); + ehci->qh_pool = 0; + } + + if (ehci->itd_pool) + pci_pool_destroy (ehci->itd_pool); + ehci->itd_pool = 0; + + if (ehci->sitd_pool) + pci_pool_destroy (ehci->sitd_pool); + ehci->sitd_pool = 0; + + if (ehci->periodic) + pci_free_consistent (ehci->hcd.pdev, + ehci->periodic_size * sizeof (u32), + ehci->periodic, ehci->periodic_dma); + ehci->periodic = 0; + + /* shadow periodic table */ + if (ehci->pshadow) + kfree (ehci->pshadow); + ehci->pshadow = 0; +} + +/* remember to add cleanup code (above) if you add anything here */ +static int ehci_mem_init (struct ehci_hcd *ehci, int flags) +{ + int i; + + /* QTDs for control/bulk/intr transfers */ + ehci->qtd_pool = pci_pool_create ("ehci_qtd", ehci->hcd.pdev, + sizeof (struct ehci_qtd), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */, + flags); + if (!ehci->qtd_pool) { + dbg ("no qtd pool"); + ehci_mem_cleanup (ehci); + return -ENOMEM; + } + + /* QH for control/bulk/intr transfers */ + ehci->qh_pool = pci_pool_create ("ehci_qh", ehci->hcd.pdev, + sizeof (struct ehci_qh), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */, + flags); + if (!ehci->qh_pool) { + dbg ("no qh pool"); + ehci_mem_cleanup (ehci); + return -ENOMEM; + } + + /* ITD for high speed ISO transfers */ + ehci->itd_pool = pci_pool_create ("ehci_itd", ehci->hcd.pdev, + sizeof (struct ehci_itd), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */, + flags); + if (!ehci->itd_pool) { + dbg ("no itd pool"); + ehci_mem_cleanup (ehci); + return -ENOMEM; + } + + /* SITD for full/low speed split ISO transfers */ + ehci->sitd_pool = pci_pool_create ("ehci_sitd", ehci->hcd.pdev, + sizeof (struct ehci_sitd), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */, + flags); + if (!ehci->sitd_pool) { + dbg ("no sitd pool"); + ehci_mem_cleanup (ehci); + return -ENOMEM; + } + + /* Hardware periodic table */ + ehci->periodic = (u32 *) + pci_alloc_consistent (ehci->hcd.pdev, + ehci->periodic_size * sizeof (u32), + &ehci->periodic_dma); + if (ehci->periodic == 0) { + dbg ("no hw periodic table"); + ehci_mem_cleanup (ehci); + return -ENOMEM; + } + for (i = 0; i < ehci->periodic_size; i++) + ehci->periodic [i] = EHCI_LIST_END; + + /* software shadow of hardware table */ + ehci->pshadow = kmalloc (ehci->periodic_size * sizeof (void *), + flags & ~EHCI_SLAB_FLAGS); + if (ehci->pshadow == 0) { + dbg ("no shadow periodic table"); + ehci_mem_cleanup (ehci); + return -ENOMEM; + } + memset (ehci->pshadow, 0, ehci->periodic_size * sizeof (void *)); + + return 0; +} diff -Nru a/drivers/usb/hcd/ehci-q.c b/drivers/usb/hcd/ehci-q.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ehci-q.c Mon Feb 25 16:54:37 2002 @@ -0,0 +1,969 @@ +/* + * Copyright (c) 2001 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI hardware queue manipulation + * + * Control, bulk, and interrupt traffic all use "qh" lists. They list "qtd" + * entries describing USB transactions, max 16-20kB/entry (with 4kB-aligned + * buffers needed for the larger number). We use one QH per endpoint, queue + * multiple (bulk or control) urbs per endpoint. URBs may need several qtds. + * A scheduled interrupt qh always has one qtd, one urb. + * + * ISO traffic uses "ISO TD" (itd, and sitd) records, and (along with + * interrupts) needs careful scheduling. Performance improvements can be + * an ongoing challenge. + * + * USB 1.1 devices are handled (a) by "companion" OHCI or UHCI root hubs, + * or otherwise through transaction translators (TTs) in USB 2.0 hubs using + * (b) special fields in qh entries or (c) split iso entries. TTs will + * buffer low/full speed data so the host collects it at high speed. + */ + +/*-------------------------------------------------------------------------*/ + +/* fill a qtd, returning how much of the buffer we were able to queue up */ + +static int +qtd_fill (struct ehci_qtd *qtd, dma_addr_t buf, size_t len, int token) +{ + int i, count; + + /* one buffer entry per 4K ... first might be short or unaligned */ + qtd->hw_buf [0] = cpu_to_le32 (buf); + count = 0x1000 - (buf & 0x0fff); /* rest of that page */ + if (likely (len < count)) /* ... iff needed */ + count = len; + else { + buf += 0x1000; + buf &= ~0x0fff; + + /* per-qtd limit: from 16K to 20K (best alignment) */ + for (i = 1; count < len && i < 5; i++) { + u64 addr = buf; + qtd->hw_buf [i] = cpu_to_le32 ((u32)addr); + qtd->hw_buf_hi [i] = cpu_to_le32 ((u32)(addr >> 32)); + buf += 0x1000; + if ((count + 0x1000) < len) + count += 0x1000; + else + count = len; + } + } + qtd->hw_token = cpu_to_le32 ((count << 16) | token); + qtd->length = count; + +#if 0 + vdbg (" qtd_fill %p, token %8x bytes %d dma %x", + qtd, le32_to_cpu (qtd->hw_token), count, qtd->hw_buf [0]); +#endif + + return count; +} + +/*-------------------------------------------------------------------------*/ + +/* update halted (but potentially linked) qh */ + +static inline void qh_update (struct ehci_qh *qh, struct ehci_qtd *qtd) +{ + qh->hw_current = 0; + qh->hw_qtd_next = QTD_NEXT (qtd->qtd_dma); + qh->hw_alt_next = EHCI_LIST_END; + + /* HC must see latest qtd and qh data before we clear ACTIVE+HALT */ + qh->hw_token &= __constant_cpu_to_le32 (QTD_TOGGLE | QTD_STS_PING); +} + +/*-------------------------------------------------------------------------*/ + +static inline void qtd_copy_status (struct urb *urb, size_t length, u32 token) +{ + /* count IN/OUT bytes, not SETUP (even short packets) */ + if (likely (QTD_PID (token) != 2)) + urb->actual_length += length - QTD_LENGTH (token); + + /* don't modify error codes */ + if (unlikely (urb->status == -EINPROGRESS && (token & QTD_STS_HALT))) { + if (token & QTD_STS_BABBLE) { + urb->status = -EOVERFLOW; + } else if (!QTD_CERR (token)) { + if (token & QTD_STS_DBE) + urb->status = (QTD_PID (token) == 1) /* IN ? */ + ? -ENOSR /* hc couldn't read data */ + : -ECOMM; /* hc couldn't write data */ + else if (token & QTD_STS_MMF) /* missed tt uframe */ + urb->status = -EPROTO; + else if (token & QTD_STS_XACT) { + if (QTD_LENGTH (token)) + urb->status = -EPIPE; + else { + dbg ("3strikes"); + urb->status = -EPROTO; + } + } else /* presumably a stall */ + urb->status = -EPIPE; + + /* CERR nonzero + data left + halt --> stall */ + } else if (QTD_LENGTH (token)) + urb->status = -EPIPE; + else /* unknown */ + urb->status = -EPROTO; + dbg ("ep %d-%s qtd token %08x --> status %d", + /* devpath */ + usb_pipeendpoint (urb->pipe), + usb_pipein (urb->pipe) ? "in" : "out", + token, urb->status); + + /* stall indicates some recovery action is needed */ + if (urb->status == -EPIPE) { + int pipe = urb->pipe; + + if (!usb_pipecontrol (pipe)) + usb_endpoint_halt (urb->dev, + usb_pipeendpoint (pipe), + usb_pipeout (pipe)); + if (urb->dev->tt && !usb_pipeint (pipe)) { +err ("must CLEAR_TT_BUFFER, hub port %d%s addr %d ep %d", + urb->dev->ttport, /* devpath */ + urb->dev->tt->multi ? "" : " (all-ports TT)", + urb->dev->devnum, usb_pipeendpoint (urb->pipe)); + // FIXME something (khubd?) should make the hub + // CLEAR_TT_BUFFER ASAP, it's blocking other + // fs/ls requests... hub_tt_clear_buffer() ? + } + } + } +} + +static void ehci_urb_complete ( + struct ehci_hcd *ehci, + dma_addr_t addr, + struct urb *urb +) { + if (urb->transfer_buffer_length && usb_pipein (urb->pipe)) + pci_dma_sync_single (ehci->hcd.pdev, addr, + urb->transfer_buffer_length, + PCI_DMA_FROMDEVICE); + + /* cleanse status if we saw no error */ + if (likely (urb->status == -EINPROGRESS)) { + if (urb->actual_length != urb->transfer_buffer_length + && (urb->transfer_flags & USB_DISABLE_SPD)) + urb->status = -EREMOTEIO; + else + urb->status = 0; + } + + /* only report unlinks once */ + if (likely (urb->status != -ENOENT && urb->status != -ENOTCONN)) + urb->complete (urb); +} + +/* urb->lock ignored from here on (hcd is done with urb) */ + +static void ehci_urb_done ( + struct ehci_hcd *ehci, + dma_addr_t addr, + struct urb *urb +) { + if (urb->transfer_buffer_length) + pci_unmap_single (ehci->hcd.pdev, + addr, + urb->transfer_buffer_length, + usb_pipein (urb->pipe) + ? PCI_DMA_FROMDEVICE + : PCI_DMA_TODEVICE); + if (likely (urb->hcpriv != 0)) { + qh_unput (ehci, (struct ehci_qh *) urb->hcpriv); + urb->hcpriv = 0; + } + + if (likely (urb->status == -EINPROGRESS)) { + if (urb->actual_length != urb->transfer_buffer_length + && (urb->transfer_flags & USB_DISABLE_SPD)) + urb->status = -EREMOTEIO; + else + urb->status = 0; + } + + /* hand off urb ownership */ + usb_hcd_giveback_urb (&ehci->hcd, urb); +} + + +/* + * Process completed qtds for a qh, issuing completions if needed. + * When freeing: frees qtds, unmaps buf, returns URB to driver. + * When not freeing (queued periodic qh): retain qtds, mapping, and urb. + * Races up to qh->hw_current; returns number of urb completions. + */ +static int +qh_completions ( + struct ehci_hcd *ehci, + struct list_head *qtd_list, + int freeing +) { + struct ehci_qtd *qtd, *last; + struct list_head *next; + struct ehci_qh *qh = 0; + int unlink = 0, halted = 0; + unsigned long flags; + int retval = 0; + + spin_lock_irqsave (&ehci->lock, flags); + if (unlikely (list_empty (qtd_list))) { + spin_unlock_irqrestore (&ehci->lock, flags); + return retval; + } + + /* scan QTDs till end of list, or we reach an active one */ + for (qtd = list_entry (qtd_list->next, struct ehci_qtd, qtd_list), + last = 0, next = 0; + next != qtd_list; + last = qtd, qtd = list_entry (next, + struct ehci_qtd, qtd_list)) { + struct urb *urb = qtd->urb; + u32 token = 0; + + /* qh is non-null iff these qtds were queued to the HC */ + qh = (struct ehci_qh *) urb->hcpriv; + + /* clean up any state from previous QTD ...*/ + if (last) { + if (likely (last->urb != urb)) { + /* complete() can reenter this HCD */ + spin_unlock_irqrestore (&ehci->lock, flags); + if (likely (freeing != 0)) + ehci_urb_done (ehci, last->buf_dma, + last->urb); + else + ehci_urb_complete (ehci, last->buf_dma, + last->urb); + spin_lock_irqsave (&ehci->lock, flags); + retval++; + } + + /* qh overlays can have HC's old cached copies of + * next qtd ptrs, if an URB was queued afterwards. + */ + if (qh && cpu_to_le32 (last->qtd_dma) == qh->hw_current + && last->hw_next != qh->hw_qtd_next) { + qh->hw_alt_next = last->hw_alt_next; + qh->hw_qtd_next = last->hw_next; + } + + if (likely (freeing != 0)) + ehci_qtd_free (ehci, last); + last = 0; + } + next = qtd->qtd_list.next; + + /* if these qtds were queued to the HC, some may be active. + * else we're cleaning up after a failed URB submission. + */ + if (likely (qh != 0)) { + int qh_halted; + + qh_halted = __constant_cpu_to_le32 (QTD_STS_HALT) + & qh->hw_token; + token = le32_to_cpu (qtd->hw_token); + halted = halted + || qh_halted + || (ehci->hcd.state == USB_STATE_HALT) + || (qh->qh_state == QH_STATE_IDLE); + + /* QH halts only because of fault or unlink; in both + * cases, queued URBs get unlinked. But for unlink, + * URBs at the head of the queue can stay linked. + */ + if (unlikely (halted != 0)) { + + /* unlink everything because of HC shutdown? */ + if (ehci->hcd.state == USB_STATE_HALT) { + freeing = unlink = 1; + urb->status = -ESHUTDOWN; + + /* explicit unlink, starting here? */ + } else if (qh->qh_state == QH_STATE_IDLE + && (urb->status == -ECONNRESET + || urb->status == -ENOENT)) { + freeing = unlink = 1; + + /* unlink everything because of error? */ + } else if (qh_halted + && !(token & QTD_STS_HALT)) { + freeing = unlink = 1; + if (urb->status == -EINPROGRESS) + urb->status = -ECONNRESET; + + /* unlink the rest? */ + } else if (unlink) { + urb->status = -ECONNRESET; + + /* QH halted to unlink urbs after this? */ + } else if ((token & QTD_STS_ACTIVE) != 0) { + qtd = 0; + continue; + } + + /* Else QH is active, so we must not modify QTDs + * that HC may be working on. Break from loop. + */ + } else if (unlikely ((token & QTD_STS_ACTIVE) != 0)) { + next = qtd_list; + qtd = 0; + continue; + } + + spin_lock (&urb->lock); + qtd_copy_status (urb, qtd->length, token); + spin_unlock (&urb->lock); + } + + /* + * NOTE: this won't work right with interrupt urbs that + * need multiple qtds ... only the first scan of qh->qtd_list + * starts at the right qtd, yet multiple scans could happen + * for transfers that are scheduled across multiple uframes. + * (Such schedules are not currently allowed!) + */ + if (likely (freeing != 0)) + list_del (&qtd->qtd_list); + else { + /* restore everything the HC could change + * from an interrupt QTD + */ + qtd->hw_token = (qtd->hw_token + & ~__constant_cpu_to_le32 (0x8300)) + | cpu_to_le32 (qtd->length << 16) + | __constant_cpu_to_le32 (QTD_IOC + | (EHCI_TUNE_CERR << 10) + | QTD_STS_ACTIVE); + qtd->hw_buf [0] &= ~__constant_cpu_to_le32 (0x0fff); + + /* this offset, and the length above, + * are likely wrong on QTDs #2..N + */ + qtd->hw_buf [0] |= cpu_to_le32 (0x0fff & qtd->buf_dma); + } + +#if 0 + if (urb->status == -EINPROGRESS) + vdbg (" qtd %p ok, urb %p, token %8x, len %d", + qtd, urb, token, urb->actual_length); + else + vdbg ("urb %p status %d, qtd %p, token %8x, len %d", + urb, urb->status, qtd, token, + urb->actual_length); +#endif + + /* SETUP for control urb? */ + if (unlikely (QTD_PID (token) == 2)) + pci_unmap_single (ehci->hcd.pdev, + qtd->buf_dma, sizeof (devrequest), + PCI_DMA_TODEVICE); + } + + /* patch up list head? */ + if (unlikely (halted && qh && !list_empty (qtd_list))) { + qh_update (qh, list_entry (qtd_list->next, + struct ehci_qtd, qtd_list)); + } + spin_unlock_irqrestore (&ehci->lock, flags); + + /* last urb's completion might still need calling */ + if (likely (last != 0)) { + if (likely (freeing != 0)) { + ehci_urb_done (ehci, last->buf_dma, last->urb); + ehci_qtd_free (ehci, last); + } else + ehci_urb_complete (ehci, last->buf_dma, last->urb); + retval++; + } + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* + * create a list of filled qtds for this URB; won't link into qh. + */ +static struct list_head * +qh_urb_transaction ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *head, + int flags +) { + struct ehci_qtd *qtd, *qtd_prev; + dma_addr_t buf, map_buf; + int len, maxpacket; + u32 token; + + /* + * URBs map to sequences of QTDs: one logical transaction + */ + qtd = ehci_qtd_alloc (ehci, flags); + if (unlikely (!qtd)) + return 0; + qtd_prev = 0; + list_add_tail (&qtd->qtd_list, head); + qtd->urb = urb; + + token = QTD_STS_ACTIVE; + token |= (EHCI_TUNE_CERR << 10); + /* for split transactions, SplitXState initialized to zero */ + + if (usb_pipecontrol (urb->pipe)) { + /* control request data is passed in the "setup" pid */ + + /* NOTE: this isn't smart about 64bit DMA, since it uses the + * default (32bit) mask rather than using the whole address + * space. we could set pdev->dma_mask to all-ones while + * getting this mapping, locking it and restoring before + * allocating qtd/qh/... or maybe only do that for the main + * data phase (below). + */ + qtd->buf_dma = pci_map_single ( + ehci->hcd.pdev, + urb->setup_packet, + sizeof (devrequest), + PCI_DMA_TODEVICE); + if (unlikely (!qtd->buf_dma)) + goto cleanup; + + /* SETUP pid */ + qtd_fill (qtd, qtd->buf_dma, sizeof (devrequest), + token | (2 /* "setup" */ << 8)); + + /* ... and always at least one more pid */ + token ^= QTD_TOGGLE; + qtd_prev = qtd; + qtd = ehci_qtd_alloc (ehci, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma); + list_add_tail (&qtd->qtd_list, head); + } + + /* + * data transfer stage: buffer setup + */ + len = urb->transfer_buffer_length; + if (likely (len > 0)) { + /* NOTE: sub-optimal mapping with 64bit DMA (see above) */ + buf = map_buf = pci_map_single (ehci->hcd.pdev, + urb->transfer_buffer, len, + usb_pipein (urb->pipe) + ? PCI_DMA_FROMDEVICE + : PCI_DMA_TODEVICE); + if (unlikely (!buf)) + goto cleanup; + } else + buf = map_buf = 0; + + if (!buf || usb_pipein (urb->pipe)) + token |= (1 /* "in" */ << 8); + /* else it's already initted to "out" pid (0 << 8) */ + + maxpacket = usb_maxpacket (urb->dev, urb->pipe, + usb_pipeout (urb->pipe)); + + /* + * buffer gets wrapped in one or more qtds; + * last one may be "short" (including zero len) + * and may serve as a control status ack + */ + for (;;) { + int this_qtd_len; + + qtd->urb = urb; + qtd->buf_dma = map_buf; + this_qtd_len = qtd_fill (qtd, buf, len, token); + len -= this_qtd_len; + buf += this_qtd_len; + + /* qh makes control packets use qtd toggle; maybe switch it */ + if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0) + token ^= QTD_TOGGLE; + + if (likely (len <= 0)) + break; + + qtd_prev = qtd; + qtd = ehci_qtd_alloc (ehci, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma); + list_add_tail (&qtd->qtd_list, head); + } + + /* + * control requests may need a terminating data "status" ack; + * bulk ones may need a terminating short packet (zero length). + */ + if (likely (buf != 0)) { + int one_more = 0; + + if (usb_pipecontrol (urb->pipe)) { + one_more = 1; + token ^= 0x0100; /* "in" <--> "out" */ + token |= QTD_TOGGLE; /* force DATA1 */ + } else if (usb_pipebulk (urb->pipe) + && (urb->transfer_flags & USB_ZERO_PACKET) + && !(urb->transfer_buffer_length % maxpacket)) { + one_more = 1; + } + if (one_more) { + qtd_prev = qtd; + qtd = ehci_qtd_alloc (ehci, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma); + list_add_tail (&qtd->qtd_list, head); + + /* never any data in such packets */ + qtd_fill (qtd, 0, 0, token); + } + } + + /* by default, enable interrupt on urb completion */ + if (likely (!(urb->transfer_flags & URB_NO_INTERRUPT))) + qtd->hw_token |= __constant_cpu_to_le32 (QTD_IOC); + return head; + +cleanup: + urb->status = -ENOMEM; + qh_completions (ehci, head, 1); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* + * Hardware maintains data toggle (like OHCI) ... here we (re)initialize + * the hardware data toggle in the QH, and set the pseudo-toggle in udev + * so we can see if usb_clear_halt() was called. NOP for control, since + * we set up qh->hw_info1 to always use the QTD toggle bits. + */ +static inline void +clear_toggle (struct usb_device *udev, int ep, int is_out, struct ehci_qh *qh) +{ + vdbg ("clear toggle, dev %d ep 0x%x-%s", + udev->devnum, ep, is_out ? "out" : "in"); + qh->hw_token &= ~__constant_cpu_to_le32 (QTD_TOGGLE); + usb_settoggle (udev, ep, is_out, 1); +} + +// Would be best to create all qh's from config descriptors, +// when each interface/altsetting is established. Unlink +// any previous qh and cancel its urbs first; endpoints are +// implicitly reset then (data toggle too). +// That'd mean updating how usbcore talks to HCDs. (2.5?) + + +/* + * Each QH holds a qtd list; a QH is used for everything except iso. + * + * For interrupt urbs, the scheduler must set the microframe scheduling + * mask(s) each time the QH gets scheduled. For highspeed, that's + * just one microframe in the s-mask. For split interrupt transactions + * there are additional complications: c-mask, maybe FSTNs. + */ +static struct ehci_qh * +ehci_qh_make ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *qtd_list, + int flags +) { + struct ehci_qh *qh = ehci_qh_alloc (ehci, flags); + u32 info1 = 0, info2 = 0; + + if (!qh) + return qh; + + /* + * init endpoint/device data for this QH + */ + info1 |= usb_pipeendpoint (urb->pipe) << 8; + info1 |= usb_pipedevice (urb->pipe) << 0; + + /* using TT? */ + switch (urb->dev->speed) { + case USB_SPEED_LOW: + info1 |= (1 << 12); /* EPS "low" */ + /* FALL THROUGH */ + + case USB_SPEED_FULL: + /* EPS 0 means "full" */ + info1 |= (EHCI_TUNE_RL_TT << 28); + if (usb_pipecontrol (urb->pipe)) { + info1 |= (1 << 27); /* for TT */ + info1 |= 1 << 14; /* toggle from qtd */ + } + info1 |= usb_maxpacket (urb->dev, urb->pipe, + usb_pipeout (urb->pipe)) << 16; + + info2 |= (EHCI_TUNE_MULT_TT << 30); + info2 |= urb->dev->ttport << 23; + info2 |= urb->dev->tt->hub->devnum << 16; + + /* NOTE: if (usb_pipeint (urb->pipe)) { scheduler sets c-mask } + * ... and a 0.96 scheduler might use FSTN nodes too + */ + break; + + case USB_SPEED_HIGH: /* no TT involved */ + info1 |= (2 << 12); /* EPS "high" */ + info1 |= (EHCI_TUNE_RL_HS << 28); + if (usb_pipecontrol (urb->pipe)) { + info1 |= 64 << 16; /* usb2 fixed maxpacket */ + info1 |= 1 << 14; /* toggle from qtd */ + } else if (usb_pipebulk (urb->pipe)) { + info1 |= 512 << 16; /* usb2 fixed maxpacket */ + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else + info1 |= usb_maxpacket (urb->dev, urb->pipe, + usb_pipeout (urb->pipe)) << 16; + break; + default: +#ifdef DEBUG + BUG (); +#endif + } + + /* NOTE: if (usb_pipeint (urb->pipe)) { scheduler sets s-mask } */ + + qh->qh_state = QH_STATE_IDLE; + qh->hw_info1 = cpu_to_le32 (info1); + qh->hw_info2 = cpu_to_le32 (info2); + + /* initialize sw and hw queues with these qtds */ + list_splice (qtd_list, &qh->qtd_list); + qh_update (qh, list_entry (qtd_list->next, struct ehci_qtd, qtd_list)); + + /* initialize data toggle state */ + if (!usb_pipecontrol (urb->pipe)) + clear_toggle (urb->dev, + usb_pipeendpoint (urb->pipe), + usb_pipeout (urb->pipe), + qh); + + return qh; +} + +/*-------------------------------------------------------------------------*/ + +/* move qh (and its qtds) onto async queue; maybe enable queue. */ + +static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + u32 dma = QH_NEXT (qh->qh_dma); + struct ehci_qh *q; + + if (unlikely (!(q = ehci->async))) { + u32 cmd = readl (&ehci->regs->command); + + /* in case a clear of CMD_ASE didn't take yet */ + while (readl (&ehci->regs->status) & STS_ASS) + udelay (100); + + qh->hw_info1 |= __constant_cpu_to_le32 (QH_HEAD); /* [4.8] */ + qh->qh_next.qh = qh; + qh->hw_next = dma; + ehci->async = qh; + writel ((u32)qh->qh_dma, &ehci->regs->async_next); + cmd |= CMD_ASE | CMD_RUN; + writel (cmd, &ehci->regs->command); + ehci->hcd.state = USB_STATE_RUNNING; + /* posted write need not be known to HC yet ... */ + } else { + /* splice right after "start" of ring */ + qh->hw_info1 &= ~__constant_cpu_to_le32 (QH_HEAD); /* [4.8] */ + qh->qh_next = q->qh_next; + qh->hw_next = q->hw_next; + q->qh_next.qh = qh; + q->hw_next = dma; + } + qh->qh_state = QH_STATE_LINKED; + /* qtd completions reported later by interrupt */ +} + +/*-------------------------------------------------------------------------*/ + +static void +submit_async ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *qtd_list, + int mem_flags +) { + struct ehci_qtd *qtd; + struct hcd_dev *dev; + int epnum; + unsigned long flags; + struct ehci_qh *qh = 0; + + qtd = list_entry (qtd_list->next, struct ehci_qtd, qtd_list); + dev = (struct hcd_dev *)urb->dev->hcpriv; + epnum = usb_pipeendpoint (urb->pipe); + if (usb_pipein (urb->pipe)) + epnum |= 0x10; + + vdbg ("%s: submit_async urb %p len %d ep %d-%s qtd %p [qh %p]", + ehci->hcd.bus_name, urb, urb->transfer_buffer_length, + epnum & 0x0f, (epnum & 0x10) ? "in" : "out", + qtd, dev ? dev->ep [epnum] : (void *)~0); + + spin_lock_irqsave (&ehci->lock, flags); + + qh = (struct ehci_qh *) dev->ep [epnum]; + if (likely (qh != 0)) { + u32 hw_next = QTD_NEXT (qtd->qtd_dma); + + /* maybe patch the qh used for set_address */ + if (unlikely (epnum == 0 + && le32_to_cpu (qh->hw_info1 & 0x7f) == 0)) + qh->hw_info1 |= cpu_to_le32 (usb_pipedevice(urb->pipe)); + + /* is an URB is queued to this qh already? */ + if (unlikely (!list_empty (&qh->qtd_list))) { + struct ehci_qtd *last_qtd; + int short_rx = 0; + + /* update the last qtd's "next" pointer */ + // dbg_qh ("non-empty qh", ehci, qh); + last_qtd = list_entry (qh->qtd_list.prev, + struct ehci_qtd, qtd_list); + last_qtd->hw_next = hw_next; + + /* previous urb allows short rx? maybe optimize. */ + if (!(last_qtd->urb->transfer_flags & USB_DISABLE_SPD) + && (epnum & 0x10)) { + // only the last QTD for now + last_qtd->hw_alt_next = hw_next; + short_rx = 1; + } + + /* Adjust any old copies in qh overlay too. + * Interrupt code must cope with case of HC having it + * cached, and clobbering these updates. + * ... complicates getting rid of extra interrupts! + */ + if (qh->hw_current == cpu_to_le32 (last_qtd->qtd_dma)) { + wmb (); + qh->hw_qtd_next = hw_next; + if (short_rx) + qh->hw_alt_next = hw_next + | (qh->hw_alt_next & 0x1e); + vdbg ("queue to qh %p, patch", qh); + } + + /* no URB queued */ + } else { + // dbg_qh ("empty qh", ehci, qh); + +// FIXME: how handle usb_clear_halt() for an EP with queued URBs? +// usbcore may not let us handle that cleanly... +// likely must cancel them all first! + + /* usb_clear_halt() means qh data toggle gets reset */ + if (usb_pipebulk (urb->pipe) + && unlikely (!usb_gettoggle (urb->dev, + (epnum & 0x0f), + !(epnum & 0x10)))) { + clear_toggle (urb->dev, + epnum & 0x0f, !(epnum & 0x10), qh); + } + qh_update (qh, qtd); + } + list_splice (qtd_list, qh->qtd_list.prev); + + } else { + /* can't sleep here, we have ehci->lock... */ + qh = ehci_qh_make (ehci, urb, qtd_list, SLAB_ATOMIC); + if (likely (qh != 0)) { + // dbg_qh ("new qh", ehci, qh); + dev->ep [epnum] = qh; + } else + urb->status = -ENOMEM; + } + + /* Control/bulk operations through TTs don't need scheduling, + * the HC and TT handle it when the TT has a buffer ready. + */ + if (likely (qh != 0)) { + urb->hcpriv = qh_put (qh); + if (likely (qh->qh_state == QH_STATE_IDLE)) + qh_link_async (ehci, qh_put (qh)); + } + spin_unlock_irqrestore (&ehci->lock, flags); + if (unlikely (!qh)) + qh_completions (ehci, qtd_list, 1); +} + +/*-------------------------------------------------------------------------*/ + +/* the async qh for the qtds being reclaimed are now unlinked from the HC */ +/* caller must not own ehci->lock */ + +static void end_unlink_async (struct ehci_hcd *ehci) +{ + struct ehci_qh *qh = ehci->reclaim; + + qh->qh_state = QH_STATE_IDLE; + qh->qh_next.qh = 0; + qh_unput (ehci, qh); // refcount from reclaim + ehci->reclaim = 0; + ehci->reclaim_ready = 0; + + qh_completions (ehci, &qh->qtd_list, 1); + + // unlink any urb should now unlink all following urbs, so that + // relinking only happens for urbs before the unlinked ones. + if (!list_empty (&qh->qtd_list) + && HCD_IS_RUNNING (ehci->hcd.state)) + qh_link_async (ehci, qh); + else + qh_unput (ehci, qh); // refcount from async list +} + + +/* makes sure the async qh will become idle */ +/* caller must own ehci->lock */ + +static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + int cmd = readl (&ehci->regs->command); + struct ehci_qh *prev; + +#ifdef DEBUG + if (ehci->reclaim + || !ehci->async + || qh->qh_state != QH_STATE_LINKED +#ifdef CONFIG_SMP +// this macro lies except on SMP compiles + || !spin_is_locked (&ehci->lock) +#endif + ) + BUG (); +#endif + + qh->qh_state = QH_STATE_UNLINK; + ehci->reclaim = qh = qh_put (qh); + + // dbg_qh ("start unlink", ehci, qh); + + /* Remove the last QH (qhead)? Stop async schedule first. */ + if (unlikely (qh == ehci->async && qh->qh_next.qh == qh)) { + /* can't get here without STS_ASS set */ + if (ehci->hcd.state != USB_STATE_HALT) { + if (cmd & CMD_PSE) + writel (cmd & ~CMD_ASE, &ehci->regs->command); + else { + ehci_ready (ehci); + while (readl (&ehci->regs->status) & STS_ASS) + udelay (100); + } + } + qh->qh_next.qh = ehci->async = 0; + + ehci->reclaim_ready = 1; + tasklet_schedule (&ehci->tasklet); + return; + } + + if (unlikely (ehci->hcd.state == USB_STATE_HALT)) { + ehci->reclaim_ready = 1; + tasklet_schedule (&ehci->tasklet); + return; + } + + prev = ehci->async; + while (prev->qh_next.qh != qh && prev->qh_next.qh != ehci->async) + prev = prev->qh_next.qh; +#ifdef DEBUG + if (prev->qh_next.qh != qh) + BUG (); +#endif + + if (qh->hw_info1 & __constant_cpu_to_le32 (QH_HEAD)) { + ehci->async = prev; + prev->hw_info1 |= __constant_cpu_to_le32 (QH_HEAD); + } + prev->hw_next = qh->hw_next; + prev->qh_next = qh->qh_next; + + ehci->reclaim_ready = 0; + cmd |= CMD_IAAD; + writel (cmd, &ehci->regs->command); + /* posted write need not be known to HC yet ... */ +} + +/*-------------------------------------------------------------------------*/ + +static void scan_async (struct ehci_hcd *ehci) +{ + struct ehci_qh *qh; + unsigned long flags; + + spin_lock_irqsave (&ehci->lock, flags); +rescan: + qh = ehci->async; + if (likely (qh != 0)) { + do { + /* clean any finished work for this qh */ + if (!list_empty (&qh->qtd_list)) { + // dbg_qh ("scan_async", ehci, qh); + qh = qh_put (qh); + spin_unlock_irqrestore (&ehci->lock, flags); + + /* concurrent unlink could happen here */ + qh_completions (ehci, &qh->qtd_list, 1); + + spin_lock_irqsave (&ehci->lock, flags); + qh_unput (ehci, qh); + } + + /* unlink idle entries (reduces PCI usage) */ + if (list_empty (&qh->qtd_list) && !ehci->reclaim) { + if (qh->qh_next.qh != qh) { + // dbg ("irq/empty"); + start_unlink_async (ehci, qh); + } else { + // FIXME: arrange to stop + // after it's been idle a while. + } + } + qh = qh->qh_next.qh; + if (!qh) /* unlinked? */ + goto rescan; + } while (qh != ehci->async); + } + + spin_unlock_irqrestore (&ehci->lock, flags); +} diff -Nru a/drivers/usb/hcd/ehci-sched.c b/drivers/usb/hcd/ehci-sched.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ehci-sched.c Mon Feb 25 16:54:37 2002 @@ -0,0 +1,1056 @@ +/* + * Copyright (c) 2001 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* this file is part of ehci-hcd.c */ + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI scheduled transaction support: interrupt, iso, split iso + * These are called "periodic" transactions in the EHCI spec. + */ + +/* + * Ceiling microseconds (typical) for that many bytes at high speed + * ISO is a bit less, no ACK ... from USB 2.0 spec, 5.11.3 (and needed + * to preallocate bandwidth) + */ +#define EHCI_HOST_DELAY 5 /* nsec, guess */ +#define HS_USECS(bytes) NS_TO_US ( ((55 * 8 * 2083)/1000) \ + + ((2083UL * (3167 + BitTime (bytes)))/1000) \ + + EHCI_HOST_DELAY) +#define HS_USECS_ISO(bytes) NS_TO_US ( ((long)(38 * 8 * 2.083)) \ + + ((2083UL * (3167 + BitTime (bytes)))/1000) \ + + EHCI_HOST_DELAY) + +static int ehci_get_frame (struct usb_hcd *hcd); + +/*-------------------------------------------------------------------------*/ + +/* + * periodic_next_shadow - return "next" pointer on shadow list + * @periodic: host pointer to qh/itd/sitd + * @tag: hardware tag for type of this record + */ +static union ehci_shadow * +periodic_next_shadow (union ehci_shadow *periodic, int tag) +{ + switch (tag) { + case Q_TYPE_QH: + return &periodic->qh->qh_next; + case Q_TYPE_FSTN: + return &periodic->fstn->fstn_next; +#ifdef have_iso + case Q_TYPE_ITD: + return &periodic->itd->itd_next; + case Q_TYPE_SITD: + return &periodic->sitd->sitd_next; +#endif /* have_iso */ + } + dbg ("BAD shadow %p tag %d", periodic->ptr, tag); + // BUG (); + return 0; +} + +/* returns true after successful unlink */ +/* caller must hold ehci->lock */ +static int periodic_unlink (struct ehci_hcd *ehci, unsigned frame, void *ptr) +{ + union ehci_shadow *prev_p = &ehci->pshadow [frame]; + u32 *hw_p = &ehci->periodic [frame]; + union ehci_shadow here = *prev_p; + union ehci_shadow *next_p; + + /* find predecessor of "ptr"; hw and shadow lists are in sync */ + while (here.ptr && here.ptr != ptr) { + prev_p = periodic_next_shadow (prev_p, Q_NEXT_TYPE (*hw_p)); + hw_p = &here.qh->hw_next; + here = *prev_p; + } + /* an interrupt entry (at list end) could have been shared */ + if (!here.ptr) { + dbg ("entry %p no longer on frame [%d]", ptr, frame); + return 0; + } + // vdbg ("periodic unlink %p from frame %d", ptr, frame); + + /* update hardware list ... HC may still know the old structure, so + * don't change hw_next until it'll have purged its cache + */ + next_p = periodic_next_shadow (&here, Q_NEXT_TYPE (*hw_p)); + *hw_p = here.qh->hw_next; + + /* unlink from shadow list; HCD won't see old structure again */ + *prev_p = *next_p; + next_p->ptr = 0; + + return 1; +} + +/* how many of the uframe's 125 usecs are allocated? */ +static unsigned short +periodic_usecs (struct ehci_hcd *ehci, unsigned frame, unsigned uframe) +{ + u32 *hw_p = &ehci->periodic [frame]; + union ehci_shadow *q = &ehci->pshadow [frame]; + unsigned usecs = 0; +#ifdef have_iso + u32 temp = 0; +#endif + + while (q->ptr) { + switch (Q_NEXT_TYPE (*hw_p)) { + case Q_TYPE_QH: + /* is it in the S-mask? */ + if (q->qh->hw_info2 & cpu_to_le32 (1 << uframe)) + usecs += q->qh->usecs; + q = &q->qh->qh_next; + break; + case Q_TYPE_FSTN: + /* for "save place" FSTNs, count the relevant INTR + * bandwidth from the previous frame + */ + if (q->fstn->hw_prev != EHCI_LIST_END) { + dbg ("not counting FSTN bandwidth yet ..."); + } + q = &q->fstn->fstn_next; + break; +#ifdef have_iso + case Q_TYPE_ITD: + temp = le32_to_cpu (q->itd->transaction [uframe]); + temp >>= 16; + temp &= 0x0fff; + if (temp) + usecs += HS_USECS_ISO (temp); + q = &q->itd->itd_next; + break; + case Q_TYPE_SITD: + temp = q->sitd->hw_fullspeed_ep & + __constant_cpu_to_le32 (1 << 31); + + // FIXME: this doesn't count data bytes right... + + /* is it in the S-mask? (count SPLIT, DATA) */ + if (q->sitd->hw_uframe & cpu_to_le32 (1 << uframe)) { + if (temp) + usecs += HS_USECS (188); + else + usecs += HS_USECS (1); + } + + /* ... C-mask? (count CSPLIT, DATA) */ + if (q->sitd->hw_uframe & + cpu_to_le32 (1 << (8 + uframe))) { + if (temp) + usecs += HS_USECS (0); + else + usecs += HS_USECS (188); + } + q = &q->sitd->sitd_next; + break; +#endif /* have_iso */ + default: + BUG (); + } + } +#ifdef DEBUG + if (usecs > 100) + err ("overallocated uframe %d, periodic is %d usecs", + frame * 8 + uframe, usecs); +#endif + return usecs; +} + +/*-------------------------------------------------------------------------*/ + +static void intr_deschedule ( + struct ehci_hcd *ehci, + unsigned frame, + struct ehci_qh *qh, + unsigned period +) { + unsigned long flags; + + spin_lock_irqsave (&ehci->lock, flags); + + do { + periodic_unlink (ehci, frame, qh); + qh_unput (ehci, qh); + frame += period; + } while (frame < ehci->periodic_size); + + qh->qh_state = QH_STATE_UNLINK; + qh->qh_next.ptr = 0; + ehci->periodic_urbs--; + + /* maybe turn off periodic schedule */ + if (!ehci->periodic_urbs) { + u32 cmd = readl (&ehci->regs->command); + + /* did setting PSE not take effect yet? + * takes effect only at frame boundaries... + */ + while (!(readl (&ehci->regs->status) & STS_PSS)) + udelay (20); + + cmd &= ~CMD_PSE; + writel (cmd, &ehci->regs->command); + /* posted write ... */ + + ehci->next_frame = -1; + } else + vdbg ("periodic schedule still enabled"); + + spin_unlock_irqrestore (&ehci->lock, flags); + + /* + * If the hc may be looking at this qh, then delay a uframe + * (yeech!) to be sure it's done. + * No other threads may be mucking with this qh. + */ + if (((ehci_get_frame (&ehci->hcd) - frame) % period) == 0) + udelay (125); + + qh->qh_state = QH_STATE_IDLE; + qh->hw_next = EHCI_LIST_END; + + vdbg ("descheduled qh %p, per = %d frame = %d count = %d, urbs = %d", + qh, period, frame, + atomic_read (&qh->refcount), ehci->periodic_urbs); +} + +static int intr_submit ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *qtd_list, + int mem_flags +) { + unsigned epnum, period; + unsigned temp; + unsigned short mult, usecs; + unsigned long flags; + struct ehci_qh *qh; + struct hcd_dev *dev; + int status = 0; + + /* get endpoint and transfer data */ + epnum = usb_pipeendpoint (urb->pipe); + if (usb_pipein (urb->pipe)) { + temp = urb->dev->epmaxpacketin [epnum]; + epnum |= 0x10; + } else + temp = urb->dev->epmaxpacketout [epnum]; + mult = 1; + if (urb->dev->speed == USB_SPEED_HIGH) { + /* high speed "high bandwidth" is coded in ep maxpacket */ + mult += (temp >> 11) & 0x03; + temp &= 0x03ff; + } else { + dbg ("no intr/tt scheduling yet"); + status = -ENOSYS; + goto done; + } + + /* + * NOTE: current completion/restart logic doesn't handle more than + * one qtd in a periodic qh ... 16-20 KB/urb is pretty big for this. + * such big requests need many periods to transfer. + */ + if (unlikely (qtd_list->next != qtd_list->prev)) { + dbg ("only one intr qtd per urb allowed"); + status = -EINVAL; + goto done; + } + + usecs = HS_USECS (urb->transfer_buffer_length); + + /* + * force a power-of-two (frames) sized polling interval + * + * NOTE: endpoint->bInterval for highspeed is measured in uframes, + * while for full/low speeds it's in frames. Here we "know" that + * urb->interval doesn't give acccess to high interrupt rates. + */ + period = ehci->periodic_size; + temp = period; + if (unlikely (urb->interval < 1)) + urb->interval = 1; + while (temp > urb->interval) + temp >>= 1; + period = urb->interval = temp; + + spin_lock_irqsave (&ehci->lock, flags); + + /* get the qh (must be empty and idle) */ + dev = (struct hcd_dev *)urb->dev->hcpriv; + qh = (struct ehci_qh *) dev->ep [epnum]; + if (qh) { + /* only allow one queued interrupt urb per EP */ + if (unlikely (qh->qh_state != QH_STATE_IDLE + || !list_empty (&qh->qtd_list))) { + dbg ("interrupt urb already queued"); + status = -EBUSY; + } else { + /* maybe reset hardware's data toggle in the qh */ + if (unlikely (!usb_gettoggle (urb->dev, epnum & 0x0f, + !(epnum & 0x10)))) { + qh->hw_token |= + __constant_cpu_to_le32 (QTD_TOGGLE); + usb_settoggle (urb->dev, epnum & 0x0f, + !(epnum & 0x10), 1); + } + /* trust the QH was set up as interrupt ... */ + list_splice (qtd_list, &qh->qtd_list); + qh_update (qh, list_entry (qtd_list->next, + struct ehci_qtd, qtd_list)); + } + } else { + /* can't sleep here, we have ehci->lock... */ + qh = ehci_qh_make (ehci, urb, qtd_list, SLAB_ATOMIC); + qtd_list = &qh->qtd_list; + if (likely (qh != 0)) { + // dbg ("new INTR qh %p", qh); + dev->ep [epnum] = qh; + } else + status = -ENOMEM; + } + + /* Schedule this periodic QH. */ + if (likely (status == 0)) { + unsigned frame = urb->interval; + + qh->hw_next = EHCI_LIST_END; + qh->hw_info2 |= cpu_to_le32 (mult << 30); + qh->usecs = usecs; + + urb->hcpriv = qh_put (qh); + status = -ENOSPC; + + /* pick a set of schedule slots, link the QH into them */ + do { + int uframe; + + /* Select some frame 0..(urb->interval - 1) with a + * microframe that can hold this transaction. + * + * FIXME for TT splits, need uframes for start and end. + * FSTNs can put end into next frame (uframes 0 or 1). + */ + frame--; + for (uframe = 0; uframe < 8; uframe++) { + int claimed; + claimed = periodic_usecs (ehci, frame, uframe); + /* 80% periodic == 100 usec max committed */ + if ((claimed + usecs) <= 100) { + vdbg ("frame %d.%d: %d usecs, plus %d", + frame, uframe, claimed, usecs); + break; + } + } + if (uframe == 8) + continue; +// FIXME delete when code below handles non-empty queues + if (ehci->pshadow [frame].ptr) + continue; + + /* QH will run once each period, starting there */ + urb->start_frame = frame; + status = 0; + + /* set S-frame mask */ + qh->hw_info2 |= cpu_to_le32 (1 << uframe); + // dbg_qh ("Schedule INTR qh", ehci, qh); + + /* stuff into the periodic schedule */ + qh->qh_state = QH_STATE_LINKED; + vdbg ("qh %p usecs %d period %d starting frame %d.%d", + qh, qh->usecs, period, frame, uframe); + do { + if (unlikely (ehci->pshadow [frame].ptr != 0)) { +// FIXME -- just link to the end, before any qh with a shorter period, +// AND handle it already being (implicitly) linked into this frame + BUG (); + } else { + ehci->pshadow [frame].qh = qh_put (qh); + ehci->periodic [frame] = + QH_NEXT (qh->qh_dma); + } + frame += period; + } while (frame < ehci->periodic_size); + + /* update bandwidth utilization records (for usbfs) */ + usb_claim_bandwidth (urb->dev, urb, usecs, 0); + + /* maybe enable periodic schedule processing */ + if (!ehci->periodic_urbs++) { + u32 cmd; + + /* did clearing PSE did take effect yet? + * takes effect only at frame boundaries... + */ + while (readl (&ehci->regs->status) & STS_PSS) + udelay (20); + + cmd = readl (&ehci->regs->command) | CMD_PSE; + writel (cmd, &ehci->regs->command); + /* posted write ... PSS happens later */ + ehci->hcd.state = USB_STATE_RUNNING; + + /* make sure tasklet scans these */ + ehci->next_frame = ehci_get_frame (&ehci->hcd); + } + break; + + } while (frame); + } + spin_unlock_irqrestore (&ehci->lock, flags); +done: + if (status) { + usb_complete_t complete = urb->complete; + + urb->complete = 0; + urb->status = status; + qh_completions (ehci, qtd_list, 1); + urb->complete = complete; + } + return status; +} + +static unsigned long +intr_complete ( + struct ehci_hcd *ehci, + unsigned frame, + struct ehci_qh *qh, + unsigned long flags /* caller owns ehci->lock ... */ +) { + struct ehci_qtd *qtd; + struct urb *urb; + int unlinking; + + /* nothing to report? */ + if (likely ((qh->hw_token & __constant_cpu_to_le32 (QTD_STS_ACTIVE)) + != 0)) + return flags; + + qtd = list_entry (qh->qtd_list.next, struct ehci_qtd, qtd_list); + urb = qtd->urb; + unlinking = (urb->status == -ENOENT) || (urb->status == -ECONNRESET); + + /* call any completions, after patching for reactivation */ + spin_unlock_irqrestore (&ehci->lock, flags); + /* NOTE: currently restricted to one qtd per qh! */ + if (qh_completions (ehci, &qh->qtd_list, 0) == 0) + urb = 0; + spin_lock_irqsave (&ehci->lock, flags); + + /* never reactivate requests that were unlinked ... */ + if (likely (urb != 0)) { + if (unlinking + || urb->status == -ECONNRESET + || urb->status == -ENOENT + // || (urb->dev == null) + || ehci->hcd.state == USB_STATE_HALT) + urb = 0; + // FIXME look at all those unlink cases ... we always + // need exactly one completion that reports unlink. + // the one above might not have been it! + } + + /* normally reactivate */ + if (likely (urb != 0)) { + if (usb_pipeout (urb->pipe)) + pci_dma_sync_single (ehci->hcd.pdev, + qtd->buf_dma, + urb->transfer_buffer_length, + PCI_DMA_TODEVICE); + urb->status = -EINPROGRESS; + urb->actual_length = 0; + + /* patch qh and restart */ + qh_update (qh, qtd); + } + return flags; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef have_iso + +static inline void itd_free (struct ehci_hcd *ehci, struct ehci_itd *itd) +{ + pci_pool_free (ehci->itd_pool, itd, itd->itd_dma); +} + +/* + * Create itd and allocate into uframes within specified frame. + * Caller must update the resulting uframe links. + */ +static struct ehci_itd * +itd_make ( + struct ehci_hcd *ehci, + struct urb *urb, + unsigned index, // urb->iso_frame_desc [index] + unsigned frame, // scheduled start + dma_addr_t dma, // mapped transfer buffer + int mem_flags +) { + struct ehci_itd *itd; + u64 temp; + u32 buf1; + unsigned epnum, maxp, multi, usecs; + unsigned length; + unsigned i, bufnum; + + /* allocate itd, start to fill it */ + itd = pci_pool_alloc (ehci->itd_pool, mem_flags, &dma); + if (!itd) + return itd; + + itd->hw_next = EHCI_LIST_END; + itd->urb = urb; + itd->index = index; + INIT_LIST_HEAD (&itd->itd_list); + itd->uframe = (frame * 8) % ehci->periodic_size; + + /* tell itd about the buffer its transfers will consume */ + length = urb->iso_frame_desc [index].length; + dma += urb->iso_frame_desc [index].offset; + temp = dma & ~0x0fff; + for (i = 0; i < 7; i++) { + itd->hw_bufp [i] = cpu_to_le32 ((u32) temp); + itd->hw_bufp_hi [i] = cpu_to_le32 ((u32)(temp >> 32)); + temp += 0x0fff; + } + + /* + * this might be a "high bandwidth" highspeed endpoint, + * as encoded in the ep descriptor's maxpacket field + */ + epnum = usb_pipeendpoint (urb->pipe); + if (usb_pipein (urb->pipe)) { + maxp = urb->dev->epmaxpacketin [epnum]; + buf1 = (1 << 11) | maxp; + } else { + maxp = urb->dev->epmaxpacketout [epnum]; + buf1 = maxp; + } + multi = 1; + multi += (temp >> 11) & 0x03; + maxp &= 0x03ff; + + /* "plus" info in low order bits of buffer pointers */ + itd->hw_bufp [0] |= cpu_to_le32 ((epnum << 8) | urb->dev->devnum); + itd->hw_bufp [1] |= cpu_to_le32 (buf1); + itd->hw_bufp [2] |= cpu_to_le32 (multi); + + /* schedule as many uframes as needed */ + maxp *= multi; + usecs = HS_USECS_ISO (maxp); + bufnum = 0; + for (i = 0; i < 8; i++) { + unsigned t, offset, scratch; + + if (length <= 0) { + itd->hw_transaction [i] = 0; + continue; + } + + /* don't commit more than 80% periodic == 100 usec */ + if ((periodic_usecs (ehci, itd->uframe, i) + usecs) > 100) + continue; + + /* we'll use this uframe; figure hw_transaction */ + t = EHCI_ISOC_ACTIVE; + t |= bufnum << 12; // which buffer? + offset = temp & 0x0fff; // offset therein + t |= offset; + if ((offset + maxp) >= 4096) // hc auto-wraps end-of-"page" + bufnum++; + if (length <= maxp) { + // interrupt only needed at end-of-urb + if ((index + 1) == urb->number_of_packets) + t |= EHCI_ITD_IOC; + scratch = length; + } else + scratch = maxp; + t |= scratch << 16; + t = cpu_to_le32 (t); + + itd->hw_transaction [i] = itd->transaction [i] = t; + length -= scratch; + } + if (length > 0) { + dbg ("iso frame too big, urb %p [%d], %d extra (of %d)", + urb, index, length, urb->iso_frame_desc [index].length); + itd_free (ehci, itd); + itd = 0; + } + return itd; +} + +static inline void +itd_link (struct ehci_hcd *ehci, unsigned frame, struct ehci_itd *itd) +{ + u32 ptr; + + ptr = cpu_to_le32 (itd->itd_dma); // type 0 == itd + if (ehci->pshadow [frame].ptr) { + if (!itd->itd_next.ptr) { + itd->itd_next = ehci->pshadow [frame]; + itd->hw_next = ehci->periodic [frame]; + } else if (itd->itd_next.ptr != ehci->pshadow [frame].ptr) { + dbg ("frame %d itd link goof", frame); + BUG (); + } + } + ehci->pshadow [frame].itd = itd; + ehci->periodic [frame] = ptr; +} + +#define ISO_ERRS (EHCI_ISOC_BUF_ERR | EHCI_ISOC_BABBLE | EHCI_ISOC_XACTERR) + +static unsigned long +itd_complete (struct ehci_hcd *ehci, struct ehci_itd *itd, unsigned long flags) +{ + struct urb *urb = itd->urb; + + /* if not unlinking: */ + if (!(urb->transfer_flags & EHCI_STATE_UNLINK) + && ehci->hcd.state != USB_STATE_HALT) { + int i; + iso_packet_descriptor_t *desc; + struct ehci_itd *first_itd = urb->hcpriv; + + /* update status for this frame's transfers */ + desc = &urb->iso_frame_desc [itd->index]; + desc->status = 0; + desc->actual_length = 0; + for (i = 0; i < 8; i++) { + u32 t = itd->hw_transaction [i]; + if (t & (ISO_ERRS | EHCI_ISOC_ACTIVE)) { + if (t & EHCI_ISOC_ACTIVE) + desc->status = -EXDEV; + else if (t & EHCI_ISOC_BUF_ERR) + desc->status = usb_pipein (urb->pipe) + ? -ENOSR /* couldn't read */ + : -ECOMM; /* couldn't write */ + else if (t & EHCI_ISOC_BABBLE) + desc->status = -EOVERFLOW; + else /* (t & EHCI_ISOC_XACTERR) */ + desc->status = -EPROTO; + break; + } + desc->actual_length += EHCI_ITD_LENGTH (t); + } + + /* handle completion now? */ + if ((itd->index + 1) != urb->number_of_packets) + return flags; + + i = usb_pipein (urb->pipe); + if (i) + pci_dma_sync_single (ehci->hcd.pdev, + first_itd->buf_dma, + urb->transfer_buffer_length, + PCI_DMA_FROMDEVICE); + + /* call completion with no locks; it can unlink ... */ + spin_unlock_irqrestore (&ehci->lock, flags); + urb->complete (urb); + spin_lock_irqsave (&ehci->lock, flags); + + /* re-activate this URB? or unlink? */ + if (!(urb->transfer_flags & EHCI_STATE_UNLINK) + && ehci->hcd.state != USB_STATE_HALT) { + if (!i) + pci_dma_sync_single (ehci->hcd.pdev, + first_itd->buf_dma, + urb->transfer_buffer_length, + PCI_DMA_TODEVICE); + + itd = urb->hcpriv; + do { + for (i = 0; i < 8; i++) + itd->hw_transaction [i] + = itd->transaction [i]; + itd = list_entry (itd->itd_list.next, + struct ehci_itd, itd_list); + } while (itd != urb->hcpriv); + return flags; + } + + /* unlink done only on the last itd */ + } else if ((itd->index + 1) != urb->number_of_packets) + return flags; + + /* we're unlinking ... */ + + /* decouple urb from the hcd */ + spin_unlock_irqrestore (&ehci->lock, flags); + if (ehci->hcd.state == USB_STATE_HALT) + urb->status = -ESHUTDOWN; + itd = urb->hcpriv; + urb->hcpriv = 0; + ehci_urb_done (ehci, itd->buf_dma, urb); + spin_lock_irqsave (&ehci->lock, flags); + + /* take itds out of the hc's periodic schedule */ + list_entry (itd->itd_list.prev, struct ehci_itd, itd_list) + ->itd_list.next = 0; + do { + struct ehci_itd *next; + + if (itd->itd_list.next) + next = list_entry (itd->itd_list.next, + struct ehci_itd, itd_list); + else + next = 0; + + // FIXME: hc WILL (!) lap us here, if we get behind + // by 128 msec (or less, with smaller periodic_size). + // Reading/caching these itds will cause trouble... + + periodic_unlink (ehci, itd->uframe, itd); + itd_free (ehci, itd); + itd = next; + } while (itd); + return flags; +} + +/*-------------------------------------------------------------------------*/ + +static int itd_submit (struct ehci_hcd *ehci, struct urb *urb) +{ + struct ehci_itd *first_itd = 0, *itd; + unsigned frame_index; + dma_addr_t dma; + unsigned long flags; + + dbg ("itd_submit"); + + /* set up one dma mapping for this urb */ + dma = pci_map_single (ehci->hcd.pdev, + urb->transfer_buffer, urb->transfer_buffer_length, + usb_pipein (urb->pipe) + ? PCI_DMA_FROMDEVICE + : PCI_DMA_TODEVICE); + if (dma == 0) + return -ENOMEM; + + /* + * Schedule as needed. This is VERY optimistic about free + * bandwidth! But the API assumes drivers can pick frames + * intelligently (how?), so there's no other good option. + * + * FIXME this doesn't handle urb->next rings, or try to + * use the iso periodicity. + */ + if (urb->transfer_flags & USB_ISO_ASAP) { + urb->start_frame = ehci_get_frame (&ehci->hcd); + urb->start_frame++; + } + urb->start_frame %= ehci->periodic_size; + + /* create and populate itds (doing uframe scheduling) */ + spin_lock_irqsave (&ehci->lock, flags); + for (frame_index = 0; + frame_index < urb->number_of_packets; + frame_index++) { + itd = itd_make (ehci, urb, frame_index, + urb->start_frame + frame_index, + dma, SLAB_ATOMIC); + if (itd) { + if (first_itd) + list_add_tail (&itd->itd_list, + &first_itd->itd_list); + else + first_itd = itd; + } else { + spin_unlock_irqrestore (&ehci->lock, flags); + if (first_itd) { + while (!list_empty (&first_itd->itd_list)) { + itd = list_entry ( + first_itd->itd_list.next, + struct ehci_itd, itd_list); + list_del (&itd->itd_list); + itd_free (ehci, itd); + } + itd_free (ehci, first_itd); + } + pci_unmap_single (ehci->hcd.pdev, + dma, urb->transfer_buffer_length, + usb_pipein (urb->pipe) + ? PCI_DMA_FROMDEVICE + : PCI_DMA_TODEVICE); + return -ENOMEM; + } + } + + /* stuff into the schedule */ + itd = first_itd; + do { + unsigned i; + + for (i = 0; i < 8; i++) { + if (!itd->hw_transaction [i]) + continue; + itd_link (ehci, itd->uframe + i, itd); + } + itd = list_entry (itd->itd_list.next, + struct ehci_itd, itd_list); + } while (itd != first_itd); + urb->hcpriv = first_itd; + + spin_unlock_irqrestore (&ehci->lock, flags); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* + * "Split ISO TDs" ... used for USB 1.1 devices going through + * the TTs in USB 2.0 hubs. + */ + +static inline void +sitd_free (struct ehci_hcd *ehci, struct ehci_sitd *sitd) +{ + pci_pool_free (ehci->sitd_pool, sitd, sitd->sitd_dma); +} + +static struct ehci_sitd * +sitd_make ( + struct ehci_hcd *ehci, + struct urb *urb, + unsigned index, // urb->iso_frame_desc [index] + unsigned uframe, // scheduled start + dma_addr_t dma, // mapped transfer buffer + int mem_flags +) { + struct ehci_sitd *sitd; + unsigned length; + + sitd = pci_pool_alloc (ehci->sitd_pool, mem_flags, &dma); + if (!sitd) + return sitd; + sitd->urb = urb; + length = urb->iso_frame_desc [index].length; + dma += urb->iso_frame_desc [index].offset; + +#if 0 + // FIXME: do the rest! +#else + sitd_free (ehci, sitd); + return 0; +#endif + +} + +static inline void +sitd_link (struct ehci_hcd *ehci, unsigned frame, struct ehci_sitd *sitd) +{ + u32 ptr; + + ptr = cpu_to_le32 (sitd->sitd_dma | 2); // type 2 == sitd + if (ehci->pshadow [frame].ptr) { + if (!sitd->sitd_next.ptr) { + sitd->sitd_next = ehci->pshadow [frame]; + sitd->hw_next = ehci->periodic [frame]; + } else if (sitd->sitd_next.ptr != ehci->pshadow [frame].ptr) { + dbg ("frame %d sitd link goof", frame); + BUG (); + } + } + ehci->pshadow [frame].sitd = sitd; + ehci->periodic [frame] = ptr; +} + +static unsigned long +sitd_complete ( + struct ehci_hcd *ehci, + struct ehci_sitd *sitd, + unsigned long flags +) { + // FIXME -- implement! + + dbg ("NYI -- sitd_complete"); + return flags; +} + +/*-------------------------------------------------------------------------*/ + +static int sitd_submit (struct ehci_hcd *ehci, struct urb *urb) +{ + // struct ehci_sitd *first_sitd = 0; + unsigned frame_index; + dma_addr_t dma; + int mem_flags; + + dbg ("NYI -- sitd_submit"); + + // FIXME -- implement! + + // FIXME: setup one big dma mapping + dma = 0; + + mem_flags = SLAB_ATOMIC; + + for (frame_index = 0; + frame_index < urb->number_of_packets; + frame_index++) { + struct ehci_sitd *sitd; + unsigned uframe; + + // FIXME: use real arguments, schedule this! + uframe = -1; + + sitd = sitd_make (ehci, urb, frame_index, + uframe, dma, mem_flags); + + if (sitd) { + /* + if (first_sitd) + list_add_tail (&sitd->sitd_list, + &first_sitd->sitd_list); + else + first_sitd = sitd; + */ + } else { + // FIXME: clean everything up + } + } + + // if we have a first sitd, then + // store them all into the periodic schedule! + // urb->hcpriv = first sitd in sitd_list + + return -ENOSYS; +} + +#endif /* have_iso */ + +/*-------------------------------------------------------------------------*/ + +static void scan_periodic (struct ehci_hcd *ehci) +{ + unsigned frame; + unsigned clock; + unsigned long flags; + + spin_lock_irqsave (&ehci->lock, flags); + + /* + * When running, scan from last scan point up to "now" + * Touches as few pages as possible: cache-friendly. + * It's safe to scan entries more than once, though. + */ + if (HCD_IS_RUNNING (ehci->hcd.state)) { + frame = ehci->next_frame; + clock = ehci_get_frame (&ehci->hcd); + + /* when shutting down, scan everything for thoroughness */ + } else { + frame = 0; + clock = ehci->periodic_size - 1; + } + for (;;) { + union ehci_shadow q; + u32 type; + +restart: + q.ptr = ehci->pshadow [frame].ptr; + type = Q_NEXT_TYPE (ehci->periodic [frame]); + + /* scan each element in frame's queue for completions */ + while (q.ptr != 0) { + int last; + union ehci_shadow temp; + + switch (type) { + case Q_TYPE_QH: + last = (q.qh->hw_next == EHCI_LIST_END); + flags = intr_complete (ehci, frame, + qh_put (q.qh), flags); + type = Q_NEXT_TYPE (q.qh->hw_next); + temp = q.qh->qh_next; + qh_unput (ehci, q.qh); + q = temp; + break; + case Q_TYPE_FSTN: + last = (q.fstn->hw_next == EHCI_LIST_END); + /* for "save place" FSTNs, look at QH entries + * in the previous frame for completions. + */ + if (q.fstn->hw_prev != EHCI_LIST_END) { + dbg ("ignoring completions from FSTNs"); + } + type = Q_NEXT_TYPE (q.fstn->hw_next); + temp = q.fstn->fstn_next; + break; +#ifdef have_iso + case Q_TYPE_ITD: + last = (q.itd->hw_next == EHCI_LIST_END); + flags = itd_complete (ehci, q.itd, flags); + type = Q_NEXT_TYPE (q.itd->hw_next); + q = q.itd->itd_next; + break; + case Q_TYPE_SITD: + last = (q.sitd->hw_next == EHCI_LIST_END); + flags = sitd_complete (ehci, q.sitd, flags); + type = Q_NEXT_TYPE (q.sitd->hw_next); + q = q.sitd->sitd_next; + break; +#endif /* have_iso */ + default: + dbg ("corrupt type %d frame %d shadow %p", + type, frame, q.ptr); + // BUG (); + last = 1; + q.ptr = 0; + } + + /* did completion remove an interior q entry? */ + if (unlikely (q.ptr == 0 && !last)) + goto restart; + } + + /* stop when we catch up to the HC */ + + // FIXME: this assumes we won't get lapped when + // latencies climb; that should be rare, but... + // detect it, and just go all the way around. + // FLR might help detect this case, so long as latencies + // don't exceed periodic_size msec (default 1.024 sec). + + // FIXME: likewise assumes HC doesn't halt mid-scan + + if (frame == clock) { + unsigned now; + + if (!HCD_IS_RUNNING (ehci->hcd.state)) + break; + ehci->next_frame = clock; + now = ehci_get_frame (&ehci->hcd); + if (clock == now) + break; + clock = now; + } else if (++frame >= ehci->periodic_size) + frame = 0; + } + spin_unlock_irqrestore (&ehci->lock, flags); + } diff -Nru a/drivers/usb/hcd/ehci.h b/drivers/usb/hcd/ehci.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd/ehci.h Mon Feb 25 16:54:37 2002 @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2001 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __LINUX_EHCI_HCD_H +#define __LINUX_EHCI_HCD_H + +/* definitions used for the EHCI driver */ + +/* ehci_hcd->lock guards shared data against other CPUs: + * ehci_hcd: async, reclaim, periodic (and shadow), ... + * hcd_dev: ep[] + * ehci_qh: qh_next, qtd_list + * ehci_qtd: qtd_list + * + * Also, hold this lock when talking to HC registers or + * when updating hw_* fields in shared qh/qtd/... structures. + */ + +#define EHCI_MAX_ROOT_PORTS 15 /* see HCS_N_PORTS */ + +struct ehci_hcd { /* one per controller */ + spinlock_t lock; + + /* async schedule support */ + struct ehci_qh *async; + struct ehci_qh *reclaim; + int reclaim_ready; + + /* periodic schedule support */ +#define DEFAULT_I_TDPS 1024 /* some HCs can do less */ + unsigned periodic_size; + u32 *periodic; /* hw periodic table */ + dma_addr_t periodic_dma; + unsigned i_thresh; /* uframes HC might cache */ + + union ehci_shadow *pshadow; /* mirror hw periodic table */ + int next_frame; /* scan periodic, start here */ + unsigned periodic_urbs; /* how many urbs scheduled? */ + + /* deferred work from IRQ, etc */ + struct tasklet_struct tasklet; + + /* per root hub port */ + unsigned long reset_done [EHCI_MAX_ROOT_PORTS]; + + /* glue to PCI and HCD framework */ + struct usb_hcd hcd; + struct ehci_caps *caps; + struct ehci_regs *regs; + + /* per-HC memory pools (could be per-PCI-bus, but ...) */ + struct pci_pool *qh_pool; /* qh per active urb */ + struct pci_pool *qtd_pool; /* one or more per qh */ + struct pci_pool *itd_pool; /* itd per iso urb */ + struct pci_pool *sitd_pool; /* sitd per split iso urb */ +}; + +/* unwrap an HCD pointer to get an EHCI_HCD pointer */ +#define hcd_to_ehci(hcd_ptr) list_entry(hcd_ptr, struct ehci_hcd, hcd) + +/* NOTE: urb->transfer_flags expected to not use this bit !!! */ +#define EHCI_STATE_UNLINK 0x8000 /* urb being unlinked */ + +/*-------------------------------------------------------------------------*/ + +/* EHCI register interface, corresponds to EHCI Revision 0.95 specification */ + +/* Section 2.2 Host Controller Capability Registers */ +struct ehci_caps { + u8 length; /* CAPLENGTH - size of this struct */ + u8 reserved; /* offset 0x1 */ + u16 hci_version; /* HCIVERSION - offset 0x2 */ + u32 hcs_params; /* HCSPARAMS - offset 0x4 */ +#define HCS_DEBUG_PORT(p) (((p)>>20)&0xf) /* bits 23:20, debug port? */ +#define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */ +#define HCS_N_CC(p) (((p)>>12)&0xf) /* bits 15:12, #companion HCs */ +#define HCS_N_PCC(p) (((p)>>8)&0xf) /* bits 11:8, ports per CC */ +#define HCS_PORTROUTED(p) ((p)&(1 << 7)) /* true: port routing */ +#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */ +#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ + + u32 hcc_params; /* HCCPARAMS - offset 0x8 */ +#define HCC_EXT_CAPS(p) (((p)>>8)&0xff) /* for pci extended caps */ +#define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */ +#define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */ +#define HCC_CANPARK(p) ((p)&(1 << 2)) /* true: can park on async qh */ +#define HCC_PGM_FRAMELISTLEN(p) ((p)&(1 << 1)) /* true: periodic_size changes*/ +#define HCC_64BIT_ADDR(p) ((p)&(1)) /* true: can use 64-bit addr */ + u8 portroute [8]; /* nibbles for routing - offset 0xC */ +} __attribute__ ((packed)); + + +/* Section 2.3 Host Controller Operational Registers */ +struct ehci_regs { + + /* USBCMD: offset 0x00 */ + u32 command; +/* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */ +#define CMD_PARK (1<<11) /* enable "park" on async qh */ +#define CMD_PARK_CNT(c) (((c)>>8)&3) /* how many transfers to park for */ +#define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */ +#define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */ +#define CMD_ASE (1<<5) /* async schedule enable */ +#define CMD_PSE (1<<4) /* periodic schedule enable */ +/* 3:2 is periodic frame list size */ +#define CMD_RESET (1<<1) /* reset HC not bus */ +#define CMD_RUN (1<<0) /* start/stop HC */ + + /* USBSTS: offset 0x04 */ + u32 status; +#define STS_ASS (1<<15) /* Async Schedule Status */ +#define STS_PSS (1<<14) /* Periodic Schedule Status */ +#define STS_RECL (1<<13) /* Reclamation */ +#define STS_HALT (1<<12) /* Not running (any reason) */ +/* some bits reserved */ + /* these STS_* flags are also intr_enable bits (USBINTR) */ +#define STS_IAA (1<<5) /* Interrupted on async advance */ +#define STS_FATAL (1<<4) /* such as some PCI access errors */ +#define STS_FLR (1<<3) /* frame list rolled over */ +#define STS_PCD (1<<2) /* port change detect */ +#define STS_ERR (1<<1) /* "error" completion (overflow, ...) */ +#define STS_INT (1<<0) /* "normal" completion (short, ...) */ + + /* USBINTR: offset 0x08 */ + u32 intr_enable; + + /* FRINDEX: offset 0x0C */ + u32 frame_index; /* current microframe number */ + /* CTRLDSSEGMENT: offset 0x10 */ + u32 segment; /* address bits 63:32 if needed */ + /* PERIODICLISTBASE: offset 0x14 */ + u32 frame_list; /* points to periodic list */ + /* ASYNCICLISTADDR: offset 0x18 */ + u32 async_next; /* address of next async queue head */ + + u32 reserved [9]; + + /* CONFIGFLAG: offset 0x40 */ + u32 configured_flag; +#define FLAG_CF (1<<0) /* true: we'll support "high speed" */ + + /* PORTSC: offset 0x44 */ + u32 port_status [0]; /* up to N_PORTS */ +/* 31:23 reserved */ +#define PORT_WKOC_E (1<<22) /* wake on overcurrent (enable) */ +#define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */ +#define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */ +/* 19:16 for port testing */ +/* 15:14 for using port indicator leds (if HCS_INDICATOR allows) */ +#define PORT_OWNER (1<<13) /* true: companion hc owns this port */ +#define PORT_POWER (1<<12) /* true: has power (see PPC) */ +#define PORT_USB11(x) (((x)&(3<<10))==(1<<10)) /* USB 1.1 device */ +/* 11:10 for detecting lowspeed devices (reset vs release ownership) */ +/* 9 reserved */ +#define PORT_RESET (1<<8) /* reset port */ +#define PORT_SUSPEND (1<<7) /* suspend port */ +#define PORT_RESUME (1<<6) /* resume it */ +#define PORT_OCC (1<<5) /* over current change */ +#define PORT_OC (1<<4) /* over current active */ +#define PORT_PEC (1<<3) /* port enable change */ +#define PORT_PE (1<<2) /* port enable */ +#define PORT_CSC (1<<1) /* connect status change */ +#define PORT_CONNECT (1<<0) /* device connected */ +} __attribute__ ((packed)); + + +/*-------------------------------------------------------------------------*/ + +#define QTD_NEXT(dma) cpu_to_le32((u32)dma) + +/* + * EHCI Specification 0.95 Section 3.5 + * QTD: describe data transfer components (buffer, direction, ...) + * See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram". + * + * These are associated only with "QH" (Queue Head) structures, + * used with control, bulk, and interrupt transfers. + */ +struct ehci_qtd { + /* first part defined by EHCI spec */ + u32 hw_next; /* see EHCI 3.5.1 */ + u32 hw_alt_next; /* see EHCI 3.5.2 */ + u32 hw_token; /* see EHCI 3.5.3 */ +#define QTD_TOGGLE (1 << 31) /* data toggle */ +#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) +#define QTD_IOC (1 << 15) /* interrupt on complete */ +#define QTD_CERR(tok) (((tok)>>10) & 0x3) +#define QTD_PID(tok) (((tok)>>8) & 0x3) +#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ +#define QTD_STS_HALT (1 << 6) /* halted on error */ +#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ +#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ +#define QTD_STS_XACT (1 << 3) /* device gave illegal response */ +#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ +#define QTD_STS_STS (1 << 1) /* split transaction state */ +#define QTD_STS_PING (1 << 0) /* issue PING? */ + u32 hw_buf [5]; /* see EHCI 3.5.4 */ + u32 hw_buf_hi [5]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t qtd_dma; /* qtd address */ + struct list_head qtd_list; /* sw qtd list */ + + /* dma same in urb's qtds, except 1st control qtd (setup buffer) */ + struct urb *urb; /* qtd's urb */ + dma_addr_t buf_dma; /* buffer address */ + size_t length; /* length of buffer */ +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* type tag from {qh,itd,sitd,fstn}->hw_next */ +#define Q_NEXT_TYPE(dma) ((dma) & __constant_cpu_to_le32 (3 << 1)) + +/* values for that type tag */ +#define Q_TYPE_ITD __constant_cpu_to_le32 (0 << 1) +#define Q_TYPE_QH __constant_cpu_to_le32 (1 << 1) +#define Q_TYPE_SITD __constant_cpu_to_le32 (2 << 1) +#define Q_TYPE_FSTN __constant_cpu_to_le32 (3 << 1) + +/* next async queue entry, or pointer to interrupt/periodic QH */ +#define QH_NEXT(dma) (cpu_to_le32(((u32)dma)&~0x01f)|Q_TYPE_QH) + +/* for periodic/async schedules and qtd lists, mark end of list */ +#define EHCI_LIST_END __constant_cpu_to_le32(1) /* "null pointer" to hw */ + +/* + * Entries in periodic shadow table are pointers to one of four kinds + * of data structure. That's dictated by the hardware; a type tag is + * encoded in the low bits of the hardware's periodic schedule. Use + * Q_NEXT_TYPE to get the tag. + * + * For entries in the async schedule, the type tag always says "qh". + */ +union ehci_shadow { + struct ehci_qh *qh; /* Q_TYPE_QH */ + struct ehci_itd *itd; /* Q_TYPE_ITD */ + struct ehci_sitd *sitd; /* Q_TYPE_SITD */ + struct ehci_fstn *fstn; /* Q_TYPE_FSTN */ + void *ptr; +}; + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.95 Section 3.6 + * QH: describes control/bulk/interrupt endpoints + * See Fig 3-7 "Queue Head Structure Layout". + * + * These appear in both the async and (for interrupt) periodic schedules. + */ + +struct ehci_qh { + /* first part defined by EHCI spec */ + u32 hw_next; /* see EHCI 3.6.1 */ + u32 hw_info1; /* see EHCI 3.6.2 */ +#define QH_HEAD 0x00008000 + u32 hw_info2; /* see EHCI 3.6.2 */ + u32 hw_current; /* qtd list - see EHCI 3.6.4 */ + + /* qtd overlay (hardware parts of a struct ehci_qtd) */ + u32 hw_qtd_next; + u32 hw_alt_next; + u32 hw_token; + u32 hw_buf [5]; + u32 hw_buf_hi [5]; + + /* the rest is HCD-private */ + dma_addr_t qh_dma; /* address of qh */ + union ehci_shadow qh_next; /* ptr to qh; or periodic */ + struct list_head qtd_list; /* sw qtd list */ + + atomic_t refcount; + unsigned short usecs; /* intr bandwidth */ + short qh_state; +#define QH_STATE_LINKED 1 /* HC sees this */ +#define QH_STATE_UNLINK 2 /* HC may still see this */ +#define QH_STATE_IDLE 3 /* HC doesn't see this */ + +#ifdef EHCI_SOFT_RETRIES + int retries; +#endif +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.95 Section 3.3 + * Fig 3-4 "Isochronous Transaction Descriptor (iTD)" + * + * Schedule records for high speed iso xfers + */ +struct ehci_itd { + /* first part defined by EHCI spec */ + u32 hw_next; /* see EHCI 3.3.1 */ + u32 hw_transaction [8]; /* see EHCI 3.3.2 */ +#define EHCI_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ +#define EHCI_ISOC_BUF_ERR (1<<30) /* Data buffer error */ +#define EHCI_ISOC_BABBLE (1<<29) /* babble detected */ +#define EHCI_ISOC_XACTERR (1<<28) /* XactErr - transaction error */ +#define EHCI_ITD_LENGTH(tok) (((tok)>>16) & 0x7fff) +#define EHCI_ITD_IOC (1 << 15) /* interrupt on complete */ + + u32 hw_bufp [7]; /* see EHCI 3.3.3 */ + u32 hw_bufp_hi [7]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t itd_dma; /* for this itd */ + union ehci_shadow itd_next; /* ptr to periodic q entry */ + + struct urb *urb; + unsigned index; /* in urb->iso_frame_desc */ + struct list_head itd_list; /* list of urb frames' itds */ + dma_addr_t buf_dma; /* frame's buffer address */ + + unsigned uframe; /* in periodic schedule */ + u32 transaction [8]; /* copy of hw_transaction */ + +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.95 Section 3.4 + * siTD, aka split-transaction isochronous Transfer Descriptor + * ... describe low/full speed iso xfers through TT in hubs + * see Figure 3-5 "Split-transaction Isochronous Transaction Descriptor (siTD) + */ +struct ehci_sitd { + /* first part defined by EHCI spec */ + u32 hw_next; +/* uses bit field macros above - see EHCI 0.95 Table 3-8 */ + u32 hw_fullspeed_ep; /* see EHCI table 3-9 */ + u32 hw_uframe; /* see EHCI table 3-10 */ + u32 hw_tx_results1; /* see EHCI table 3-11 */ + u32 hw_tx_results2; /* see EHCI table 3-12 */ + u32 hw_tx_results3; /* see EHCI table 3-12 */ + u32 hw_backpointer; /* see EHCI table 3-13 */ + u32 hw_buf_hi [2]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t sitd_dma; + union ehci_shadow sitd_next; /* ptr to periodic q entry */ + struct urb *urb; + dma_addr_t buf_dma; /* buffer address */ +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.96 Section 3.7 + * Periodic Frame Span Traversal Node (FSTN) + * + * Manages split interrupt transactions (using TT) that span frame boundaries + * into uframes 0/1; see 4.12.2.2. In those uframes, a "save place" FSTN + * makes the HC jump (back) to a QH to scan for fs/ls QH completions until + * it hits a "restore" FSTN; then it returns to finish other uframe 0/1 work. + */ +struct ehci_fstn { + u32 hw_next; /* any periodic q entry */ + u32 hw_prev; /* qh or EHCI_LIST_END */ + + /* the rest is HCD-private */ + dma_addr_t fstn_dma; + union ehci_shadow fstn_next; /* ptr to periodic q entry */ +} __attribute__ ((aligned (32))); + +#endif /* __LINUX_EHCI_HCD_H */ diff -Nru a/drivers/usb/hcd.c b/drivers/usb/hcd.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd.c Mon Feb 25 16:54:37 2002 @@ -0,0 +1,1307 @@ +/* + * Copyright (c) 2001 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#else + #undef DEBUG +#endif + +#include +#include "hcd.h" + +#include +#include +#include +#include + + +/*-------------------------------------------------------------------------*/ + +/* + * USB Host Controller Driver framework + * + * Plugs into usbcore (usb_bus) and lets HCDs share code, minimizing + * HCD-specific behaviors/bugs. + * + * This does error checks, tracks devices and urbs, and delegates to a + * "hc_driver" only for code (and data) that really needs to know about + * hardware differences. That includes root hub registers, i/o queues, + * and so on ... but as little else as possible. + * + * Shared code includes most of the "root hub" code (these are emulated, + * though each HC's hardware works differently) and PCI glue, plus request + * tracking overhead. The HCD code should only block on spinlocks or on + * hardware handshaking; blocking on software events (such as other kernel + * threads releasing resources, or completing actions) is all generic. + * + * Happens the USB 2.0 spec says this would be invisible inside the "USBD", + * and includes mostly a "HCDI" (HCD Interface) along with some APIs used + * only by the hub driver ... and that neither should be seen or used by + * usb client device drivers. + * + * Contributors of ideas or unattributed patches include: David Brownell, + * Roman Weissgaerber, Rory Bolt, ... + * + * HISTORY: + * 2001-12-12 Initial patch version for Linux 2.5.1 kernel. + */ + +/*-------------------------------------------------------------------------*/ + +/* host controllers we manage */ +static LIST_HEAD (hcd_list); + +/* used when updating list of hcds */ +static DECLARE_MUTEX (hcd_list_lock); + +/* used when updating hcd data */ +static spinlock_t hcd_data_lock = SPIN_LOCK_UNLOCKED; + +static struct usb_operations hcd_operations; + +/*-------------------------------------------------------------------------*/ + +/* + * Sharable chunks of root hub code. + */ + +/*-------------------------------------------------------------------------*/ + +/* usb 2.0 root hub device descriptor */ +static const u8 usb2_rh_dev_descriptor [18] = { + 0x12, /* __u8 bLength; */ + 0x01, /* __u8 bDescriptorType; Device */ + 0x00, 0x02, /* __u16 bcdUSB; v2.0 */ + + 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* __u8 bDeviceSubClass; */ + 0x01, /* __u8 bDeviceProtocol; [ usb 2.0 single TT ]*/ + 0x08, /* __u8 bMaxPacketSize0; 8 Bytes */ + + 0x00, 0x00, /* __u16 idVendor; */ + 0x00, 0x00, /* __u16 idProduct; */ + 0x40, 0x02, /* __u16 bcdDevice; (v2.4) */ + + 0x03, /* __u8 iManufacturer; */ + 0x02, /* __u8 iProduct; */ + 0x01, /* __u8 iSerialNumber; */ + 0x01 /* __u8 bNumConfigurations; */ +}; + +/* no usb 2.0 root hub "device qualifier" descriptor: one speed only */ + +/* usb 1.1 root hub device descriptor */ +static const u8 usb11_rh_dev_descriptor [18] = { + 0x12, /* __u8 bLength; */ + 0x01, /* __u8 bDescriptorType; Device */ + 0x10, 0x01, /* __u16 bcdUSB; v1.1 */ + + 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* __u8 bDeviceSubClass; */ + 0x00, /* __u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x08, /* __u8 bMaxPacketSize0; 8 Bytes */ + + 0x00, 0x00, /* __u16 idVendor; */ + 0x00, 0x00, /* __u16 idProduct; */ + 0x40, 0x02, /* __u16 bcdDevice; (v2.4) */ + + 0x03, /* __u8 iManufacturer; */ + 0x02, /* __u8 iProduct; */ + 0x01, /* __u8 iSerialNumber; */ + 0x01 /* __u8 bNumConfigurations; */ +}; + + +/*-------------------------------------------------------------------------*/ + +/* Configuration descriptor for all our root hubs */ + +static const u8 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) */ + 0x0c /* __u8 ep_bInterval; (12ms -- usb 2.0 spec) */ +}; + +/*-------------------------------------------------------------------------*/ + +/* + * 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) +{ + int retval; + + for (retval = 0; *ascii && utfmax > 1; utfmax -= 2, retval += 2) { + *utf++ = *ascii++ & 0x7f; + *utf++ = 0; + } + return retval; +} + +/* + * rh_string - provides manufacturer, product and serial strings for root hub + * @id: the string ID number (1: serial number, 2: product, 3: vendor) + * @pci_desc: PCI device descriptor for the relevant HC + * @type: string describing our driver + * @data: return packet in UTF-16 LE + * @len: length of the return packet + * + * Produces either a manufacturer, product or serial number string for the + * virtual root hub device. + */ +static int rh_string ( + int id, + struct pci_dev *pci_desc, + char *type, + u8 *data, + int len +) { + char buf [100]; + + // language ids + if (id == 0) { + *data++ = 4; *data++ = 3; /* 4 bytes string data */ + *data++ = 0; *data++ = 0; /* some language id */ + return 4; + + // serial number + } else if (id == 1) { + strcpy (buf, pci_desc->slot_name); + + // product description + } else if (id == 2) { + strcpy (buf, pci_desc->name); + + // id 3 == vendor description + } else if (id == 3) { + sprintf (buf, "%s %s %s", UTS_SYSNAME, UTS_RELEASE, type); + + // unsupported IDs --> "protocol stall" + } else + return 0; + + data [0] = 2 + ascii2utf (buf, data + 2, len - 2); + data [1] = 3; /* type == string */ + return data [0]; +} + + +/* Root hub control transfers execute synchronously */ +static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) +{ + devrequest *cmd = (devrequest *) urb->setup_packet; + u16 typeReq, wValue, wIndex, wLength; + const u8 *bufp = 0; + u8 *ubuf = urb->transfer_buffer; + int len = 0; + + typeReq = (cmd->requesttype << 8) | cmd->request; + wValue = le16_to_cpu (cmd->value); + wIndex = le16_to_cpu (cmd->index); + wLength = le16_to_cpu (cmd->length); + + if (wLength > urb->transfer_buffer_length) + goto error; + + /* set up for success */ + urb->status = 0; + urb->actual_length = wLength; + switch (typeReq) { + + /* DEVICE REQUESTS */ + + case DeviceRequest | USB_REQ_GET_STATUS: + // DEVICE_REMOTE_WAKEUP + ubuf [0] = 1; // selfpowered + ubuf [1] = 0; + /* FALLTHROUGH */ + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + case DeviceOutRequest | USB_REQ_SET_FEATURE: + dbg ("no device features yet yet"); + break; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + ubuf [0] = 1; + /* FALLTHROUGH */ + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch (wValue & 0xff00) { + case USB_DT_DEVICE << 8: + if (hcd->driver->flags & HCD_USB2) + bufp = usb2_rh_dev_descriptor; + else if (hcd->driver->flags & HCD_USB11) + bufp = usb11_rh_dev_descriptor; + else + goto error; + len = 18; + break; + case USB_DT_CONFIG << 8: + bufp = rh_config_descriptor; + len = sizeof rh_config_descriptor; + break; + case USB_DT_STRING << 8: + urb->actual_length = rh_string ( + wValue & 0xff, + hcd->pdev, + (char *) hcd->description, + ubuf, wLength); + break; + default: + goto error; + } + break; + case DeviceRequest | USB_REQ_GET_INTERFACE: + ubuf [0] = 0; + /* FALLTHROUGH */ + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + break; + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + // wValue == urb->dev->devaddr + dbg ("%s root hub device address %d", + hcd->bus_name, wValue); + break; + + /* INTERFACE REQUESTS (no defined feature/status flags) */ + + /* ENDPOINT REQUESTS */ + + case EndpointRequest | USB_REQ_GET_STATUS: + // ENDPOINT_HALT flag + ubuf [0] = 0; + ubuf [1] = 0; + /* FALLTHROUGH */ + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + case EndpointOutRequest | USB_REQ_SET_FEATURE: + dbg ("no endpoint features yet"); + break; + + /* CLASS REQUESTS (and errors) */ + + default: + /* non-generic request */ + urb->status = hcd->driver->hub_control (hcd, + typeReq, wValue, wIndex, + ubuf, wLength); + break; +error: + /* "protocol stall" on error */ + urb->status = -EPIPE; + dbg ("unsupported hub control message (maxchild %d)", + urb->dev->maxchild); + } + if (urb->status) { + urb->actual_length = 0; + dbg ("CTRL: TypeReq=0x%x val=0x%x idx=0x%x len=%d ==> %d", + typeReq, wValue, wIndex, wLength, urb->status); + } + if (bufp) { + if (urb->transfer_buffer_length < len) + len = urb->transfer_buffer_length; + urb->actual_length = len; + // always USB_DIR_IN, toward host + memcpy (ubuf, bufp, len); + } + + /* any errors get returned through the urb completion */ + usb_hcd_giveback_urb (hcd, urb); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* + * Root Hub interrupt transfers are synthesized with a timer. + * Completions are called in_interrupt() but not in_irq(). + */ + +static void rh_report_status (unsigned long ptr); + +static int rh_status_urb (struct usb_hcd *hcd, struct urb *urb) +{ + int len = 1 + (urb->dev->maxchild / 8); + + /* rh_timer protected by hcd_data_lock */ + if (timer_pending (&hcd->rh_timer) + || urb->status != -EINPROGRESS + || !HCD_IS_RUNNING (hcd->state) + || urb->transfer_buffer_length < len) { + dbg ("not queuing status urb, stat %d", urb->status); + return -EINVAL; + } + + urb->hcpriv = hcd; /* nonzero to indicate it's queued */ + 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; + add_timer (&hcd->rh_timer); + return 0; +} + +/* timer callback */ + +static void rh_report_status (unsigned long ptr) +{ + struct urb *urb; + struct usb_hcd *hcd; + int length; + unsigned long flags; + + urb = (struct urb *) ptr; + spin_lock_irqsave (&urb->lock, flags); + if (!urb->dev) { + spin_unlock_irqrestore (&urb->lock, flags); + return; + } + + hcd = urb->dev->bus->hcpriv; + if (urb->status == -EINPROGRESS) { + if (HCD_IS_RUNNING (hcd->state)) { + length = hcd->driver->hub_status_data (hcd, + urb->transfer_buffer); + spin_unlock_irqrestore (&urb->lock, flags); + if (length > 0) { + urb->actual_length = length; + urb->status = 0; + urb->complete (urb); + } + spin_lock_irqsave (&hcd_data_lock, flags); + urb->status = -EINPROGRESS; + if (HCD_IS_RUNNING (hcd->state) + && rh_status_urb (hcd, urb) != 0) { + /* another driver snuck in? */ + dbg ("%s, can't resubmit roothub status urb?", + hcd->bus_name); + spin_unlock_irqrestore (&hcd_data_lock, flags); + BUG (); + } + spin_unlock_irqrestore (&hcd_data_lock, flags); + } else + spin_unlock_irqrestore (&urb->lock, flags); + } else { + /* this urb's been unlinked */ + urb->hcpriv = 0; + spin_unlock_irqrestore (&urb->lock, flags); + + usb_hcd_giveback_urb (hcd, urb); + } +} + +/*-------------------------------------------------------------------------*/ + +static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb) +{ + if (usb_pipeint (urb->pipe)) { + int retval; + unsigned long flags; + + spin_lock_irqsave (&hcd_data_lock, flags); + retval = rh_status_urb (hcd, urb); + spin_unlock_irqrestore (&hcd_data_lock, flags); + return retval; + } + if (usb_pipecontrol (urb->pipe)) + return rh_call_control (hcd, urb); + else + return -EINVAL; +} + +/*-------------------------------------------------------------------------*/ + +static void rh_status_dequeue (struct usb_hcd *hcd, struct urb *urb) +{ + unsigned long flags; + + spin_lock_irqsave (&hcd_data_lock, flags); + del_timer_sync (&hcd->rh_timer); + hcd->rh_timer.data = 0; + spin_unlock_irqrestore (&hcd_data_lock, flags); + + /* we rely on RH callback code not unlinking its URB! */ + usb_hcd_giveback_urb (hcd, urb); +} + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_PCI + +/* PCI-based HCs are normal, but custom bus glue should be ok */ + +static void hcd_irq (int irq, void *__hcd, struct pt_regs *r); +static void hc_died (struct usb_hcd *hcd); + +/*-------------------------------------------------------------------------*/ + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + +/** + * usb_hcd_pci_probe - initialize PCI-based HCDs + * @dev: USB Host Controller being probed + * @id: pci hotplug id connecting controller to HCD framework + * + * Allocates basic PCI resources for this USB host controller, and + * then invokes the start() method for the HCD associated with it + * through the hotplug entry's driver_data. + * + * Store this function in the HCD's struct pci_driver as probe(). + */ +int usb_hcd_pci_probe (struct pci_dev *dev, const struct pci_device_id *id) +{ + struct hc_driver *driver; + unsigned long resource, len; + void *base; + u8 latency, limit; + struct usb_bus *bus; + struct usb_hcd *hcd; + int retval, region; + char buf [8], *bufp = buf; + + if (!id || !(driver = (struct hc_driver *) id->driver_data)) + return -EINVAL; + + if (pci_enable_device (dev) < 0) + return -ENODEV; + + if (!dev->irq) { + err ("Found HC with no IRQ. Check BIOS/PCI %s setup!", + dev->slot_name); + return -ENODEV; + } + + if (driver->flags & HCD_MEMORY) { // EHCI, OHCI + region = 0; + resource = pci_resource_start (dev, 0); + len = pci_resource_len (dev, 0); + if (!request_mem_region (resource, len, driver->description)) { + dbg ("controller already in use"); + return -EBUSY; + } + base = ioremap_nocache (resource, len); + if (base == NULL) { + dbg ("error mapping memory"); + retval = -EFAULT; +clean_1: + release_mem_region (resource, len); + err ("init %s fail, %d", dev->slot_name, retval); + return retval; + } + + } else { // UHCI + resource = len = 0; + for (region = 0; region < PCI_ROM_RESOURCE; region++) { + if (!(pci_resource_flags (dev, region) & IORESOURCE_IO)) + continue; + + resource = pci_resource_start (dev, region); + len = pci_resource_len (dev, region); + if (request_region (resource, len, + driver->description)) + break; + } + if (region == PCI_ROM_RESOURCE) { + dbg ("no i/o regions available"); + return -EBUSY; + } + base = (void *) resource; + } + + // driver->start(), later on, will transfer device from + // control by SMM/BIOS to control by Linux (if needed) + + pci_set_master (dev); + hcd = driver->hcd_alloc (); + if (hcd == NULL){ + dbg ("hcd alloc fail"); + retval = -ENOMEM; +clean_2: + if (driver->flags & HCD_MEMORY) { + iounmap (base); + goto clean_1; + } else { + release_region (resource, len); + err ("init %s fail, %d", dev->slot_name, retval); + return retval; + } + } + dev->driver_data = hcd; + hcd->driver = driver; + hcd->description = driver->description; + hcd->pdev = dev; + info ("%s @ %s, %s", hcd->description, dev->slot_name, dev->name); + + pci_read_config_byte (dev, PCI_LATENCY_TIMER, &latency); + if (latency) { + pci_read_config_byte (dev, PCI_MAX_LAT, &limit); + if (limit && limit < latency) { + dbg ("PCI latency reduced to max %d", limit); + pci_write_config_byte (dev, PCI_LATENCY_TIMER, limit); + } + } + +#ifndef __sparc__ + sprintf (buf, "%d", dev->irq); +#else + bufp = __irq_itoa(irq); +#endif + if (request_irq (dev->irq, hcd_irq, SA_SHIRQ, hcd->description, hcd) + != 0) { + err ("request interrupt %s failed", bufp); + retval = -EBUSY; +clean_3: + driver->hcd_free (hcd); + goto clean_2; + } + hcd->irq = dev->irq; + + hcd->regs = base; + hcd->region = region; + info ("irq %s, %s %p", bufp, + (driver->flags & HCD_MEMORY) ? "pci mem" : "io base", + base); + +// FIXME simpler: make "bus" be that data, not pointer to it. + bus = usb_alloc_bus (&hcd_operations); + if (bus == NULL) { + dbg ("usb_alloc_bus fail"); + retval = -ENOMEM; + free_irq (dev->irq, hcd); + goto clean_3; + } + hcd->bus = bus; + hcd->bus_name = dev->slot_name; + bus->hcpriv = (void *) hcd; + + INIT_LIST_HEAD (&hcd->dev_list); + INIT_LIST_HEAD (&hcd->hcd_list); + + down (&hcd_list_lock); + list_add (&hcd->hcd_list, &hcd_list); + up (&hcd_list_lock); + + usb_register_bus (bus); + + if ((retval = driver->start (hcd)) < 0) + usb_hcd_pci_remove (dev); + + return retval; +} +EXPORT_SYMBOL (usb_hcd_pci_probe); + + +/* may be called without controller electrically present */ +/* may be called with controller, bus, and devices active */ + +/** + * usb_hcd_pci_remove - shutdown processing for PCI-based HCDs + * @dev: USB Host Controller being removed + * + * Reverses the effect of usb_hcd_pci_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + * + * Store this function in the HCD's struct pci_driver as remove(). + */ +void usb_hcd_pci_remove (struct pci_dev *dev) +{ + struct usb_hcd *hcd; + struct usb_device *hub; + + hcd = (struct usb_hcd *) dev->driver_data; + if (!hcd) + return; + info ("remove: %s, state %x", hcd->bus_name, hcd->state); + + if (in_interrupt ()) BUG (); + + hub = hcd->bus->root_hub; + hcd->state = USB_STATE_QUIESCING; + + dbg ("%s: roothub graceful disconnect", hcd->bus_name); + usb_disconnect (&hub); + // usb_disconnect (&hcd->bus->root_hub); + + hcd->driver->stop (hcd); + hcd->state = USB_STATE_HALT; + + free_irq (hcd->irq, hcd); + if (hcd->driver->flags & HCD_MEMORY) { + iounmap (hcd->regs); + release_mem_region (pci_resource_start (dev, 0), + pci_resource_len (dev, 0)); + } else { + release_region (pci_resource_start (dev, hcd->region), + pci_resource_len (dev, hcd->region)); + } + + down (&hcd_list_lock); + list_del (&hcd->hcd_list); + up (&hcd_list_lock); + + usb_deregister_bus (hcd->bus); + usb_free_bus (hcd->bus); + hcd->bus = NULL; + + hcd->driver->hcd_free (hcd); +} +EXPORT_SYMBOL (usb_hcd_pci_remove); + + +#ifdef CONFIG_PM + +/* + * Some "sleep" power levels imply updating struct usb_driver + * to include a callback asking hcds to do their bit by checking + * if all the drivers can suspend. Gets involved with remote wakeup. + * + * If there are pending urbs, then HCs will need to access memory, + * causing extra power drain. New sleep()/wakeup() PM calls might + * be needed, beyond PCI suspend()/resume(). The root hub timer + * still be accessing memory though ... + * + * FIXME: USB should have some power budgeting support working with + * all kinds of hubs. + * + * FIXME: This assumes only D0->D3 suspend and D3->D0 resume. + * D1 and D2 states should do something, yes? + * + * FIXME: Should provide generic enable_wake(), calling pci_enable_wake() + * for all supported states, so that USB remote wakeup can work for any + * devices that support it (and are connected via powered hubs). + * + * FIXME: resume doesn't seem to work right any more... + */ + + +// 2.4 kernels have issued concurrent resumes (w/APM) +// we defend against that error; PCI doesn't yet. + +/** + * usb_hcd_pci_suspend - power management suspend of a PCI-based HCD + * @dev: USB Host Controller being suspended + * + * Store this function in the HCD's struct pci_driver as suspend(). + */ +int usb_hcd_pci_suspend (struct pci_dev *dev, u32 state) +{ + struct usb_hcd *hcd; + int retval; + + hcd = (struct usb_hcd *) dev->driver_data; + info ("suspend %s to state %d", hcd->bus_name, state); + + pci_save_state (dev, hcd->pci_state); + + // FIXME for all connected devices, leaf-to-root: + // driver->suspend() + // proposed "new 2.5 driver model" will automate that + + /* driver may want to disable DMA etc */ + retval = hcd->driver->suspend (hcd, state); + hcd->state = USB_STATE_SUSPENDED; + + pci_set_power_state (dev, state); + return retval; +} +EXPORT_SYMBOL (usb_hcd_pci_suspend); + +/** + * usb_hcd_pci_resume - power management resume of a PCI-based HCD + * @dev: USB Host Controller being resumed + * + * Store this function in the HCD's struct pci_driver as resume(). + */ +int usb_hcd_pci_resume (struct pci_dev *dev) +{ + struct usb_hcd *hcd; + int retval; + + hcd = (struct usb_hcd *) dev->driver_data; + info ("resume %s", hcd->bus_name); + + /* guard against multiple resumes (APM bug?) */ + atomic_inc (&hcd->resume_count); + if (atomic_read (&hcd->resume_count) != 1) { + err ("concurrent PCI resumes for %s", hcd->bus_name); + retval = 0; + goto done; + } + + retval = -EBUSY; + if (hcd->state != USB_STATE_SUSPENDED) { + dbg ("can't resume, not suspended!"); + goto done; + } + hcd->state = USB_STATE_RESUMING; + + pci_set_power_state (dev, 0); + pci_restore_state (dev, hcd->pci_state); + + retval = hcd->driver->resume (hcd); + if (!HCD_IS_RUNNING (hcd->state)) { + dbg ("resume %s failure, retval %d", hcd->bus_name, retval); + hc_died (hcd); +// FIXME: recover, reset etc. + } else { + // FIXME for all connected devices, root-to-leaf: + // driver->resume (); + // proposed "new 2.5 driver model" will automate that + } + +done: + atomic_dec (&hcd->resume_count); + return retval; +} +EXPORT_SYMBOL (usb_hcd_pci_resume); + +#endif /* CONFIG_PM */ + +#endif + +/*-------------------------------------------------------------------------*/ + +/* + * Generic HC operations. + */ + +/*-------------------------------------------------------------------------*/ + +/* called from khubd, or root hub init threads for hcd-private init */ +static int hcd_alloc_dev (struct usb_device *udev) +{ + struct hcd_dev *dev; + struct usb_hcd *hcd; + unsigned long flags; + + if (!udev || udev->hcpriv) + return -EINVAL; + if (!udev->bus || !udev->bus->hcpriv) + return -ENODEV; + hcd = udev->bus->hcpriv; + if (hcd->state == USB_STATE_QUIESCING) + return -ENOLINK; + + dev = (struct hcd_dev *) kmalloc (sizeof *dev, GFP_KERNEL); + if (dev == NULL) + return -ENOMEM; + memset (dev, 0, sizeof *dev); + + INIT_LIST_HEAD (&dev->dev_list); + INIT_LIST_HEAD (&dev->urb_list); + + spin_lock_irqsave (&hcd_data_lock, flags); + list_add (&dev->dev_list, &hcd->dev_list); + // refcount is implicit + udev->hcpriv = dev; + spin_unlock_irqrestore (&hcd_data_lock, flags); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static void hc_died (struct usb_hcd *hcd) +{ + struct list_head *devlist, *urblist; + struct hcd_dev *dev; + struct urb *urb; + unsigned long flags; + + /* flag every pending urb as done */ + spin_lock_irqsave (&hcd_data_lock, flags); + list_for_each (devlist, &hcd->dev_list) { + dev = list_entry (devlist, struct hcd_dev, dev_list); + list_for_each (urblist, &dev->urb_list) { + urb = list_entry (urblist, struct urb, urb_list); + dbg ("shutdown %s urb %p pipe %x, current status %d", + hcd->bus_name, urb, urb->pipe, urb->status); + if (urb->status == -EINPROGRESS) + urb->status = -ESHUTDOWN; + } + } + urb = (struct urb *) hcd->rh_timer.data; + if (urb) + urb->status = -ESHUTDOWN; + spin_unlock_irqrestore (&hcd_data_lock, flags); + + if (urb) + rh_status_dequeue (hcd, urb); + hcd->driver->stop (hcd); +} + +/*-------------------------------------------------------------------------*/ + +/* may be called in any context with a valid urb->dev usecount */ +/* caller surrenders "ownership" of urb (and chain at urb->next). */ + +static int hcd_submit_urb (struct urb *urb) +{ + int status; + struct usb_hcd *hcd; + struct hcd_dev *dev; + unsigned long flags; + int pipe; + int mem_flags; + + if (!urb || urb->hcpriv || !urb->complete) + return -EINVAL; + + urb->status = -EINPROGRESS; + urb->actual_length = 0; + INIT_LIST_HEAD (&urb->urb_list); + + if (!urb->dev || !urb->dev->bus || urb->dev->devnum <= 0) + return -ENODEV; + hcd = urb->dev->bus->hcpriv; + dev = urb->dev->hcpriv; + if (!hcd || !dev) + return -ENODEV; + + /* can't submit new urbs when quiescing, halted, ... */ + if (hcd->state == USB_STATE_QUIESCING || !HCD_IS_RUNNING (hcd->state)) + return -ESHUTDOWN; + pipe = 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. + mem_flags = in_interrupt () ? GFP_ATOMIC : GFP_KERNEL; + +#ifdef DEBUG + { + unsigned int orig_flags = urb->transfer_flags; + unsigned int allowed; + + /* enforce simple/standard policy */ + allowed = USB_ASYNC_UNLINK; // affects later unlinks + allowed |= USB_NO_FSBR; // only affects UHCI + switch (usb_pipetype (pipe)) { + case PIPE_CONTROL: + allowed |= USB_DISABLE_SPD; + break; + case PIPE_BULK: + allowed |= USB_DISABLE_SPD | USB_QUEUE_BULK + | USB_ZERO_PACKET | URB_NO_INTERRUPT; + break; + case PIPE_INTERRUPT: + allowed |= USB_DISABLE_SPD; + break; + case PIPE_ISOCHRONOUS: + allowed |= USB_ISO_ASAP; + break; + } + urb->transfer_flags &= allowed; + + /* warn if submitter gave bogus flags */ + if (urb->transfer_flags != orig_flags) + warn ("BOGUS urb flags, %x --> %x", + orig_flags, urb->transfer_flags); + } +#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. + * + * FIXME: make urb timeouts be generic, keeping the HCD cores + * as simple as possible. + */ + + // NOTE: a generic device/urb monitoring hook would go here. + // hcd_monitor_hook(MONITOR_URB_SUBMIT, urb) + // It would catch submission paths for all urbs. + + /* + * Atomically queue the urb, first to our records, then to the HCD. + * Access to urb->status is controlled by urb->lock ... changes on + * i/o completion (normal or fault) or unlinking. + */ + + // FIXME: verify that quiescing hc works right (RH cleans up) + + spin_lock_irqsave (&hcd_data_lock, flags); + if (HCD_IS_RUNNING (hcd->state) && hcd->state != USB_STATE_QUIESCING) { + usb_inc_dev_use (urb->dev); + list_add (&urb->urb_list, &dev->urb_list); + status = 0; + } else { + INIT_LIST_HEAD (&urb->urb_list); + status = -ESHUTDOWN; + } + spin_unlock_irqrestore (&hcd_data_lock, flags); + + 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; +} + +/*-------------------------------------------------------------------------*/ + +/* called in any context */ +static int hcd_get_frame_number (struct usb_device *udev) +{ + struct usb_hcd *hcd = (struct usb_hcd *)udev->bus->hcpriv; + return hcd->driver->get_frame_number (hcd); +} + +/*-------------------------------------------------------------------------*/ + +struct completion_splice { // modified urb context: + /* did we complete? */ + int done; + + /* original urb data */ + void (*complete)(struct urb *); + void *context; +}; + +static void unlink_complete (struct urb *urb) +{ + struct completion_splice *splice; + + splice = (struct completion_splice *) urb->context; + + /* issue original completion call */ + urb->complete = splice->complete; + urb->context = splice->context; + urb->complete (urb); + + splice->done = 1; +} + +/* + * called in any context; note ASYNC_UNLINK restrictions + * + * caller guarantees urb won't be recycled till both unlink() + * and the urb's completion function return + */ +static int hcd_unlink_urb (struct urb *urb) +{ + struct hcd_dev *dev; + struct usb_hcd *hcd = 0; + unsigned long flags; + struct completion_splice splice; + int retval; + + 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. + */ + spin_lock_irqsave (&urb->lock, flags); + if (!urb->hcpriv + || urb->status != -EINPROGRESS + || urb->transfer_flags & USB_TIMEOUT_KILLED) { + retval = -EINVAL; + goto done; + } + + if (!urb->dev || !urb->dev->bus) { + retval = -ENODEV; + goto done; + } + dev = urb->dev->hcpriv; + hcd = urb->dev->bus->hcpriv; + if (!dev || !hcd) { + retval = -ENODEV; + goto done; + } + + /* maybe set up to block on completion notification */ + if ((urb->transfer_flags & USB_TIMEOUT_KILLED)) + urb->status = -ETIMEDOUT; + else if (!(urb->transfer_flags & USB_ASYNC_UNLINK)) { + if (in_interrupt ()) { + dbg ("non-async unlink in_interrupt"); + retval = -EWOULDBLOCK; + goto done; + } + /* synchronous unlink: block till we see the completion */ + splice.done = 0; + splice.complete = urb->complete; + splice.context = urb->context; + urb->complete = unlink_complete; + urb->context = &splice; + urb->status = -ENOENT; + } else { + /* asynchronous unlink */ + urb->status = -ECONNRESET; + } + spin_unlock_irqrestore (&urb->lock, flags); + + if (urb == (struct urb *) hcd->rh_timer.data) { + rh_status_dequeue (hcd, urb); + retval = 0; + } else { + retval = hcd->driver->urb_dequeue (hcd, urb); +// FIXME: if retval and we tried to splice, whoa!! +if (retval && urb->status == -ENOENT) err ("whoa! retval %d", retval); + } + + /* block till giveback, if needed */ + 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); + } + } else if ((urb->transfer_flags & USB_ASYNC_UNLINK) && retval == 0) { + return -EINPROGRESS; + } + goto bye; +done: + spin_unlock_irqrestore (&urb->lock, flags); +bye: + if (retval) + dbg ("%s: hcd_unlink_urb fail %d", + hcd ? hcd->bus_name : "(no bus?)", + retval); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* called by khubd, rmmod, apmd, or other thread for hcd-private cleanup */ + +// FIXME: likely best to have explicit per-setting (config+alt) +// setup primitives in the usbcore-to-hcd driver API, so nothing +// is implicit. kernel 2.5 needs a bunch of config cleanup... + +static int hcd_free_dev (struct usb_device *udev) +{ + struct hcd_dev *dev; + struct usb_hcd *hcd; + unsigned long flags; + + if (!udev || !udev->hcpriv) + return -EINVAL; + + if (!udev->bus || !udev->bus->hcpriv) + return -ENODEV; + + // should udev->devnum == -1 ?? + + dev = udev->hcpriv; + hcd = udev->bus->hcpriv; + + /* device driver problem with refcounts? */ + if (!list_empty (&dev->urb_list)) { + dbg ("free busy dev, %s devnum %d (bug!)", + hcd->bus_name, udev->devnum); + return -EINVAL; + } + + hcd->driver->free_config (hcd, udev); + + spin_lock_irqsave (&hcd_data_lock, flags); + list_del (&dev->dev_list); + udev->hcpriv = NULL; + spin_unlock_irqrestore (&hcd_data_lock, flags); + + kfree (dev); + return 0; +} + +static struct usb_operations hcd_operations = { + allocate: hcd_alloc_dev, + get_frame_number: hcd_get_frame_number, + submit_urb: hcd_submit_urb, + unlink_urb: hcd_unlink_urb, + deallocate: hcd_free_dev, +}; + +/*-------------------------------------------------------------------------*/ + +static void hcd_irq (int irq, void *__hcd, struct pt_regs * r) +{ + struct usb_hcd *hcd = __hcd; + int start = hcd->state; + + hcd->driver->irq (hcd); + if (hcd->state != start && hcd->state == USB_STATE_HALT) + hc_died (hcd); +} + +/*-------------------------------------------------------------------------*/ + +/** + * 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. + * + * This hands the URB from HCD to its USB device driver, using its + * completion function. The HCD has freed all per-urb resources + * (and is done using urb->hcpriv). It also released all HCD locks; + * the device driver won't cause deadlocks if it resubmits this URB, + * and won't confuse things by modifying and resubmitting this one. + * Bandwidth and other resources will be deallocated. + * + * HCDs must not use this for periodic URBs that are still scheduled + * 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). + */ +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); + + // NOTE: a generic device/urb monitoring hook would go here. + // hcd_monitor_hook(MONITOR_URB_FINISH, urb, dev) + // It would catch exit/unlink paths for all urbs, but non-exit + // completions for periodic urbs need hooks inside the HCD. + // 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. + */ + } + + /* 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 --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/hcd.h Mon Feb 25 16:54:37 2002 @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2001 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +/*-------------------------------------------------------------------------*/ + +/* + * USB Host Controller Driver (usb_hcd) framework + * + * Since "struct usb_bus" is so thin, you can't share much code in it. + * This framework is a layer over that, and should be more sharable. + */ + +/*-------------------------------------------------------------------------*/ + +struct usb_hcd { /* usb_bus.hcpriv points to this */ + + /* + * housekeeping + */ + struct usb_bus *bus; /* hcd is-a bus */ + struct list_head hcd_list; + + const char *bus_name; + + const char *description; /* "ehci-hcd" etc */ + + struct timer_list rh_timer; /* drives root hub */ + struct list_head dev_list; /* devices on this bus */ + + /* + * hardware info/state + */ + struct hc_driver *driver; /* hw-specific hooks */ + int irq; /* irq allocated */ + void *regs; /* device memory/io */ + +#ifdef CONFIG_PCI + /* a few non-PCI controllers exist, mostly for OHCI */ + struct pci_dev *pdev; /* pci is typical */ + int region; /* pci region for regs */ + u32 pci_state [16]; /* for PM state save */ + atomic_t resume_count; /* multiple resumes issue */ +#endif + + int state; +# define __ACTIVE 0x01 +# define __SLEEPY 0x02 +# define __SUSPEND 0x04 +# define __TRANSIENT 0x80 + +# define USB_STATE_HALT 0 +# define USB_STATE_RUNNING (__ACTIVE) +# define USB_STATE_READY (__ACTIVE|__SLEEPY) +# define USB_STATE_QUIESCING (__SUSPEND|__TRANSIENT|__ACTIVE) +# define USB_STATE_RESUMING (__SUSPEND|__TRANSIENT) +# define USB_STATE_SUSPENDED (__SUSPEND) + +#define HCD_IS_RUNNING(state) ((state) & __ACTIVE) +#define HCD_IS_SUSPENDED(state) ((state) & __SUSPEND) + + /* more shared queuing code would be good; it should support + * smarter scheduling, handle transaction translators, etc; + * input size of periodic table to an interrupt scheduler. + * (ohci 32, uhci 1024, ehci 256/512/1024). + */ +}; + +struct hcd_dev { /* usb_device.hcpriv points to this */ + struct list_head dev_list; /* on this hcd */ + struct list_head urb_list; /* pending on this dev */ + + /* per-configuration HC/HCD state, such as QH or ED */ + void *ep[32]; +}; + +// urb.hcpriv is really hardware-specific + +struct hcd_timeout { /* timeouts we allocate */ + struct list_head timeout_list; + struct timer_list timer; +}; + +/*-------------------------------------------------------------------------*/ + +/* each driver provides one of these, and hardware init support */ + +struct hc_driver { + const char *description; /* "ehci-hcd" etc */ + + /* irq handler */ + void (*irq) (struct usb_hcd *hcd); + + int flags; +#define HCD_MEMORY 0x0001 /* HC regs use memory (else I/O) */ +#define HCD_USB11 0x0010 /* USB 1.1 */ +#define HCD_USB2 0x0020 /* USB 2.0 */ + + /* called to init HCD and root hub */ + int (*start) (struct usb_hcd *hcd); + + /* called after all devices were suspended */ + int (*suspend) (struct usb_hcd *hcd, u32 state); + + /* called before any devices get resumed */ + int (*resume) (struct usb_hcd *hcd); + + /* cleanly make HCD stop writing memory and doing I/O */ + void (*stop) (struct usb_hcd *hcd); + + /* return current frame number */ + int (*get_frame_number) (struct usb_hcd *hcd); + +// FIXME: rework generic-to-specific HCD linkage (specific contains generic) + + /* memory lifecycle */ + struct usb_hcd *(*hcd_alloc) (void); + void (*hcd_free) (struct usb_hcd *hcd); + + /* manage i/o requests, device state */ + int (*urb_enqueue) (struct usb_hcd *hcd, struct urb *urb, + int mem_flags); + int (*urb_dequeue) (struct usb_hcd *hcd, struct urb *urb); + + // frees configuration resources -- allocated as needed during + // urb_enqueue, and not freed by urb_dequeue + void (*free_config) (struct usb_hcd *hcd, + struct usb_device *dev); + + /* root hub support */ + int (*hub_status_data) (struct usb_hcd *hcd, char *buf); + int (*hub_control) (struct usb_hcd *hcd, + u16 typeReq, u16 wValue, u16 wIndex, + char *buf, u16 wLength); +}; + +extern void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb); + +#ifdef CONFIG_PCI + +extern int usb_hcd_pci_probe (struct pci_dev *dev, + const struct pci_device_id *id); +extern void usb_hcd_pci_remove (struct pci_dev *dev); + +#ifdef CONFIG_PM +// FIXME: see Documentation/power/pci.txt (2.4.6 and later?) +// extern int usb_hcd_pci_save_state (struct pci_dev *dev, u32 state); +extern int usb_hcd_pci_suspend (struct pci_dev *dev, u32 state); +extern int usb_hcd_pci_resume (struct pci_dev *dev); +// extern int usb_hcd_pci_enable_wake (struct pci_dev *dev, u32 state, int flg); +#endif /* CONFIG_PM */ + +#endif /* CONFIG_PCI */ + +/*-------------------------------------------------------------------------*/ + +/* + * HCD Root Hub support + */ + +#include "hub.h" + +/* (shifted) direction/type/recipient from the USB 2.0 spec, table 9.2 */ +#define DeviceRequest \ + ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8) +#define DeviceOutRequest \ + ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8) + +#define InterfaceRequest \ + ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) + +#define EndpointRequest \ + ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) +#define EndpointOutRequest \ + ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) + +/* table 9.6 standard features */ +#define DEVICE_REMOTE_WAKEUP 1 +#define ENDPOINT_HALT 0 + +/* class requests from the USB 2.0 hub spec, table 11-15 */ +/* GetBusState and SetHubDescriptor are optional, omitted */ +#define ClearHubFeature (0x2000 | USB_REQ_CLEAR_FEATURE) +#define ClearPortFeature (0x2300 | USB_REQ_CLEAR_FEATURE) +#define GetHubDescriptor (0xa000 | USB_REQ_GET_DESCRIPTOR) +#define GetHubStatus (0xa000 | USB_REQ_GET_STATUS) +#define GetPortStatus (0xa300 | USB_REQ_GET_STATUS) +#define SetHubFeature (0x2000 | USB_REQ_SET_FEATURE) +#define SetPortFeature (0x2300 | USB_REQ_SET_FEATURE) + + +/*-------------------------------------------------------------------------*/ + +/* hub.h ... DeviceRemovable in 2.4.2-ac11, gone in 2.4.10 */ +// bleech -- resurfaced in 2.4.11 or 2.4.12 +#define bitmap DeviceRemovable + + +/*-------------------------------------------------------------------------*/ + +/* random stuff */ + +#define RUN_CONTEXT (in_irq () ? "in_irq" \ + : (in_interrupt () ? "in_interrupt" : "can sleep")) diff -Nru a/drivers/usb/hub.c b/drivers/usb/hub.c --- a/drivers/usb/hub.c Mon Feb 25 16:54:34 2002 +++ b/drivers/usb/hub.c Mon Feb 25 16:54:34 2002 @@ -217,9 +217,12 @@ break; case 1: dbg("Single TT"); + hub->tt.hub = dev; break; case 2: - dbg("Multiple TT"); + dbg("TT per port"); + hub->tt.hub = dev; + hub->tt.multi = 1; break; default: dbg("Unrecognized hub protocol %d", @@ -496,6 +499,29 @@ err("cannot disconnect hub %d", dev->devnum); } +static int usb_hub_port_status(struct usb_device *hub, int port, + u16 *status, u16 *change) +{ + struct usb_port_status *portsts; + int ret = -ENOMEM; + + portsts = kmalloc(sizeof(*portsts), GFP_KERNEL); + if (portsts) { + ret = usb_get_port_status(hub, port + 1, portsts); + if (ret < 0) + err("%s (%d) failed (err = %d)", __FUNCTION__, hub->devnum, ret); + else { + *status = le16_to_cpu(portsts->wPortStatus); + *change = le16_to_cpu(portsts->wPortChange); + dbg("port %d, portstatus %x, change %x, %s", port + 1, + *status, *change, portspeed(*status)); + ret = 0; + } + kfree(portsts); + } + return ret; +} + #define HUB_RESET_TRIES 5 #define HUB_PROBE_TRIES 2 #define HUB_SHORT_RESET_TIME 10 @@ -507,24 +533,26 @@ struct usb_device *dev, unsigned int delay) { int delay_time, ret; - struct usb_port_status portsts; - unsigned short portchange, portstatus; + u16 portstatus; + u16 portchange; for (delay_time = 0; delay_time < HUB_RESET_TIMEOUT; delay_time += delay) { /* wait to give the device a chance to reset */ wait_ms(delay); /* read and decode port status */ - ret = usb_get_port_status(hub, port + 1, &portsts); + ret = usb_hub_port_status(hub, port, &portstatus, &portchange); if (ret < 0) { - err("get_port_status(%d) failed (err = %d)", port + 1, ret); return -1; } - portstatus = le16_to_cpu(portsts.wPortStatus); - portchange = le16_to_cpu(portsts.wPortChange); - dbg("port %d, portstatus %x, change %x, %s", port + 1, - portstatus, portchange, portspeed (portstatus)); + /* Device went away? */ + if (!(portstatus & USB_PORT_STAT_CONNECTION)) + return 1; + + /* Device went away? */ + if (!(portstatus & USB_PORT_STAT_CONNECTION)) + return 1; /* bomb out completely if something weird happened */ if ((portchange & USB_PORT_STAT_C_CONNECTION)) @@ -592,17 +620,58 @@ port + 1, hub->devnum, ret); } -static void usb_hub_port_connect_change(struct usb_device *hub, int port, - struct usb_port_status *portsts) +/* USB 2.0 spec, 7.1.7.3 / fig 7-29: + * + * Between connect detection and reset signaling there must be a delay + * of 100ms at least for debounce and power-settling. The corresponding + * timer shall restart whenever the downstream port detects a disconnect. + * + * Apparently there are some bluetooth and irda-dongles and a number + * of low-speed devices which require longer delays of about 200-400ms. + * Not covered by the spec - but easy to deal with. + * + * This implementation uses 400ms minimum debounce timeout and checks + * every 10ms for transient disconnects to restart the delay. + */ + +#define HUB_DEBOUNCE_TIMEOUT 400 +#define HUB_DEBOUNCE_STEP 10 + +/* return: -1 on error, 0 on success, 1 on disconnect. */ +static int usb_hub_port_debounce(struct usb_device *hub, int port) { + int ret; + unsigned delay_time; + u16 portchange, portstatus; + + for (delay_time = 0; delay_time < HUB_DEBOUNCE_TIMEOUT; /* empty */ ) { + + /* wait debounce step increment */ + wait_ms(HUB_DEBOUNCE_STEP); + + ret = usb_hub_port_status(hub, port, &portstatus, &portchange); + if (ret < 0) + return -1; + + if ((portchange & USB_PORT_STAT_C_CONNECTION)) { + usb_clear_port_feature(hub, port+1, USB_PORT_FEAT_C_CONNECTION); + delay_time = 0; + } + else + delay_time += HUB_DEBOUNCE_STEP; + } + return ((portstatus&USB_PORT_STAT_CONNECTION)) ? 0 : 1; +} + +static void usb_hub_port_connect_change(struct usb_hub *hubstate, int port, + u16 portstatus, u16 portchange) +{ + struct usb_device *hub = hubstate->dev; struct usb_device *dev; - unsigned short portstatus, portchange; unsigned int delay = HUB_SHORT_RESET_TIME; int i; char *portstr, *tempstr; - portstatus = le16_to_cpu(portsts->wPortStatus); - portchange = le16_to_cpu(portsts->wPortChange); dbg("port %d, portstatus %x, change %x, %s", port + 1, portstatus, portchange, portspeed (portstatus)); @@ -621,11 +690,10 @@ return; } - /* Some low speed devices have problems with the quick delay, so */ - /* be a bit pessimistic with those devices. RHbug #23670 */ - if (portstatus & USB_PORT_STAT_LOW_SPEED) { - wait_ms(400); - delay = HUB_LONG_RESET_TIME; + if (usb_hub_port_debounce(hub, port)) { + err("connect-debounce failed, port %d disabled", port+1); + usb_hub_port_disable(hub, port); + return; } down(&usb_address0_sem); @@ -654,6 +722,16 @@ /* Find a new device ID for it */ usb_connect(dev); + /* Set up TT records, if needed */ + if (hub->tt) { + dev->tt = hub->tt; + dev->ttport = hub->ttport; + } else if (dev->speed != USB_SPEED_HIGH + && hub->speed == USB_SPEED_HIGH) { + dev->tt = &hubstate->tt; + dev->ttport = port + 1; + } + /* Create a readable topology string */ cdev = dev; pdev = dev->parent; @@ -709,7 +787,10 @@ struct usb_device *dev; struct usb_hub *hub; struct usb_hub_status hubsts; - unsigned short hubstatus, hubchange; + u16 hubstatus; + u16 hubchange; + u16 portstatus; + u16 portchange; int i, ret; /* @@ -751,22 +832,15 @@ } for (i = 0; i < hub->descriptor->bNbrPorts; i++) { - struct usb_port_status portsts; - unsigned short portstatus, portchange; - - ret = usb_get_port_status(dev, i + 1, &portsts); + ret = usb_hub_port_status(dev, i, &portstatus, &portchange); if (ret < 0) { - err("get_port_status failed (err = %d)", ret); continue; } - portstatus = le16_to_cpu(portsts.wPortStatus); - portchange = le16_to_cpu(portsts.wPortChange); - if (portchange & USB_PORT_STAT_C_CONNECTION) { dbg("port %d connection change", i + 1); - usb_hub_port_connect_change(dev, i, &portsts); + usb_hub_port_connect_change(hub, i, portstatus, portchange); } else if (portchange & USB_PORT_STAT_C_ENABLE) { dbg("port %d enable change, status %x", i + 1, portstatus); usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_ENABLE); @@ -780,7 +854,7 @@ (portstatus & USB_PORT_STAT_CONNECTION) && (dev->children[i])) { err("already running port %i disabled by hub (EMI?), re-enabling...", i + 1); - usb_hub_port_connect_change(dev, i, &portsts); + usb_hub_port_connect_change(hub, i, portstatus, portchange); } } diff -Nru a/drivers/usb/hub.h b/drivers/usb/hub.h --- a/drivers/usb/hub.h Mon Feb 25 16:54:34 2002 +++ b/drivers/usb/hub.h Mon Feb 25 16:54:34 2002 @@ -2,6 +2,7 @@ #define __LINUX_HUB_H #include +#include /* likely()/unlikely() */ /* * Hub request types @@ -136,6 +137,7 @@ struct usb_hub_descriptor *descriptor; struct semaphore khubd_sem; + struct usb_tt tt; /* Transaction Translator */ }; #endif /* __LINUX_HUB_H */ diff -Nru a/include/linux/usb.h b/include/linux/usb.h --- a/include/linux/usb.h Mon Feb 25 16:54:34 2002 +++ b/include/linux/usb.h Mon Feb 25 16:54:34 2002 @@ -429,6 +429,8 @@ #define USB_QUEUE_BULK 0x0010 #define USB_NO_FSBR 0x0020 #define USB_ZERO_PACKET 0x0040 // Finish bulk OUTs always with zero length packet +#define URB_NO_INTERRUPT 0x0080 /* HINT: no non-error interrupt needed */ + /* ... less overhead for QUEUE_BULK */ #define USB_TIMEOUT_KILLED 0x1000 // only set by HCD! typedef struct @@ -733,6 +735,22 @@ atomic_t refcnt; }; +/* + * As of USB 2.0, full/low speed devices are segregated into trees. + * One type grows from USB 1.1 host controllers (OHCI, UHCI etc). + * The other type grows from high speed hubs when they connect to + * full/low speed devices using "Transaction Translators" (TTs). + * + * TTs should only be known to the hub driver, and high speed bus + * drivers (only EHCI for now). They affect periodic scheduling and + * sometimes control/bulk error recovery. + */ +struct usb_tt { + struct usb_device *hub; /* upstream highspeed hub */ + int multi; /* true means one TT per port */ +}; + + /* This is arbitrary. * From USB 2.0 spec Table 11-13, offset 7, a hub can * have up to 255 ports. The most yet reported is 10. @@ -748,8 +766,8 @@ USB_SPEED_HIGH /* usb 2.0 */ } speed; - struct usb_device *tt; /* usb1.1 device on usb2.0 bus */ - int ttport; /* device/hub port on that tt */ + struct usb_tt *tt; /* low/full speed dev, highspeed hub */ + int ttport; /* device port on that tt hub */ atomic_t refcnt; /* Reference count */ struct semaphore serialize;