hcd.c
上传用户:jlfgdled
上传日期:2013-04-10
资源大小:33168k
文件大小:40k
- /*
- * Copyright (c) 2001-2002 by David Brownell
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * 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/completion.h>
- #include <linux/uts.h> /* for UTS_SYSNAME */
- #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. Think of it as the "upper level" of
- * some drivers, where the "lower level" is hardware-specific.
- *
- * 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:
- * 2002-sept Merge some 2.5 updates so we can share hardware level HCD
- * code between the 2.4.20+ and 2.5 trees.
- * 2002-feb merge to 2.4.19
- * 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.
- */
- /*-------------------------------------------------------------------------*/
- #define KERNEL_REL ((LINUX_VERSION_CODE >> 16) & 0x0ff)
- #define KERNEL_VER ((LINUX_VERSION_CODE >> 8) & 0x0ff)
- /* usb 2.0 root hub device descriptor */
- static const u8 usb2_rh_dev_descriptor [18] = {
- 0x12, /* __u8 bLength; */
- 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; */
- KERNEL_VER, KERNEL_REL, /* __u16 bcdDevice */
- 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; */
- KERNEL_VER, KERNEL_REL, /* __u16 bcdDevice */
- 0x03, /* __u8 iManufacturer; */
- 0x02, /* __u8 iProduct; */
- 0x01, /* __u8 iSerialNumber; */
- 0x01 /* __u8 bNumConfigurations; */
- };
- /*-------------------------------------------------------------------------*/
- /* Configuration descriptors for our root hubs */
- static const u8 fs_rh_config_descriptor [] = {
- /* one configuration */
- 0x09, /* __u8 bLength; */
- 0x02, /* __u8 bDescriptorType; Configuration */
- 0x19, 0x00, /* __u16 wTotalLength; */
- 0x01, /* __u8 bNumInterfaces; (1) */
- 0x01, /* __u8 bConfigurationValue; */
- 0x00, /* __u8 iConfiguration; */
- 0x40, /* __u8 bmAttributes;
- Bit 7: Bus-powered,
- 6: Self-powered,
- 5 Remote-wakwup,
- 4..0: resvd */
- 0x00, /* __u8 MaxPower; */
-
- /* USB 1.1:
- * USB 2.0, single TT organization (mandatory):
- * one interface, protocol 0
- *
- * USB 2.0, multiple TT organization (optional):
- * two interfaces, protocols 1 (like single TT)
- * and 2 (multiple TT mode) ... config is
- * sometimes settable
- * NOT IMPLEMENTED
- */
- /* one interface */
- 0x09, /* __u8 if_bLength; */
- 0x04, /* __u8 if_bDescriptorType; Interface */
- 0x00, /* __u8 if_bInterfaceNumber; */
- 0x00, /* __u8 if_bAlternateSetting; */
- 0x01, /* __u8 if_bNumEndpoints; */
- 0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */
- 0x00, /* __u8 if_bInterfaceSubClass; */
- 0x00, /* __u8 if_bInterfaceProtocol; [usb1.1 or single tt] */
- 0x00, /* __u8 if_iInterface; */
-
- /* one endpoint (status change endpoint) */
- 0x07, /* __u8 ep_bLength; */
- 0x05, /* __u8 ep_bDescriptorType; Endpoint */
- 0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */
- 0x03, /* __u8 ep_bmAttributes; Interrupt */
- 0x02, 0x00, /* __u16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) */
- 0xff /* __u8 ep_bInterval; (255ms -- usb 2.0 spec) */
- };
- static const u8 hs_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; (256ms -- 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 *s, u8 *utf, int utfmax)
- {
- int retval;
- for (retval = 0; *s && utfmax > 1; utfmax -= 2, retval += 2) {
- *utf++ = *s++;
- *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 usb_hcd *hcd,
- 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, hcd->bus_name);
- // product description
- } else if (id == 2) {
- strcpy (buf, hcd->product_desc);
- // id 3 == vendor description
- } else if (id == 3) {
- sprintf (buf, "%s %s %s", UTS_SYSNAME, UTS_RELEASE,
- hcd->description);
- // 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)
- {
- struct usb_ctrlrequest *cmd = (struct usb_ctrlrequest *) urb->setup_packet;
- u16 typeReq, wValue, wIndex, wLength;
- const u8 *bufp = 0;
- u8 *ubuf = urb->transfer_buffer;
- int len = 0;
- typeReq = (cmd->bRequestType << 8) | cmd->bRequest;
- wValue = le16_to_cpu (cmd->wValue);
- wIndex = le16_to_cpu (cmd->wIndex);
- wLength = le16_to_cpu (cmd->wLength);
- 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:
- if (hcd->driver->flags & HCD_USB2) {
- bufp = hs_rh_config_descriptor;
- len = sizeof hs_rh_config_descriptor;
- } else {
- bufp = fs_rh_config_descriptor;
- len = sizeof fs_rh_config_descriptor;
- }
- break;
- case USB_DT_STRING << 8:
- urb->actual_length = rh_string (
- wValue & 0xff, hcd,
- 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;
- /* USB 2.0 spec says 256msec; this is close enough */
- hcd->rh_timer.expires = jiffies + HZ/4;
- 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
- * Context: !in_interrupt()
- *
- * 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;
- 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;
- }
- }
- pci_set_drvdata(dev, hcd);
- hcd->driver = driver;
- hcd->description = driver->description;
- hcd->pdev = dev;
- info ("%s @ %s, %s", hcd->description, dev->slot_name, dev->name);
- #ifndef __sparc__
- sprintf (buf, "%d", dev->irq);
- #else
- bufp = __irq_itoa(dev->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; /* prefer bus->bus_name */
- bus->bus_name = dev->slot_name;
- hcd->product_desc = dev->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
- * Context: !in_interrupt()
- *
- * Reverses the effect of usb_hcd_pci_probe(), first invoking
- * the HCD's stop() method. It is always called from a thread
- * 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 = pci_get_drvdata(dev);
- 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 = pci_get_drvdata(dev);
- 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 = pci_get_drvdata(dev);
- 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);
- }
- /*-------------------------------------------------------------------------*/
- static void urb_unlink (struct urb *urb)
- {
- unsigned long flags;
- struct usb_device *dev;
- /* Release any periodic transfer bandwidth */
- if (urb->bandwidth)
- usb_release_bandwidth (urb->dev, urb,
- usb_pipeisoc (urb->pipe));
- /* clear all state linking urb to this dev (and hcd) */
- spin_lock_irqsave (&hcd_data_lock, flags);
- list_del_init (&urb->urb_list);
- dev = urb->dev;
- urb->dev = NULL;
- usb_dec_dev_use (dev);
- spin_unlock_irqrestore (&hcd_data_lock, flags);
- }
- /* may be called in any context with a valid urb->dev usecount */
- /* caller surrenders "ownership" of urb */
- static int hcd_submit_urb (struct urb *urb)
- {
- int status;
- struct usb_hcd *hcd;
- struct hcd_dev *dev;
- unsigned long flags;
- int pipe, temp, max;
- int mem_flags;
- if (!urb || urb->hcpriv || !urb->complete)
- return -EINVAL;
- urb->status = -EINPROGRESS;
- urb->actual_length = 0;
- urb->bandwidth = 0;
- INIT_LIST_HEAD (&urb->urb_list);
- if (!urb->dev || !urb->dev->bus || urb->dev->devnum <= 0)
- 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;
- temp = usb_pipetype (urb->pipe);
- if (usb_endpoint_halted (urb->dev, usb_pipeendpoint (pipe),
- usb_pipeout (pipe)))
- return -EPIPE;
- /* NOTE: 2.5 passes this value explicitly in submit() */
- mem_flags = in_interrupt () ? GFP_ATOMIC : GFP_KERNEL;
- /* FIXME there should be a sharable lock protecting us against
- * config/altsetting changes and disconnects, kicking in here.
- */
- /* Sanity check, so HCDs can rely on clean data */
- max = usb_maxpacket (urb->dev, pipe, usb_pipeout (pipe));
- if (max <= 0) {
- err ("bogus endpoint (bad maxpacket)");
- return -EINVAL;
- }
- /* "high bandwidth" mode, 1-3 packets/uframe? */
- if (urb->dev->speed == USB_SPEED_HIGH) {
- int mult;
- switch (temp) {
- case PIPE_ISOCHRONOUS:
- case PIPE_INTERRUPT:
- mult = 1 + ((max >> 11) & 0x03);
- max &= 0x03ff;
- max *= mult;
- }
- }
- /* periodic transfers limit size per frame/uframe */
- switch (temp) {
- case PIPE_ISOCHRONOUS: {
- int n, len;
- if (urb->number_of_packets <= 0)
- return -EINVAL;
- for (n = 0; n < urb->number_of_packets; n++) {
- len = urb->iso_frame_desc [n].length;
- if (len < 0 || len > max)
- return -EINVAL;
- }
- }
- break;
- case PIPE_INTERRUPT:
- if (urb->transfer_buffer_length > max)
- return -EINVAL;
- }
- /* the I/O buffer must usually be mapped/unmapped */
- if (urb->transfer_buffer_length < 0)
- return -EINVAL;
- // FIXME set urb->transfer_dma and/or setup_dma
- if (urb->next) {
- warn ("use explicit queuing not urb->next");
- return -EINVAL;
- }
- #ifdef DEBUG
- /* stuff that drivers shouldn't do, but which shouldn't
- * cause problems in HCDs if they get it wrong.
- */
- {
- unsigned int orig_flags = urb->transfer_flags;
- unsigned int allowed;
- /* enforce simple/standard policy */
- allowed = USB_ASYNC_UNLINK; // affects later unlinks
- allowed |= USB_NO_FSBR; // only affects UHCI
- switch (temp) {
- 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;
- /* fail if submitter gave bogus flags */
- if (urb->transfer_flags != orig_flags) {
- err ("BOGUS urb flags, %x --> %x",
- orig_flags, urb->transfer_flags);
- return -EINVAL;
- }
- }
- #endif
- /*
- * Force periodic transfer intervals to be legal values that are
- * a power of two (so HCDs don't need to).
- *
- * FIXME want bus->{intr,iso}_sched_horizon values here. Each HC
- * supports different values... this uses EHCI/UHCI defaults (and
- * EHCI can use smaller non-default values).
- */
- switch (temp) {
- case PIPE_ISOCHRONOUS:
- case PIPE_INTERRUPT:
- /* too small? */
- if (urb->interval <= 0)
- return -EINVAL;
- /* too big? */
- switch (urb->dev->speed) {
- case USB_SPEED_HIGH: /* units are microframes */
- // NOTE usb handles 2^15
- if (urb->interval > (1024 * 8))
- urb->interval = 1024 * 8;
- temp = 1024 * 8;
- break;
- case USB_SPEED_FULL: /* units are frames/msec */
- case USB_SPEED_LOW:
- if (temp == PIPE_INTERRUPT) {
- if (urb->interval > 255)
- return -EINVAL;
- // NOTE ohci only handles up to 32
- temp = 128;
- } else {
- if (urb->interval > 1024)
- urb->interval = 1024;
- // NOTE usb and ohci handle up to 2^15
- temp = 1024;
- }
- break;
- default:
- return -EINVAL;
- }
- /* power of two? */
- while (temp > urb->interval)
- temp >>= 1;
- urb->interval = temp;
- }
- /*
- * FIXME: make urb timeouts be generic, keeping the HCD cores
- * as simple as possible.
- */
- // 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)
- return status;
- if (urb->dev == hcd->bus->root_hub)
- status = rh_urb_enqueue (hcd, urb);
- else
- status = hcd->driver->urb_enqueue (hcd, urb, mem_flags);
- /* urb->dev got nulled if hcd called giveback for us
- * NOTE: ref to urb->dev is a race without (2.5) refcounting,
- * unless driver only returns status when it didn't giveback
- */
- if (status && urb->dev)
- urb_unlink (urb);
- return status;
- }
- /*-------------------------------------------------------------------------*/
- /* 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? */
- struct completion 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);
- /* then let the synchronous unlink call complete */
- complete (&splice->done);
- }
- /*
- * 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;
- /*
- * we contend for urb->status with the hcd core,
- * which changes it while returning the urb.
- *
- * Caller guaranteed that the urb pointer hasn't been freed, and
- * that it was submitted. But as a rule it can't know whether or
- * not it's already been unlinked ... so we respect the reversed
- * lock sequence needed for the usb_hcd_giveback_urb() code paths
- * (urb lock, then hcd_data_lock) in case some other CPU is now
- * unlinking it.
- */
- spin_lock_irqsave (&urb->lock, flags);
- spin_lock (&hcd_data_lock);
- if (!urb->hcpriv || urb->transfer_flags & USB_TIMEOUT_KILLED) {
- retval = -EINVAL;
- goto done;
- }
- if (!urb->dev || !urb->dev->bus) {
- retval = -ENODEV;
- goto done;
- }
- /* giveback clears dev; non-null means it's linked at this level */
- dev = urb->dev->hcpriv;
- hcd = urb->dev->bus->hcpriv;
- if (!dev || !hcd) {
- retval = -ENODEV;
- goto done;
- }
- /* For non-periodic transfers, any status except -EINPROGRESS means
- * the HCD has already started to unlink this URB from the hardware.
- * In that case, there's no more work to do.
- *
- * For periodic transfers, this is the only way to trigger unlinking
- * from the hardware. Since we (currently) overload urb->status to
- * tell the driver to unlink, error status might get clobbered ...
- * unless that transfer hasn't yet restarted. One such case is when
- * the URB gets unlinked from its completion handler.
- *
- * FIXME use an URB_UNLINKED flag to match URB_TIMEOUT_KILLED
- */
- switch (usb_pipetype (urb->pipe)) {
- case PIPE_CONTROL:
- case PIPE_BULK:
- if (urb->status != -EINPROGRESS) {
- retval = -EINVAL;
- goto done;
- }
- }
- /* maybe set up to block on completion notification */
- if ((urb->transfer_flags & USB_TIMEOUT_KILLED))
- urb->status = -ETIMEDOUT;
- 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 */
- init_completion (&splice.done);
- 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 (&hcd_data_lock);
- 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) {
- dbg ("%s: wait for giveback urb %p",
- hcd->bus_name, urb);
- wait_for_completion (&splice.done);
- } else if ((urb->transfer_flags & USB_ASYNC_UNLINK) && retval == 0) {
- return -EINPROGRESS;
- }
- goto bye;
- done:
- spin_unlock (&hcd_data_lock);
- spin_unlock_irqrestore (&urb->lock, flags);
- bye:
- if (retval)
- 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;
- if (unlikely (hcd->state == USB_STATE_HALT)) /* irq sharing? */
- return;
- hcd->driver->irq (hcd);
- if (hcd->state != start && hcd->state == USB_STATE_HALT)
- hc_died (hcd);
- }
- /*-------------------------------------------------------------------------*/
- /**
- * usb_hcd_giveback_urb - return URB from HCD to device driver
- * @hcd: host controller returning the URB
- * @urb: urb being returned to the USB device driver.
- * Context: in_interrupt()
- *
- * This hands the URB from HCD to its USB device driver, using its
- * completion function. The HCD has freed all per-urb resources
- * (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.
- *
- * NOTE that no urb->next processing is done, even for isochronous URBs.
- * ISO streaming functionality can be achieved by having completion handlers
- * re-queue URBs. Such explicit queuing doesn't discard error reports.
- */
- void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb)
- {
- urb_unlink (urb);
- // 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 len %d",
- urb, urb->status, urb->actual_length);
- // FIXME unmap urb->transfer_dma and/or setup_dma
- /* pass ownership to the completion handler */
- urb->complete (urb);
- }
- EXPORT_SYMBOL (usb_hcd_giveback_urb);