📄 hcd.c
字号:
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; 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; 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);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -