📄 uhci-hcd.c
字号:
uhci_delete_queued_urb(uhci, urb); /* The interrupt loop will reclaim the QH's */ uhci_remove_qh(uhci, urbp->qh); urbp->qh = NULL;}static int uhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb){ struct uhci_hcd *uhci = hcd_to_uhci(hcd); unsigned long flags; struct urb_priv *urbp; unsigned int age; spin_lock_irqsave(&uhci->schedule_lock, flags); urbp = urb->hcpriv; if (!urbp) /* URB was never linked! */ goto done; list_del_init(&urbp->urb_list); uhci_unlink_generic(uhci, urb); age = uhci_get_current_frame_number(uhci); if (age != uhci->urb_remove_age) { uhci_remove_pending_urbps(uhci); uhci->urb_remove_age = age; } /* If we're the first, set the next interrupt bit */ if (list_empty(&uhci->urb_remove_list)) uhci_set_next_interrupt(uhci); list_add_tail(&urbp->urb_list, &uhci->urb_remove_list);done: spin_unlock_irqrestore(&uhci->schedule_lock, flags); return 0;}static int uhci_fsbr_timeout(struct uhci_hcd *uhci, struct urb *urb){ struct urb_priv *urbp = (struct urb_priv *)urb->hcpriv; struct list_head *head; struct uhci_td *td; int count = 0; uhci_dec_fsbr(uhci, urb); urbp->fsbr_timeout = 1; /* * Ideally we would want to fix qh->element as well, but it's * read/write by the HC, so that can introduce a race. It's not * really worth the hassle */ head = &urbp->td_list; list_for_each_entry(td, head, list) { /* * Make sure we don't do the last one (since it'll have the * TERM bit set) as well as we skip every so many TD's to * make sure it doesn't hog the bandwidth */ if (td->list.next != head && (count % DEPTH_INTERVAL) == (DEPTH_INTERVAL - 1)) td->link |= UHCI_PTR_DEPTH; count++; } return 0;}/* * uhci_get_current_frame_number() * * returns the current frame number for a USB bus/controller. */static unsigned int uhci_get_current_frame_number(struct uhci_hcd *uhci){ return inw(uhci->io_addr + USBFRNUM);}static int init_stall_timer(struct usb_hcd *hcd);static void stall_callback(unsigned long ptr){ struct usb_hcd *hcd = (struct usb_hcd *)ptr; struct uhci_hcd *uhci = hcd_to_uhci(hcd); struct urb_priv *up; unsigned long flags; int called_uhci_finish_completion = 0; spin_lock_irqsave(&uhci->schedule_lock, flags); if (!list_empty(&uhci->urb_remove_list) && uhci_get_current_frame_number(uhci) != uhci->urb_remove_age) { uhci_remove_pending_urbps(uhci); uhci_finish_completion(hcd, NULL); called_uhci_finish_completion = 1; } list_for_each_entry(up, &uhci->urb_list, urb_list) { struct urb *u = up->urb; spin_lock(&u->lock); /* Check if the FSBR timed out */ if (up->fsbr && !up->fsbr_timeout && time_after_eq(jiffies, up->fsbrtime + IDLE_TIMEOUT)) uhci_fsbr_timeout(uhci, u); spin_unlock(&u->lock); } spin_unlock_irqrestore(&uhci->schedule_lock, flags); /* Wake up anyone waiting for an URB to complete */ if (called_uhci_finish_completion) wake_up_all(&uhci->waitqh); /* Really disable FSBR */ if (!uhci->fsbr && uhci->fsbrtimeout && time_after_eq(jiffies, uhci->fsbrtimeout)) { uhci->fsbrtimeout = 0; uhci->skel_term_qh->link = UHCI_PTR_TERM; } /* Poll for and perform state transitions */ hc_state_transitions(uhci); if (unlikely(uhci->suspended_ports && uhci->state != UHCI_SUSPENDED)) uhci_check_resume(uhci); init_stall_timer(hcd);}static int init_stall_timer(struct usb_hcd *hcd){ struct uhci_hcd *uhci = hcd_to_uhci(hcd); init_timer(&uhci->stall_timer); uhci->stall_timer.function = stall_callback; uhci->stall_timer.data = (unsigned long)hcd; uhci->stall_timer.expires = jiffies + msecs_to_jiffies(100); add_timer(&uhci->stall_timer); return 0;}static void uhci_free_pending_qhs(struct uhci_hcd *uhci){ struct uhci_qh *qh, *tmp; list_for_each_entry_safe(qh, tmp, &uhci->qh_remove_list, remove_list) { list_del_init(&qh->remove_list); uhci_free_qh(uhci, qh); }}static void uhci_free_pending_tds(struct uhci_hcd *uhci){ struct uhci_td *td, *tmp; list_for_each_entry_safe(td, tmp, &uhci->td_remove_list, remove_list) { list_del_init(&td->remove_list); uhci_free_td(uhci, td); }}static voiduhci_finish_urb(struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs)__releases(uhci->schedule_lock)__acquires(uhci->schedule_lock){ struct uhci_hcd *uhci = hcd_to_uhci(hcd); uhci_destroy_urb_priv(uhci, urb); spin_unlock(&uhci->schedule_lock); usb_hcd_giveback_urb(hcd, urb, regs); spin_lock(&uhci->schedule_lock);}static void uhci_finish_completion(struct usb_hcd *hcd, struct pt_regs *regs){ struct uhci_hcd *uhci = hcd_to_uhci(hcd); struct urb_priv *urbp, *tmp; list_for_each_entry_safe(urbp, tmp, &uhci->complete_list, urb_list) { struct urb *urb = urbp->urb; list_del_init(&urbp->urb_list); uhci_finish_urb(hcd, urb, regs); }}static void uhci_remove_pending_urbps(struct uhci_hcd *uhci){ /* Splice the urb_remove_list onto the end of the complete_list */ list_splice_init(&uhci->urb_remove_list, uhci->complete_list.prev);}static irqreturn_t uhci_irq(struct usb_hcd *hcd, struct pt_regs *regs){ struct uhci_hcd *uhci = hcd_to_uhci(hcd); unsigned long io_addr = uhci->io_addr; unsigned short status; struct urb_priv *urbp, *tmp; unsigned int age; /* * Read the interrupt status, and write it back to clear the * interrupt cause. Contrary to the UHCI specification, the * "HC Halted" status bit is persistent: it is RO, not R/WC. */ status = inw(io_addr + USBSTS); if (!(status & ~USBSTS_HCH)) /* shared interrupt, not mine */ return IRQ_NONE; outw(status, io_addr + USBSTS); /* Clear it */ if (status & ~(USBSTS_USBINT | USBSTS_ERROR | USBSTS_RD)) { if (status & USBSTS_HSE) dev_err(uhci_dev(uhci), "host system error, " "PCI problems?\n"); if (status & USBSTS_HCPE) dev_err(uhci_dev(uhci), "host controller process " "error, something bad happened!\n"); if ((status & USBSTS_HCH) && uhci->state > 0) { dev_err(uhci_dev(uhci), "host controller halted, " "very bad!\n"); /* FIXME: Reset the controller, fix the offending TD */ } } if (status & USBSTS_RD) uhci->resume_detect = 1; spin_lock(&uhci->schedule_lock); age = uhci_get_current_frame_number(uhci); if (age != uhci->qh_remove_age) uhci_free_pending_qhs(uhci); if (age != uhci->td_remove_age) uhci_free_pending_tds(uhci); if (age != uhci->urb_remove_age) uhci_remove_pending_urbps(uhci); if (list_empty(&uhci->urb_remove_list) && list_empty(&uhci->td_remove_list) && list_empty(&uhci->qh_remove_list)) uhci_clear_next_interrupt(uhci); else uhci_set_next_interrupt(uhci); /* Walk the list of pending URBs to see which ones completed * (must be _safe because uhci_transfer_result() dequeues URBs) */ list_for_each_entry_safe(urbp, tmp, &uhci->urb_list, urb_list) { struct urb *urb = urbp->urb; /* Checks the status and does all of the magic necessary */ uhci_transfer_result(uhci, urb); } uhci_finish_completion(hcd, regs); spin_unlock(&uhci->schedule_lock); /* Wake up anyone waiting for an URB to complete */ wake_up_all(&uhci->waitqh); return IRQ_HANDLED;}static void reset_hc(struct uhci_hcd *uhci){ unsigned long io_addr = uhci->io_addr; /* Turn off PIRQ, SMI, and all interrupts. This also turns off * the BIOS's USB Legacy Support. */ pci_write_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP, 0); outw(0, uhci->io_addr + USBINTR); /* Global reset for 50ms */ uhci->state = UHCI_RESET; outw(USBCMD_GRESET, io_addr + USBCMD); msleep(50); outw(0, io_addr + USBCMD); /* Another 10ms delay */ msleep(10); uhci->resume_detect = 0;}static void suspend_hc(struct uhci_hcd *uhci){ unsigned long io_addr = uhci->io_addr; dev_dbg(uhci_dev(uhci), "%s\n", __FUNCTION__); uhci->state = UHCI_SUSPENDED; uhci->resume_detect = 0; outw(USBCMD_EGSM, io_addr + USBCMD);}static void wakeup_hc(struct uhci_hcd *uhci){ unsigned long io_addr = uhci->io_addr; switch (uhci->state) { case UHCI_SUSPENDED: /* Start the resume */ dev_dbg(uhci_dev(uhci), "%s\n", __FUNCTION__); /* Global resume for >= 20ms */ outw(USBCMD_FGR | USBCMD_EGSM, io_addr + USBCMD); uhci->state = UHCI_RESUMING_1; uhci->state_end = jiffies + msecs_to_jiffies(20); break; case UHCI_RESUMING_1: /* End global resume */ uhci->state = UHCI_RESUMING_2; outw(0, io_addr + USBCMD); /* Falls through */ case UHCI_RESUMING_2: /* Wait for EOP to be sent */ if (inw(io_addr + USBCMD) & USBCMD_FGR) break; /* Run for at least 1 second, and * mark it configured with a 64-byte max packet */ uhci->state = UHCI_RUNNING_GRACE; uhci->state_end = jiffies + HZ; outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP, io_addr + USBCMD); break; case UHCI_RUNNING_GRACE: /* Now allowed to suspend */ uhci->state = UHCI_RUNNING; break; default: break; }}static int ports_active(struct uhci_hcd *uhci){ unsigned long io_addr = uhci->io_addr; int connection = 0; int i; for (i = 0; i < uhci->rh_numports; i++) connection |= (inw(io_addr + USBPORTSC1 + i * 2) & USBPORTSC_CCS); return connection;}static int suspend_allowed(struct uhci_hcd *uhci){ unsigned long io_addr = uhci->io_addr; int i; if (to_pci_dev(uhci_dev(uhci))->vendor != PCI_VENDOR_ID_INTEL) return 1; /* Some of Intel's USB controllers have a bug that causes false * resume indications if any port has an over current condition. * To prevent problems, we will not allow a global suspend if * any ports are OC. * * Some motherboards using Intel's chipsets (but not using all * the USB ports) appear to hardwire the over current inputs active * to disable the USB ports. */ /* check for over current condition on any port */ for (i = 0; i < uhci->rh_numports; i++) { if (inw(io_addr + USBPORTSC1 + i * 2) & USBPORTSC_OC) return 0; } return 1;}static void hc_state_transitions(struct uhci_hcd *uhci){ switch (uhci->state) { case UHCI_RUNNING: /* global suspend if nothing connected for 1 second */ if (!ports_active(uhci) && suspend_allowed(uhci)) { uhci->state = UHCI_SUSPENDING_GRACE; uhci->state_end = jiffies + HZ; } break; case UHCI_SUSPENDING_GRACE: if (ports_active(uhci)) uhci->state = UHCI_RUNNING; else if (time_after_eq(jiffies, uhci->state_end)) suspend_hc(uhci); break; case UHCI_SUSPENDED: /* wakeup if requested by a device */ if (uhci->resume_detect) wakeup_hc(uhci); break; case UHCI_RESUMING_1: case UHCI_RESUMING_2: case UHCI_RUNNING_GRACE: if (time_after_eq(jiffies, uhci->state_end)) wakeup_hc(uhci); break; default: break; }}static int start_hc(struct uhci_hcd *uhci){ unsigned long io_addr = uhci->io_addr; int timeout = 10; /* * Reset the HC - this will force us to get a * new notification of any already connected * ports due to the virtual disconnect that it * implies. */ outw(USBCMD_HCRESET, io_addr + USBCMD); while (inw(io_addr + USBCMD) & USBCMD_HCRESET) { if (--timeout < 0) { dev_err(uhci_dev(uhci), "USBCMD_HCRESET timed out!\n"); return -ETIMEDOUT; } msleep(1); } /* Turn on PIRQ and all interrupts */ pci_write_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP, USBLEGSUP_DEFAULT); outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP, io_addr + USBINTR); /* Start at frame 0 */ outw(0, io_addr + USBFRNUM); outl(uhci->fl->dma_handle, io_addr + USBFLBASEADD); /* Run and mark it configured with a 64-byte max packet */ uhci->state = UHCI_RUNNING_GRACE; uhci->state_end = jiffies + HZ; outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP, io_addr + USBCMD); uhci_to_hcd(uhci)->state = USB_STATE_RUNNING; return 0;}/* * De-allocate all resources.. */static void release_uhci(struct uhci_hcd *uhci){ int i; for (i = 0; i < UHCI_NUM_SKELQH; i++) if (uhci->skelqh[i]) { uhci_free_qh(uhci, uhci->skelqh[i]); uhci->skelqh[i] = NULL; } if (uhci->term_td) { uhci_free_td(uhci, uhci->term_td); uhci->term_td = NULL; } if (uhci->qh_pool) { dma_pool_destroy(uhci->qh_pool); uhci->qh_pool = NULL; } if (uhci->td_pool) { dma_pool_destroy(uhci->td_pool);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -