📄 ehci-hcd.c
字号:
/* no more interrupts ... */ if (hcd->state == USB_STATE_RUNNING) ehci_ready (ehci); if (in_interrupt ()) { /* must not happen!! */ ehci_err (ehci, "stopped in_interrupt!\n"); return; } del_timer_sync (&ehci->watchdog); ehci_reset (ehci); /* let companion controllers work when we aren't */ writel (0, &ehci->regs->configured_flag); unregister_reboot_notifier (&ehci->reboot_notifier); remove_debug_files (ehci); /* root hub is shut down separately (first, when possible) */ spin_lock_irq (&ehci->lock); ehci_work (ehci, NULL); spin_unlock_irq (&ehci->lock); ehci_mem_cleanup (ehci);#ifdef EHCI_STATS ehci_dbg (ehci, "irq normal %ld err %ld reclaim %ld (lost %ld)\n", ehci->stats.normal, ehci->stats.error, ehci->stats.reclaim, ehci->stats.lost_iaa); ehci_dbg (ehci, "complete %ld unlink %ld\n", ehci->stats.complete, ehci->stats.unlink);#endif dbg_status (ehci, "ehci_stop completed", readl (&ehci->regs->status));}static int ehci_get_frame (struct usb_hcd *hcd){ struct ehci_hcd *ehci = hcd_to_ehci (hcd); return (readl (&ehci->regs->frame_index) >> 3) % ehci->periodic_size;}/*-------------------------------------------------------------------------*/#ifdef CONFIG_PM/* suspend/resume, section 4.3 */static int ehci_suspend (struct usb_hcd *hcd, u32 state){ struct ehci_hcd *ehci = hcd_to_ehci (hcd); int ports; int i; ehci_dbg (ehci, "suspend to %d\n", state); ports = HCS_N_PORTS (ehci->hcs_params); // FIXME: This assumes what's probably a D3 level suspend... // FIXME: usb wakeup events on this bus should resume the machine. // pci config register PORTWAKECAP controls which ports can do it; // bios may have initted the register... /* suspend each port, then stop the hc */ for (i = 0; i < ports; i++) { int temp = readl (&ehci->regs->port_status [i]); if ((temp & PORT_PE) == 0 || (temp & PORT_OWNER) != 0) continue; ehci_dbg (ehci, "suspend port %d", i); temp |= PORT_SUSPEND; writel (temp, &ehci->regs->port_status [i]); } if (hcd->state == USB_STATE_RUNNING) ehci_ready (ehci); writel (readl (&ehci->regs->command) & ~CMD_RUN, &ehci->regs->command);// save pci FLADJ value /* who tells PCI to reduce power consumption? */ return 0;}static int ehci_resume (struct usb_hcd *hcd){ struct ehci_hcd *ehci = hcd_to_ehci (hcd); int ports; int i; ehci_dbg (ehci, "resume\n"); ports = HCS_N_PORTS (ehci->hcs_params); // FIXME: if controller didn't retain state, // return and let generic code clean it up // test configured_flag ? /* resume HC and each port */// restore pci FLADJ value // khubd and drivers will set HC running, if needed; hcd->state = USB_STATE_READY; // FIXME Philips/Intel/... etc don't really have a "READY" // state ... turn on CMD_RUN too for (i = 0; i < ports; i++) { int temp = readl (&ehci->regs->port_status [i]); if ((temp & PORT_PE) == 0 || (temp & PORT_SUSPEND) != 0) continue; ehci_dbg (ehci, "resume port %d", i); temp |= PORT_RESUME; writel (temp, &ehci->regs->port_status [i]); readl (&ehci->regs->command); /* unblock posted writes */ wait_ms (20); temp &= ~PORT_RESUME; writel (temp, &ehci->regs->port_status [i]); } readl (&ehci->regs->command); /* unblock posted writes */ return 0;}#endif/*-------------------------------------------------------------------------*//* * ehci_work is called from some interrupts, timers, and so on. * it calls driver completion functions, after dropping ehci->lock. */static void ehci_work (struct ehci_hcd *ehci, struct pt_regs *regs){ if (ehci->reclaim_ready) end_unlink_async (ehci, regs); scan_async (ehci, regs); if (ehci->next_uframe != -1) scan_periodic (ehci, regs);}/*-------------------------------------------------------------------------*/static void ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs){ struct ehci_hcd *ehci = hcd_to_ehci (hcd); u32 status; int bh; spin_lock (&ehci->lock); status = readl (&ehci->regs->status); /* e.g. cardbus physical eject */ if (status == ~(u32) 0) { ehci_dbg (ehci, "device removed\n"); goto dead; } status &= INTR_MASK; if (!status) /* irq sharing? */ goto done; /* clear (just) interrupts */ writel (status, &ehci->regs->status); readl (&ehci->regs->command); /* unblock posted write */ bh = 0;#ifdef EHCI_VERBOSE_DEBUG /* unrequested/ignored: Port Change Detect, Frame List Rollover */ dbg_status (ehci, "irq", status);#endif /* INT, ERR, and IAA interrupt rates can be throttled */ /* normal [4.15.1.2] or error [4.15.1.1] completion */ if (likely ((status & (STS_INT|STS_ERR)) != 0)) { if (likely ((status & STS_ERR) == 0)) COUNT (ehci->stats.normal); else COUNT (ehci->stats.error); bh = 1; } /* complete the unlinking of some qh [4.15.2.3] */ if (status & STS_IAA) { COUNT (ehci->stats.reclaim); ehci->reclaim_ready = 1; bh = 1; } /* PCI errors [4.15.2.4] */ if (unlikely ((status & STS_FATAL) != 0)) { ehci_err (ehci, "fatal error\n");dead: ehci_reset (ehci); /* generic layer kills/unlinks all urbs, then * uses ehci_stop to clean up the rest */ bh = 1; } if (bh) ehci_work (ehci, regs);done: spin_unlock (&ehci->lock);}/*-------------------------------------------------------------------------*//* * non-error returns are a promise to giveback() the urb later * we drop ownership so next owner (or urb unlink) can get it * * urb + dev is in hcd_dev.urb_list * we're queueing TDs onto software and hardware lists * * hcd-specific init for hcpriv hasn't been done yet * * NOTE: control, bulk, and interrupt share the same code to append TDs * to a (possibly active) QH, and the same QH scanning code. */static int ehci_urb_enqueue ( struct usb_hcd *hcd, struct urb *urb, int mem_flags) { struct ehci_hcd *ehci = hcd_to_ehci (hcd); struct list_head qtd_list; urb->transfer_flags &= ~EHCI_STATE_UNLINK; INIT_LIST_HEAD (&qtd_list); switch (usb_pipetype (urb->pipe)) { // case PIPE_CONTROL: // case PIPE_BULK: default: if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) return -ENOMEM; return submit_async (ehci, urb, &qtd_list, mem_flags); case PIPE_INTERRUPT: if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) return -ENOMEM; return intr_submit (ehci, urb, &qtd_list, mem_flags); case PIPE_ISOCHRONOUS: if (urb->dev->speed == USB_SPEED_HIGH) return itd_submit (ehci, urb, mem_flags);#ifdef have_split_iso else return sitd_submit (ehci, urb, mem_flags);#else dbg ("no split iso support yet"); return -ENOSYS;#endif /* have_split_iso */ }}/* remove from hardware lists * completions normally happen asynchronously */static int ehci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb){ struct ehci_hcd *ehci = hcd_to_ehci (hcd); struct ehci_qh *qh; unsigned long flags; spin_lock_irqsave (&ehci->lock, flags); switch (usb_pipetype (urb->pipe)) { // case PIPE_CONTROL: // case PIPE_BULK: default: qh = (struct ehci_qh *) urb->hcpriv; if (!qh) break; /* if we need to use IAA and it's busy, defer */ if (qh->qh_state == QH_STATE_LINKED && ehci->reclaim && HCD_IS_RUNNING (ehci->hcd.state) ) { struct ehci_qh *last; for (last = ehci->reclaim; last->reclaim; last = last->reclaim) continue; qh->qh_state = QH_STATE_UNLINK_WAIT; last->reclaim = qh; /* bypass IAA if the hc can't care */ } else if (!HCD_IS_RUNNING (ehci->hcd.state) && ehci->reclaim) end_unlink_async (ehci, NULL); /* something else might have unlinked the qh by now */ if (qh->qh_state == QH_STATE_LINKED) start_unlink_async (ehci, qh); break; case PIPE_INTERRUPT: qh = (struct ehci_qh *) urb->hcpriv; if (!qh) break; if (qh->qh_state == QH_STATE_LINKED) { /* messy, can spin or block a microframe ... */ intr_deschedule (ehci, qh, 1); /* qh_state == IDLE */ } qh_completions (ehci, qh, NULL); /* reschedule QH iff another request is queued */ if (!list_empty (&qh->qtd_list) && HCD_IS_RUNNING (ehci->hcd.state)) { int status; status = qh_schedule (ehci, qh); spin_unlock_irqrestore (&ehci->lock, flags); if (status != 0) { // shouldn't happen often, but ... // FIXME kill those tds' urbs err ("can't reschedule qh %p, err %d", qh, status); } return status; } break; case PIPE_ISOCHRONOUS: // itd or sitd ... // wait till next completion, do it then. // completion irqs can wait up to 1024 msec, urb->transfer_flags |= EHCI_STATE_UNLINK; break; } spin_unlock_irqrestore (&ehci->lock, flags); return 0;}/*-------------------------------------------------------------------------*/// bulk qh holds the data togglestatic void ehci_free_config (struct usb_hcd *hcd, struct usb_device *udev){ struct hcd_dev *dev = (struct hcd_dev *)udev->hcpriv; struct ehci_hcd *ehci = hcd_to_ehci (hcd); int i; unsigned long flags; /* ASSERT: no requests/urbs are still linked (so no TDs) */ /* ASSERT: nobody can be submitting urbs for this any more */ ehci_dbg (ehci, "free_config %s devnum %d\n", udev->devpath, udev->devnum); spin_lock_irqsave (&ehci->lock, flags); for (i = 0; i < 32; i++) { if (dev->ep [i]) { struct ehci_qh *qh; char *why; /* dev->ep never has ITDs or SITDs */ qh = (struct ehci_qh *) dev->ep [i]; /* detect/report non-recoverable errors */ if (in_interrupt ()) why = "disconnect() didn't"; else if ((qh->hw_info2 & cpu_to_le32 (0xffff)) != 0 && qh->qh_state != QH_STATE_IDLE) why = "(active periodic)"; else why = 0; if (why) { err ("dev %s-%s ep %d-%s error: %s", hcd_to_bus (hcd)->bus_name, udev->devpath, i & 0xf, (i & 0x10) ? "IN" : "OUT", why); BUG (); } dev->ep [i] = 0; if (qh->qh_state == QH_STATE_IDLE) goto idle; ehci_dbg (ehci, "free_config, async ep 0x%02x qh %p", i, qh); /* scan_async() empties the ring as it does its work, * using IAA, but doesn't (yet?) turn it off. if it * doesn't empty this qh, likely it's the last entry. */ while (qh->qh_state == QH_STATE_LINKED && ehci->reclaim && HCD_IS_RUNNING (ehci->hcd.state) ) { spin_unlock_irqrestore (&ehci->lock, flags); /* wait_ms() won't spin, we're a thread; * and we know IRQ/timer/... can progress */ wait_ms (1); spin_lock_irqsave (&ehci->lock, flags); } if (qh->qh_state == QH_STATE_LINKED) start_unlink_async (ehci, qh); while (qh->qh_state != QH_STATE_IDLE && ehci->hcd.state != USB_STATE_HALT) { spin_unlock_irqrestore (&ehci->lock, flags); wait_ms (1); spin_lock_irqsave (&ehci->lock, flags); }idle: qh_put (ehci, qh); } } spin_unlock_irqrestore (&ehci->lock, flags);}/*-------------------------------------------------------------------------*/static const struct hc_driver ehci_driver = { .description = hcd_name, /* * generic hardware linkage */ .irq = ehci_irq, .flags = HCD_MEMORY | HCD_USB2, /* * basic lifecycle operations */ .start = ehci_start,#ifdef CONFIG_PM .suspend = ehci_suspend, .resume = ehci_resume,#endif .stop = ehci_stop, /* * memory lifecycle (except per-request) */ .hcd_alloc = ehci_hcd_alloc, .hcd_free = ehci_hcd_free, /* * managing i/o requests and associated device resources */ .urb_enqueue = ehci_urb_enqueue, .urb_dequeue = ehci_urb_dequeue, .free_config = ehci_free_config, /* * scheduling support */ .get_frame_number = ehci_get_frame, /* * root hub support */ .hub_status_data = ehci_hub_status_data, .hub_control = ehci_hub_control,};/*-------------------------------------------------------------------------*//* EHCI spec says PCI is required. *//* PCI driver selection metadata; PCI hotplugging uses this */static const struct pci_device_id __devinitdata pci_ids [] = { { /* handle any USB 2.0 EHCI controller */ .class = ((PCI_CLASS_SERIAL_USB << 8) | 0x20), .class_mask = ~0, .driver_data = (unsigned long) &ehci_driver, /* no matter who makes it */ .vendor = PCI_ANY_ID, .device = PCI_ANY_ID, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID,}, { /* end: all zeroes */ }};MODULE_DEVICE_TABLE (pci, pci_ids);/* pci driver glue; this is a "new style" PCI driver module */static struct pci_driver ehci_pci_driver = { .name = (char *) hcd_name, .id_table = pci_ids, .probe = usb_hcd_pci_probe, .remove = usb_hcd_pci_remove,#ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, .resume = usb_hcd_pci_resume,#endif};#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESCMODULE_DESCRIPTION (DRIVER_INFO);MODULE_AUTHOR (DRIVER_AUTHOR);MODULE_LICENSE ("GPL");static int __init init (void) { dbg (DRIVER_INFO); dbg ("block sizes: qh %Zd qtd %Zd itd %Zd sitd %Zd", sizeof (struct ehci_qh), sizeof (struct ehci_qtd), sizeof (struct ehci_itd), sizeof (struct ehci_sitd)); return pci_module_init (&ehci_pci_driver);}module_init (init);static void __exit cleanup (void) { pci_unregister_driver (&ehci_pci_driver);}module_exit (cleanup);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -