📄 ohci-q.c
字号:
/* * OHCI HCD (Host Controller Driver) for USB. * * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> * * This file is licenced under the GPL. */static void urb_free_priv (struct ohci_hcd *hc, urb_priv_t *urb_priv){ int last = urb_priv->length - 1; if (last >= 0) { int i; struct td *td; for (i = 0; i <= last; i++) { td = urb_priv->td [i]; if (td) td_free (hc, td); } } list_del (&urb_priv->pending); kfree (urb_priv);}/*-------------------------------------------------------------------------*//* * URB goes back to driver, and isn't reissued. * It's completely gone from HC data structures. * PRECONDITION: ohci lock held, irqs blocked. */static voidfinish_urb (struct ohci_hcd *ohci, struct urb *urb, struct pt_regs *regs)__releases(ohci->lock)__acquires(ohci->lock){ // ASSERT (urb->hcpriv != 0); urb_free_priv (ohci, urb->hcpriv); urb->hcpriv = NULL; spin_lock (&urb->lock); if (likely (urb->status == -EINPROGRESS)) urb->status = 0; /* report short control reads right even though the data TD always * has TD_R set. (much simpler, but creates the 1-td limit.) */ if (unlikely (urb->transfer_flags & URB_SHORT_NOT_OK) && unlikely (usb_pipecontrol (urb->pipe)) && urb->actual_length < urb->transfer_buffer_length && usb_pipein (urb->pipe) && urb->status == 0) { urb->status = -EREMOTEIO; } spin_unlock (&urb->lock); switch (usb_pipetype (urb->pipe)) { case PIPE_ISOCHRONOUS: ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs--; break; case PIPE_INTERRUPT: ohci_to_hcd(ohci)->self.bandwidth_int_reqs--; break; }#ifdef OHCI_VERBOSE_DEBUG urb_print (urb, "RET", usb_pipeout (urb->pipe));#endif /* urb->complete() can reenter this HCD */ spin_unlock (&ohci->lock); usb_hcd_giveback_urb (ohci_to_hcd(ohci), urb, regs); spin_lock (&ohci->lock); /* stop periodic dma if it's not needed */ if (ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs == 0 && ohci_to_hcd(ohci)->self.bandwidth_int_reqs == 0) { ohci->hc_control &= ~(OHCI_CTRL_PLE|OHCI_CTRL_IE); ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); }}/*-------------------------------------------------------------------------* * ED handling functions *-------------------------------------------------------------------------*/ /* search for the right schedule branch to use for a periodic ed. * does some load balancing; returns the branch, or negative errno. */static int balance (struct ohci_hcd *ohci, int interval, int load){ int i, branch = -ENOSPC; /* iso periods can be huge; iso tds specify frame numbers */ if (interval > NUM_INTS) interval = NUM_INTS; /* search for the least loaded schedule branch of that period * that has enough bandwidth left unreserved. */ for (i = 0; i < interval ; i++) { if (branch < 0 || ohci->load [branch] > ohci->load [i]) {#if 1 /* CONFIG_USB_BANDWIDTH */ int j; /* usb 1.1 says 90% of one frame */ for (j = i; j < NUM_INTS; j += interval) { if ((ohci->load [j] + load) > 900) break; } if (j < NUM_INTS) continue;#endif branch = i; } } return branch;}/*-------------------------------------------------------------------------*//* both iso and interrupt requests have periods; this routine puts them * into the schedule tree in the apppropriate place. most iso devices use * 1msec periods, but that's not required. */static void periodic_link (struct ohci_hcd *ohci, struct ed *ed){ unsigned i; ohci_vdbg (ohci, "link %sed %p branch %d [%dus.], interval %d\n", (ed->hwINFO & cpu_to_hc32 (ohci, ED_ISO)) ? "iso " : "", ed, ed->branch, ed->load, ed->interval); for (i = ed->branch; i < NUM_INTS; i += ed->interval) { struct ed **prev = &ohci->periodic [i]; __hc32 *prev_p = &ohci->hcca->int_table [i]; struct ed *here = *prev; /* sorting each branch by period (slow before fast) * lets us share the faster parts of the tree. * (plus maybe: put interrupt eds before iso) */ while (here && ed != here) { if (ed->interval > here->interval) break; prev = &here->ed_next; prev_p = &here->hwNextED; here = *prev; } if (ed != here) { ed->ed_next = here; if (here) ed->hwNextED = *prev_p; wmb (); *prev = ed; *prev_p = cpu_to_hc32(ohci, ed->dma); wmb(); } ohci->load [i] += ed->load; } ohci_to_hcd(ohci)->self.bandwidth_allocated += ed->load / ed->interval;}/* link an ed into one of the HC chains */static int ed_schedule (struct ohci_hcd *ohci, struct ed *ed){ int branch; if (ohci_to_hcd(ohci)->state == HC_STATE_QUIESCING) return -EAGAIN; ed->state = ED_OPER; ed->ed_prev = NULL; ed->ed_next = NULL; ed->hwNextED = 0; wmb (); /* we care about rm_list when setting CLE/BLE in case the HC was at * work on some TD when CLE/BLE was turned off, and isn't quiesced * yet. finish_unlinks() restarts as needed, some upcoming INTR_SF. * * control and bulk EDs are doubly linked (ed_next, ed_prev), but * periodic ones are singly linked (ed_next). that's because the * periodic schedule encodes a tree like figure 3-5 in the ohci * spec: each qh can have several "previous" nodes, and the tree * doesn't have unused/idle descriptors. */ switch (ed->type) { case PIPE_CONTROL: if (ohci->ed_controltail == NULL) { WARN_ON (ohci->hc_control & OHCI_CTRL_CLE); ohci_writel (ohci, ed->dma, &ohci->regs->ed_controlhead); } else { ohci->ed_controltail->ed_next = ed; ohci->ed_controltail->hwNextED = cpu_to_hc32 (ohci, ed->dma); } ed->ed_prev = ohci->ed_controltail; if (!ohci->ed_controltail && !ohci->ed_rm_list) { wmb(); ohci->hc_control |= OHCI_CTRL_CLE; ohci_writel (ohci, 0, &ohci->regs->ed_controlcurrent); ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); } ohci->ed_controltail = ed; break; case PIPE_BULK: if (ohci->ed_bulktail == NULL) { WARN_ON (ohci->hc_control & OHCI_CTRL_BLE); ohci_writel (ohci, ed->dma, &ohci->regs->ed_bulkhead); } else { ohci->ed_bulktail->ed_next = ed; ohci->ed_bulktail->hwNextED = cpu_to_hc32 (ohci, ed->dma); } ed->ed_prev = ohci->ed_bulktail; if (!ohci->ed_bulktail && !ohci->ed_rm_list) { wmb(); ohci->hc_control |= OHCI_CTRL_BLE; ohci_writel (ohci, 0, &ohci->regs->ed_bulkcurrent); ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); } ohci->ed_bulktail = ed; break; // case PIPE_INTERRUPT: // case PIPE_ISOCHRONOUS: default: branch = balance (ohci, ed->interval, ed->load); if (branch < 0) { ohci_dbg (ohci, "ERR %d, interval %d msecs, load %d\n", branch, ed->interval, ed->load); // FIXME if there are TDs queued, fail them! return branch; } ed->branch = branch; periodic_link (ohci, ed); } /* the HC may not see the schedule updates yet, but if it does * then they'll be properly ordered. */ return 0;}/*-------------------------------------------------------------------------*//* scan the periodic table to find and unlink this ED */static void periodic_unlink (struct ohci_hcd *ohci, struct ed *ed){ int i; for (i = ed->branch; i < NUM_INTS; i += ed->interval) { struct ed *temp; struct ed **prev = &ohci->periodic [i]; __hc32 *prev_p = &ohci->hcca->int_table [i]; while (*prev && (temp = *prev) != ed) { prev_p = &temp->hwNextED; prev = &temp->ed_next; } if (*prev) { *prev_p = ed->hwNextED; *prev = ed->ed_next; } ohci->load [i] -= ed->load; } ohci_to_hcd(ohci)->self.bandwidth_allocated -= ed->load / ed->interval; ohci_vdbg (ohci, "unlink %sed %p branch %d [%dus.], interval %d\n", (ed->hwINFO & cpu_to_hc32 (ohci, ED_ISO)) ? "iso " : "", ed, ed->branch, ed->load, ed->interval);}/* unlink an ed from one of the HC chains. * just the link to the ed is unlinked. * the link from the ed still points to another operational ed or 0 * so the HC can eventually finish the processing of the unlinked ed * (assuming it already started that, which needn't be true). * * ED_UNLINK is a transient state: the HC may still see this ED, but soon * it won't. ED_SKIP means the HC will finish its current transaction, * but won't start anything new. The TD queue may still grow; device * drivers don't know about this HCD-internal state. * * When the HC can't see the ED, something changes ED_UNLINK to one of: * * - ED_OPER: when there's any request queued, the ED gets rescheduled * immediately. HC should be working on them. * * - ED_IDLE: when there's no TD queue. there's no reason for the HC * to care about this ED; safe to disable the endpoint. * * When finish_unlinks() runs later, after SOF interrupt, it will often * complete one or more URB unlinks before making that state change. */static void ed_deschedule (struct ohci_hcd *ohci, struct ed *ed) { ed->hwINFO |= cpu_to_hc32 (ohci, ED_SKIP); wmb (); ed->state = ED_UNLINK; /* To deschedule something from the control or bulk list, just * clear CLE/BLE and wait. There's no safe way to scrub out list * head/current registers until later, and "later" isn't very * tightly specified. Figure 6-5 and Section 6.4.2.2 show how * the HC is reading the ED queues (while we modify them). * * For now, ed_schedule() is "later". It might be good paranoia * to scrub those registers in finish_unlinks(), in case of bugs * that make the HC try to use them. */ switch (ed->type) { case PIPE_CONTROL: /* remove ED from the HC's list: */ if (ed->ed_prev == NULL) { if (!ed->hwNextED) { ohci->hc_control &= ~OHCI_CTRL_CLE; ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); // a ohci_readl() later syncs CLE with the HC } else ohci_writel (ohci, hc32_to_cpup (ohci, &ed->hwNextED), &ohci->regs->ed_controlhead); } else { ed->ed_prev->ed_next = ed->ed_next; ed->ed_prev->hwNextED = ed->hwNextED; } /* remove ED from the HCD's list: */ if (ohci->ed_controltail == ed) { ohci->ed_controltail = ed->ed_prev; if (ohci->ed_controltail) ohci->ed_controltail->ed_next = NULL; } else if (ed->ed_next) { ed->ed_next->ed_prev = ed->ed_prev; } break; case PIPE_BULK: /* remove ED from the HC's list: */ if (ed->ed_prev == NULL) { if (!ed->hwNextED) { ohci->hc_control &= ~OHCI_CTRL_BLE; ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); // a ohci_readl() later syncs BLE with the HC } else ohci_writel (ohci, hc32_to_cpup (ohci, &ed->hwNextED), &ohci->regs->ed_bulkhead); } else { ed->ed_prev->ed_next = ed->ed_next; ed->ed_prev->hwNextED = ed->hwNextED; } /* remove ED from the HCD's list: */ if (ohci->ed_bulktail == ed) { ohci->ed_bulktail = ed->ed_prev; if (ohci->ed_bulktail) ohci->ed_bulktail->ed_next = NULL; } else if (ed->ed_next) { ed->ed_next->ed_prev = ed->ed_prev;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -