| /* |
| * 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> |
| #include <asm/byteorder.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) |
| { |
| 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: |
| 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; |
| } |
| } |
| 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); |
| |
| 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(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; |
| 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 = 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); |
| } |
| |
| /*-------------------------------------------------------------------------*/ |
| |
| /* 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 mem_flags) |
| { |
| int status; |
| struct usb_hcd *hcd; |
| struct hcd_dev *dev; |
| unsigned long flags; |
| int pipe; |
| |
| 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; |
| |
| #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. |
| |
| /* increment the reference count of the urb, as we now also control it. */ |
| urb = usb_get_urb(urb); |
| |
| /* |
| * 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; |
| |
| /* |
| * 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 = 0; |
| 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 (&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) { |
| 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 (&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; |
| |
| 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, GFP_ATOMIC); |
| 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); |
| usb_put_urb (urb); |
| } |
| EXPORT_SYMBOL (usb_hcd_giveback_urb); |