![[LWN Logo]](/images/lcorner.png) |
|
![[LWN.net]](/images/Included.png) |
From: Greg KH <greg@kroah.com>
To: marcelo@conectiva.com.br
Subject: [linux-usb-devel] [PATCH 1 of 6] USB 2.0 support
Date: Tue, 26 Feb 2002 11:08:15 -0800
Cc: linux-usb-devel@lists.sourceforge.net
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 <file:Documentation/usb/ehci.txt>.
+
+ 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 <file:Documentation/modules.txt>.
+
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
+ <dbrownell@users.sourceforge.net>
+
+
+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 <linux/config.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/smp_lock.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+
+#ifndef CONFIG_USB_DEBUG
+ #define CONFIG_USB_DEBUG /* this is still experimental! */
+#endif
+
+#ifdef CONFIG_USB_DEBUG
+ #define DEBUG
+#else
+ #undef DEBUG
+#endif
+
+#include <linux/usb.h>
+#include "../hcd.h"
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+#include <asm/unaligned.h>
+
+//#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 <linux/config.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/smp_lock.h>
+#include <linux/errno.h>
+#include <linux/kmod.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/uts.h> /* 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 <linux/usb.h>
+#include "hcd.h"
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+#include <asm/unaligned.h>
+
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * 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 <linux/list.h>
+#include <linux/compiler.h> /* 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;
_______________________________________________
linux-usb-devel@lists.sourceforge.net
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel