summaryrefslogtreecommitdiff
path: root/drivers/usb/host/ehci-sched.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/ehci-sched.c')
-rw-r--r--drivers/usb/host/ehci-sched.c552
1 files changed, 195 insertions, 357 deletions
diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c
index 33182c6d1ff9..7cf3da7babf0 100644
--- a/drivers/usb/host/ehci-sched.c
+++ b/drivers/usb/host/ehci-sched.c
@@ -479,70 +479,26 @@ static int tt_no_collision (
/*-------------------------------------------------------------------------*/
-static int enable_periodic (struct ehci_hcd *ehci)
+static void enable_periodic(struct ehci_hcd *ehci)
{
- int status;
-
- if (ehci->periodic_sched++)
- return 0;
-
- /* did clearing PSE did take effect yet?
- * takes effect only at frame boundaries...
- */
- status = handshake_on_error_set_halt(ehci, &ehci->regs->status,
- STS_PSS, 0, 9 * 125);
- if (status) {
- usb_hc_died(ehci_to_hcd(ehci));
- return status;
- }
+ if (ehci->periodic_count++)
+ return;
- ehci->command |= CMD_PSE;
- ehci_writel(ehci, ehci->command, &ehci->regs->command);
- /* posted write ... PSS happens later */
+ /* Stop waiting to turn off the periodic schedule */
+ ehci->enabled_hrtimer_events &= ~BIT(EHCI_HRTIMER_DISABLE_PERIODIC);
- /* make sure ehci_work scans these */
- ehci->next_uframe = ehci_read_frame_index(ehci)
- % (ehci->periodic_size << 3);
- if (unlikely(ehci->broken_periodic))
- ehci->last_periodic_enable = ktime_get_real();
- return 0;
+ /* Don't start the schedule until PSS is 0 */
+ ehci_poll_PSS(ehci);
+ turn_on_io_watchdog(ehci);
}
-static int disable_periodic (struct ehci_hcd *ehci)
+static void disable_periodic(struct ehci_hcd *ehci)
{
- int status;
-
- if (--ehci->periodic_sched)
- return 0;
-
- if (unlikely(ehci->broken_periodic)) {
- /* delay experimentally determined */
- ktime_t safe = ktime_add_us(ehci->last_periodic_enable, 1000);
- ktime_t now = ktime_get_real();
- s64 delay = ktime_us_delta(safe, now);
-
- if (unlikely(delay > 0))
- udelay(delay);
- }
-
- /* did setting PSE not take effect yet?
- * takes effect only at frame boundaries...
- */
- status = handshake_on_error_set_halt(ehci, &ehci->regs->status,
- STS_PSS, STS_PSS, 9 * 125);
- if (status) {
- usb_hc_died(ehci_to_hcd(ehci));
- return status;
- }
-
- ehci->command &= ~CMD_PSE;
- ehci_writel(ehci, ehci->command, &ehci->regs->command);
- /* posted write ... */
-
- free_cached_lists(ehci);
+ if (--ehci->periodic_count)
+ return;
- ehci->next_uframe = -1;
- return 0;
+ /* Don't turn off the schedule until PSS is 1 */
+ ehci_poll_PSS(ehci);
}
/*-------------------------------------------------------------------------*/
@@ -553,7 +509,7 @@ static int disable_periodic (struct ehci_hcd *ehci)
* this just links in a qh; caller guarantees uframe masks are set right.
* no FSTN support (yet; ehci 0.96+)
*/
-static int qh_link_periodic (struct ehci_hcd *ehci, struct ehci_qh *qh)
+static void qh_link_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh)
{
unsigned i;
unsigned period = qh->period;
@@ -606,28 +562,38 @@ static int qh_link_periodic (struct ehci_hcd *ehci, struct ehci_qh *qh)
}
qh->qh_state = QH_STATE_LINKED;
qh->xacterrs = 0;
- qh_get (qh);
/* update per-qh bandwidth for usbfs */
ehci_to_hcd(ehci)->self.bandwidth_allocated += qh->period
? ((qh->usecs + qh->c_usecs) / qh->period)
: (qh->usecs * 8);
+ list_add(&qh->intr_node, &ehci->intr_qh_list);
+
/* maybe enable periodic schedule processing */
- return enable_periodic(ehci);
+ ++ehci->intr_count;
+ enable_periodic(ehci);
}
-static int qh_unlink_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh)
+static void qh_unlink_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh)
{
unsigned i;
unsigned period;
- // FIXME:
- // IF this isn't high speed
- // and this qh is active in the current uframe
- // (and overlay token SplitXstate is false?)
- // THEN
- // qh->hw_info1 |= cpu_to_hc32(1 << 7 /* "ignore" */);
+ /*
+ * If qh is for a low/full-speed device, simply unlinking it
+ * could interfere with an ongoing split transaction. To unlink
+ * it safely would require setting the QH_INACTIVATE bit and
+ * waiting at least one frame, as described in EHCI 4.12.2.5.
+ *
+ * We won't bother with any of this. Instead, we assume that the
+ * only reason for unlinking an interrupt QH while the current URB
+ * is still active is to dequeue all the URBs (flush the whole
+ * endpoint queue).
+ *
+ * If rebalancing the periodic schedule is ever implemented, this
+ * approach will no longer be valid.
+ */
/* high bandwidth, or otherwise part of every microframe */
if ((period = qh->period) == 0)
@@ -650,18 +616,15 @@ static int qh_unlink_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh)
/* qh->qh_next still "live" to HC */
qh->qh_state = QH_STATE_UNLINK;
qh->qh_next.ptr = NULL;
- qh_put (qh);
- /* maybe turn off periodic schedule */
- return disable_periodic(ehci);
+ if (ehci->qh_scan_next == qh)
+ ehci->qh_scan_next = list_entry(qh->intr_node.next,
+ struct ehci_qh, intr_node);
+ list_del(&qh->intr_node);
}
-static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh)
+static void start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh)
{
- unsigned wait;
- struct ehci_qh_hw *hw = qh->hw;
- int rc;
-
/* If the QH isn't linked then there's nothing we can do
* unless we were called during a giveback, in which case
* qh_completions() has to deal with it.
@@ -674,28 +637,45 @@ static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh)
qh_unlink_periodic (ehci, qh);
- /* simple/paranoid: always delay, expecting the HC needs to read
- * qh->hw_next or finish a writeback after SPLIT/CSPLIT ... and
- * expect khubd to clean up after any CSPLITs we won't issue.
- * active high speed queues may need bigger delays...
+ /* Make sure the unlinks are visible before starting the timer */
+ wmb();
+
+ /*
+ * The EHCI spec doesn't say how long it takes the controller to
+ * stop accessing an unlinked interrupt QH. The timer delay is
+ * 9 uframes; presumably that will be long enough.
*/
- if (list_empty (&qh->qtd_list)
- || (cpu_to_hc32(ehci, QH_CMASK)
- & hw->hw_info2) != 0)
- wait = 2;
+ qh->unlink_cycle = ehci->intr_unlink_cycle;
+
+ /* New entries go at the end of the intr_unlink list */
+ if (ehci->intr_unlink)
+ ehci->intr_unlink_last->unlink_next = qh;
else
- wait = 55; /* worst case: 3 * 1024 */
+ ehci->intr_unlink = qh;
+ ehci->intr_unlink_last = qh;
+
+ if (ehci->intr_unlinking)
+ ; /* Avoid recursive calls */
+ else if (ehci->rh_state < EHCI_RH_RUNNING)
+ ehci_handle_intr_unlinks(ehci);
+ else if (ehci->intr_unlink == qh) {
+ ehci_enable_event(ehci, EHCI_HRTIMER_UNLINK_INTR, true);
+ ++ehci->intr_unlink_cycle;
+ }
+}
+
+static void end_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh)
+{
+ struct ehci_qh_hw *hw = qh->hw;
+ int rc;
- udelay (wait);
qh->qh_state = QH_STATE_IDLE;
hw->hw_next = EHCI_LIST_END(ehci);
- wmb ();
qh_completions(ehci, qh);
/* reschedule QH iff another request is queued */
- if (!list_empty(&qh->qtd_list) &&
- ehci->rh_state == EHCI_RH_RUNNING) {
+ if (!list_empty(&qh->qtd_list) && ehci->rh_state == EHCI_RH_RUNNING) {
rc = qh_schedule(ehci, qh);
/* An error here likely indicates handshake failure
@@ -708,6 +688,10 @@ static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh)
ehci_err(ehci, "can't reschedule qh %p, err %d\n",
qh, rc);
}
+
+ /* maybe turn off periodic schedule */
+ --ehci->intr_count;
+ disable_periodic(ehci);
}
/*-------------------------------------------------------------------------*/
@@ -884,7 +868,7 @@ static int qh_schedule(struct ehci_hcd *ehci, struct ehci_qh *qh)
ehci_dbg (ehci, "reused qh %p schedule\n", qh);
/* stuff into the periodic schedule */
- status = qh_link_periodic (ehci, qh);
+ qh_link_periodic(ehci, qh);
done:
return status;
}
@@ -944,6 +928,35 @@ done_not_linked:
return status;
}
+static void scan_intr(struct ehci_hcd *ehci)
+{
+ struct ehci_qh *qh;
+
+ list_for_each_entry_safe(qh, ehci->qh_scan_next, &ehci->intr_qh_list,
+ intr_node) {
+ rescan:
+ /* clean any finished work for this qh */
+ if (!list_empty(&qh->qtd_list)) {
+ int temp;
+
+ /*
+ * Unlinks could happen here; completion reporting
+ * drops the lock. That's why ehci->qh_scan_next
+ * always holds the next qh to scan; if the next qh
+ * gets unlinked then ehci->qh_scan_next is adjusted
+ * in qh_unlink_periodic().
+ */
+ temp = qh_completions(ehci, qh);
+ if (unlikely(qh->needs_rescan ||
+ (list_empty(&qh->qtd_list) &&
+ qh->qh_state == QH_STATE_LINKED)))
+ start_unlink_intr(ehci, qh);
+ else if (temp != 0)
+ goto rescan;
+ }
+ }
+}
+
/*-------------------------------------------------------------------------*/
/* ehci_iso_stream ops work with both ITD and SITD */
@@ -958,7 +971,6 @@ iso_stream_alloc (gfp_t mem_flags)
INIT_LIST_HEAD(&stream->td_list);
INIT_LIST_HEAD(&stream->free_list);
stream->next_uframe = -1;
- stream->refcount = 1;
}
return stream;
}
@@ -1058,57 +1070,6 @@ iso_stream_init (
stream->maxp = maxp;
}
-static void
-iso_stream_put(struct ehci_hcd *ehci, struct ehci_iso_stream *stream)
-{
- stream->refcount--;
-
- /* free whenever just a dev->ep reference remains.
- * not like a QH -- no persistent state (toggle, halt)
- */
- if (stream->refcount == 1) {
- // BUG_ON (!list_empty(&stream->td_list));
-
- while (!list_empty (&stream->free_list)) {
- struct list_head *entry;
-
- entry = stream->free_list.next;
- list_del (entry);
-
- /* knows about ITD vs SITD */
- if (stream->highspeed) {
- struct ehci_itd *itd;
-
- itd = list_entry (entry, struct ehci_itd,
- itd_list);
- dma_pool_free (ehci->itd_pool, itd,
- itd->itd_dma);
- } else {
- struct ehci_sitd *sitd;
-
- sitd = list_entry (entry, struct ehci_sitd,
- sitd_list);
- dma_pool_free (ehci->sitd_pool, sitd,
- sitd->sitd_dma);
- }
- }
-
- stream->bEndpointAddress &= 0x0f;
- if (stream->ep)
- stream->ep->hcpriv = NULL;
-
- kfree(stream);
- }
-}
-
-static inline struct ehci_iso_stream *
-iso_stream_get (struct ehci_iso_stream *stream)
-{
- if (likely (stream != NULL))
- stream->refcount++;
- return stream;
-}
-
static struct ehci_iso_stream *
iso_stream_find (struct ehci_hcd *ehci, struct urb *urb)
{
@@ -1129,7 +1090,6 @@ iso_stream_find (struct ehci_hcd *ehci, struct urb *urb)
if (unlikely (stream == NULL)) {
stream = iso_stream_alloc(GFP_ATOMIC);
if (likely (stream != NULL)) {
- /* dev->ep owns the initial refcount */
ep->hcpriv = stream;
stream->ep = ep;
iso_stream_init(ehci, stream, urb->dev, urb->pipe,
@@ -1144,9 +1104,6 @@ iso_stream_find (struct ehci_hcd *ehci, struct urb *urb)
stream = NULL;
}
- /* caller guarantees an eventual matching iso_stream_put */
- stream = iso_stream_get (stream);
-
spin_unlock_irqrestore (&ehci->lock, flags);
return stream;
}
@@ -1254,17 +1211,19 @@ itd_urb_transaction (
spin_lock_irqsave (&ehci->lock, flags);
for (i = 0; i < num_itds; i++) {
- /* free_list.next might be cache-hot ... but maybe
- * the HC caches it too. avoid that issue for now.
+ /*
+ * Use iTDs from the free list, but not iTDs that may
+ * still be in use by the hardware.
*/
-
- /* prefer previously-allocated itds */
- if (likely (!list_empty(&stream->free_list))) {
- itd = list_entry (stream->free_list.prev,
+ if (likely(!list_empty(&stream->free_list))) {
+ itd = list_first_entry(&stream->free_list,
struct ehci_itd, itd_list);
+ if (itd->frame == ehci->now_frame)
+ goto alloc_itd;
list_del (&itd->itd_list);
itd_dma = itd->itd_dma;
} else {
+ alloc_itd:
spin_unlock_irqrestore (&ehci->lock, flags);
itd = dma_pool_alloc (ehci->itd_pool, mem_flags,
&itd_dma);
@@ -1528,6 +1487,10 @@ iso_stream_schedule (
urb->start_frame = stream->next_uframe;
if (!stream->highspeed)
urb->start_frame >>= 3;
+
+ /* Make sure scan_isoc() sees these */
+ if (ehci->isoc_count == 0)
+ ehci->next_frame = now >> 3;
return 0;
fail:
@@ -1615,8 +1578,7 @@ itd_link (struct ehci_hcd *ehci, unsigned frame, struct ehci_itd *itd)
}
/* fit urb's itds into the selected schedule slot; activate as needed */
-static int
-itd_link_urb (
+static void itd_link_urb(
struct ehci_hcd *ehci,
struct urb *urb,
unsigned mod,
@@ -1659,7 +1621,7 @@ itd_link_urb (
itd = list_entry (iso_sched->td_list.next,
struct ehci_itd, itd_list);
list_move_tail (&itd->itd_list, &stream->td_list);
- itd->stream = iso_stream_get (stream);
+ itd->stream = stream;
itd->urb = urb;
itd_init (ehci, stream, itd);
}
@@ -1686,8 +1648,8 @@ itd_link_urb (
iso_sched_free (stream, iso_sched);
urb->hcpriv = NULL;
- timer_action (ehci, TIMER_IO_WATCHDOG);
- return enable_periodic(ehci);
+ ++ehci->isoc_count;
+ enable_periodic(ehci);
}
#define ISO_ERRS (EHCI_ISOC_BUF_ERR | EHCI_ISOC_BABBLE | EHCI_ISOC_XACTERR)
@@ -1702,11 +1664,8 @@ itd_link_urb (
* (b) only this endpoint's completions submit URBs. It seems some silicon
* corrupts things if you reuse completed descriptors very quickly...
*/
-static unsigned
-itd_complete (
- struct ehci_hcd *ehci,
- struct ehci_itd *itd
-) {
+static bool itd_complete(struct ehci_hcd *ehci, struct ehci_itd *itd)
+{
struct urb *urb = itd->urb;
struct usb_iso_packet_descriptor *desc;
u32 t;
@@ -1714,7 +1673,7 @@ itd_complete (
int urb_index = -1;
struct ehci_iso_stream *stream = itd->stream;
struct usb_device *dev;
- unsigned retval = false;
+ bool retval = false;
/* for each uframe with a packet */
for (uframe = 0; uframe < 8; uframe++) {
@@ -1767,9 +1726,11 @@ itd_complete (
ehci_urb_done(ehci, urb, 0);
retval = true;
urb = NULL;
- (void) disable_periodic(ehci);
- ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--;
+ --ehci->isoc_count;
+ disable_periodic(ehci);
+
+ ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--;
if (ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs == 0) {
if (ehci->amd_pll_fix == 1)
usb_amd_quirk_pll_enable();
@@ -1783,28 +1744,20 @@ itd_complete (
dev->devpath, stream->bEndpointAddress & 0x0f,
(stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out");
}
- iso_stream_put (ehci, stream);
done:
itd->urb = NULL;
- if (ehci->clock_frame != itd->frame || itd->index[7] != -1) {
- /* OK to recycle this ITD now. */
- itd->stream = NULL;
- list_move(&itd->itd_list, &stream->free_list);
- iso_stream_put(ehci, stream);
- } else {
- /* HW might remember this ITD, so we can't recycle it yet.
- * Move it to a safe place until a new frame starts.
- */
- list_move(&itd->itd_list, &ehci->cached_itd_list);
- if (stream->refcount == 2) {
- /* If iso_stream_put() were called here, stream
- * would be freed. Instead, just prevent reuse.
- */
- stream->ep->hcpriv = NULL;
- stream->ep = NULL;
- }
+
+ /* Add to the end of the free list for later reuse */
+ list_move_tail(&itd->itd_list, &stream->free_list);
+
+ /* Recycle the iTDs when the pipeline is empty (ep no longer in use) */
+ if (list_empty(&stream->td_list)) {
+ list_splice_tail_init(&stream->free_list,
+ &ehci->cached_itd_list);
+ start_free_itds(ehci);
}
+
return retval;
}
@@ -1861,12 +1814,9 @@ static int itd_submit (struct ehci_hcd *ehci, struct urb *urb,
itd_link_urb (ehci, urb, ehci->periodic_size << 3, stream);
else
usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb);
-done_not_linked:
+ done_not_linked:
spin_unlock_irqrestore (&ehci->lock, flags);
-
-done:
- if (unlikely (status < 0))
- iso_stream_put (ehci, stream);
+ done:
return status;
}
@@ -1955,17 +1905,19 @@ sitd_urb_transaction (
* means we never need two sitds for full speed packets.
*/
- /* free_list.next might be cache-hot ... but maybe
- * the HC caches it too. avoid that issue for now.
+ /*
+ * Use siTDs from the free list, but not siTDs that may
+ * still be in use by the hardware.
*/
-
- /* prefer previously-allocated sitds */
- if (!list_empty(&stream->free_list)) {
- sitd = list_entry (stream->free_list.prev,
+ if (likely(!list_empty(&stream->free_list))) {
+ sitd = list_first_entry(&stream->free_list,
struct ehci_sitd, sitd_list);
+ if (sitd->frame == ehci->now_frame)
+ goto alloc_sitd;
list_del (&sitd->sitd_list);
sitd_dma = sitd->sitd_dma;
} else {
+ alloc_sitd:
spin_unlock_irqrestore (&ehci->lock, flags);
sitd = dma_pool_alloc (ehci->sitd_pool, mem_flags,
&sitd_dma);
@@ -2034,8 +1986,7 @@ sitd_link (struct ehci_hcd *ehci, unsigned frame, struct ehci_sitd *sitd)
}
/* fit urb's sitds into the selected schedule slot; activate as needed */
-static int
-sitd_link_urb (
+static void sitd_link_urb(
struct ehci_hcd *ehci,
struct urb *urb,
unsigned mod,
@@ -2081,7 +2032,7 @@ sitd_link_urb (
sitd = list_entry (sched->td_list.next,
struct ehci_sitd, sitd_list);
list_move_tail (&sitd->sitd_list, &stream->td_list);
- sitd->stream = iso_stream_get (stream);
+ sitd->stream = stream;
sitd->urb = urb;
sitd_patch(ehci, stream, sitd, sched, packet);
@@ -2096,8 +2047,8 @@ sitd_link_urb (
iso_sched_free (stream, sched);
urb->hcpriv = NULL;
- timer_action (ehci, TIMER_IO_WATCHDOG);
- return enable_periodic(ehci);
+ ++ehci->isoc_count;
+ enable_periodic(ehci);
}
/*-------------------------------------------------------------------------*/
@@ -2115,18 +2066,15 @@ sitd_link_urb (
* (b) only this endpoint's completions submit URBs. It seems some silicon
* corrupts things if you reuse completed descriptors very quickly...
*/
-static unsigned
-sitd_complete (
- struct ehci_hcd *ehci,
- struct ehci_sitd *sitd
-) {
+static bool sitd_complete(struct ehci_hcd *ehci, struct ehci_sitd *sitd)
+{
struct urb *urb = sitd->urb;
struct usb_iso_packet_descriptor *desc;
u32 t;
int urb_index = -1;
struct ehci_iso_stream *stream = sitd->stream;
struct usb_device *dev;
- unsigned retval = false;
+ bool retval = false;
urb_index = sitd->index;
desc = &urb->iso_frame_desc [urb_index];
@@ -2163,9 +2111,11 @@ sitd_complete (
ehci_urb_done(ehci, urb, 0);
retval = true;
urb = NULL;
- (void) disable_periodic(ehci);
- ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--;
+ --ehci->isoc_count;
+ disable_periodic(ehci);
+
+ ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--;
if (ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs == 0) {
if (ehci->amd_pll_fix == 1)
usb_amd_quirk_pll_enable();
@@ -2179,28 +2129,20 @@ sitd_complete (
dev->devpath, stream->bEndpointAddress & 0x0f,
(stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out");
}
- iso_stream_put (ehci, stream);
done:
sitd->urb = NULL;
- if (ehci->clock_frame != sitd->frame) {
- /* OK to recycle this SITD now. */
- sitd->stream = NULL;
- list_move(&sitd->sitd_list, &stream->free_list);
- iso_stream_put(ehci, stream);
- } else {
- /* HW might remember this SITD, so we can't recycle it yet.
- * Move it to a safe place until a new frame starts.
- */
- list_move(&sitd->sitd_list, &ehci->cached_sitd_list);
- if (stream->refcount == 2) {
- /* If iso_stream_put() were called here, stream
- * would be freed. Instead, just prevent reuse.
- */
- stream->ep->hcpriv = NULL;
- stream->ep = NULL;
- }
+
+ /* Add to the end of the free list for later reuse */
+ list_move_tail(&sitd->sitd_list, &stream->free_list);
+
+ /* Recycle the siTDs when the pipeline is empty (ep no longer in use) */
+ if (list_empty(&stream->td_list)) {
+ list_splice_tail_init(&stream->free_list,
+ &ehci->cached_sitd_list);
+ start_free_itds(ehci);
}
+
return retval;
}
@@ -2254,74 +2196,39 @@ static int sitd_submit (struct ehci_hcd *ehci, struct urb *urb,
sitd_link_urb (ehci, urb, ehci->periodic_size << 3, stream);
else
usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb);
-done_not_linked:
+ done_not_linked:
spin_unlock_irqrestore (&ehci->lock, flags);
-
-done:
- if (status < 0)
- iso_stream_put (ehci, stream);
+ done:
return status;
}
/*-------------------------------------------------------------------------*/
-static void free_cached_lists(struct ehci_hcd *ehci)
+static void scan_isoc(struct ehci_hcd *ehci)
{
- struct ehci_itd *itd, *n;
- struct ehci_sitd *sitd, *sn;
-
- list_for_each_entry_safe(itd, n, &ehci->cached_itd_list, itd_list) {
- struct ehci_iso_stream *stream = itd->stream;
- itd->stream = NULL;
- list_move(&itd->itd_list, &stream->free_list);
- iso_stream_put(ehci, stream);
- }
-
- list_for_each_entry_safe(sitd, sn, &ehci->cached_sitd_list, sitd_list) {
- struct ehci_iso_stream *stream = sitd->stream;
- sitd->stream = NULL;
- list_move(&sitd->sitd_list, &stream->free_list);
- iso_stream_put(ehci, stream);
- }
-}
-
-/*-------------------------------------------------------------------------*/
-
-static void
-scan_periodic (struct ehci_hcd *ehci)
-{
- unsigned now_uframe, frame, clock, clock_frame, mod;
- unsigned modified;
-
- mod = ehci->periodic_size << 3;
+ unsigned uf, now_frame, frame;
+ unsigned fmask = ehci->periodic_size - 1;
+ bool modified, live;
/*
* When running, scan from last scan point up to "now"
* else clean up by scanning everything that's left.
* Touches as few pages as possible: cache-friendly.
*/
- now_uframe = ehci->next_uframe;
- if (ehci->rh_state == EHCI_RH_RUNNING) {
- clock = ehci_read_frame_index(ehci);
- clock_frame = (clock >> 3) & (ehci->periodic_size - 1);
+ if (ehci->rh_state >= EHCI_RH_RUNNING) {
+ uf = ehci_read_frame_index(ehci);
+ now_frame = (uf >> 3) & fmask;
+ live = true;
} else {
- clock = now_uframe + mod - 1;
- clock_frame = -1;
+ now_frame = (ehci->next_frame - 1) & fmask;
+ live = false;
}
- if (ehci->clock_frame != clock_frame) {
- free_cached_lists(ehci);
- ehci->clock_frame = clock_frame;
- }
- clock &= mod - 1;
- clock_frame = clock >> 3;
- ++ehci->periodic_stamp;
+ ehci->now_frame = now_frame;
+ frame = ehci->next_frame;
for (;;) {
union ehci_shadow q, *q_p;
__hc32 type, *hw_p;
- unsigned incomplete = false;
-
- frame = now_uframe >> 3;
restart:
/* scan each element in frame's queue for completions */
@@ -2329,48 +2236,17 @@ restart:
hw_p = &ehci->periodic [frame];
q.ptr = q_p->ptr;
type = Q_NEXT_TYPE(ehci, *hw_p);
- modified = 0;
+ modified = false;
while (q.ptr != NULL) {
- unsigned uf;
- union ehci_shadow temp;
- int live;
-
- live = (ehci->rh_state == EHCI_RH_RUNNING);
switch (hc32_to_cpu(ehci, type)) {
- case Q_TYPE_QH:
- /* handle any completions */
- temp.qh = qh_get (q.qh);
- type = Q_NEXT_TYPE(ehci, q.qh->hw->hw_next);
- q = q.qh->qh_next;
- if (temp.qh->stamp != ehci->periodic_stamp) {
- modified = qh_completions(ehci, temp.qh);
- if (!modified)
- temp.qh->stamp = ehci->periodic_stamp;
- if (unlikely(list_empty(&temp.qh->qtd_list) ||
- temp.qh->needs_rescan))
- intr_deschedule(ehci, temp.qh);
- }
- qh_put (temp.qh);
- break;
- case Q_TYPE_FSTN:
- /* for "save place" FSTNs, look at QH entries
- * in the previous frame for completions.
- */
- if (q.fstn->hw_prev != EHCI_LIST_END(ehci)) {
- ehci_dbg(ehci,
- "ignoring completions from FSTNs\n");
- }
- type = Q_NEXT_TYPE(ehci, q.fstn->hw_next);
- q = q.fstn->fstn_next;
- break;
case Q_TYPE_ITD:
/* If this ITD is still active, leave it for
* later processing ... check the next entry.
* No need to check for activity unless the
* frame is current.
*/
- if (frame == clock_frame && live) {
+ if (frame == now_frame && live) {
rmb();
for (uf = 0; uf < 8; uf++) {
if (q.itd->hw_transaction[uf] &
@@ -2378,7 +2254,6 @@ restart:
break;
}
if (uf < 8) {
- incomplete = true;
q_p = &q.itd->itd_next;
hw_p = &q.itd->hw_next;
type = Q_NEXT_TYPE(ehci,
@@ -2410,14 +2285,12 @@ restart:
* No need to check for activity unless the
* frame is current.
*/
- if (((frame == clock_frame) ||
- (((frame + 1) & (ehci->periodic_size - 1))
- == clock_frame))
+ if (((frame == now_frame) ||
+ (((frame + 1) & fmask) == now_frame))
&& live
&& (q.sitd->hw_results &
SITD_ACTIVE(ehci))) {
- incomplete = true;
q_p = &q.sitd->sitd_next;
hw_p = &q.sitd->hw_next;
type = Q_NEXT_TYPE(ehci,
@@ -2445,58 +2318,23 @@ restart:
ehci_dbg(ehci, "corrupt type %d frame %d shadow %p\n",
type, frame, q.ptr);
// BUG ();
+ /* FALL THROUGH */
+ case Q_TYPE_QH:
+ case Q_TYPE_FSTN:
+ /* End of the iTDs and siTDs */
q.ptr = NULL;
+ break;
}
/* assume completion callbacks modify the queue */
- if (unlikely (modified)) {
- if (likely(ehci->periodic_sched > 0))
- goto restart;
- /* short-circuit this scan */
- now_uframe = clock;
- break;
- }
+ if (unlikely(modified && ehci->isoc_count > 0))
+ goto restart;
}
- /* If we can tell we caught up to the hardware, stop now.
- * We can't advance our scan without collecting the ISO
- * transfers that are still pending in this frame.
- */
- if (incomplete && ehci->rh_state == EHCI_RH_RUNNING) {
- ehci->next_uframe = now_uframe;
+ /* Stop when we have reached the current frame */
+ if (frame == now_frame)
break;
- }
-
- // FIXME: this assumes we won't get lapped when
- // latencies climb; that should be rare, but...
- // detect it, and just go all the way around.
- // FLR might help detect this case, so long as latencies
- // don't exceed periodic_size msec (default 1.024 sec).
-
- // FIXME: likewise assumes HC doesn't halt mid-scan
-
- if (now_uframe == clock) {
- unsigned now;
-
- if (ehci->rh_state != EHCI_RH_RUNNING
- || ehci->periodic_sched == 0)
- break;
- ehci->next_uframe = now_uframe;
- now = ehci_read_frame_index(ehci) & (mod - 1);
- if (now_uframe == now)
- break;
-
- /* rescan the rest of this frame, then ... */
- clock = now;
- clock_frame = clock >> 3;
- if (ehci->clock_frame != clock_frame) {
- free_cached_lists(ehci);
- ehci->clock_frame = clock_frame;
- ++ehci->periodic_stamp;
- }
- } else {
- now_uframe++;
- now_uframe &= mod - 1;
- }
+ frame = (frame + 1) & fmask;
}
+ ehci->next_frame = now_frame;
}