USB: xhci: Minimize HW event ring dequeue pointer writes.
authorSarah Sharp <sarah.a.sharp@linux.intel.com>
Fri, 30 Jul 2010 05:12:49 +0000 (22:12 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Tue, 10 Aug 2010 21:35:44 +0000 (14:35 -0700)
The xHCI specification suggests that writing the hardware event ring dequeue
pointer register too often can be an expensive operation for the xHCI hardware
to manage.  It suggests minimizing the number of writes to that register.

Originally, the driver wrote the event ring dequeue pointer after each
event was processed.  Depending on how the event ring moderation register
is set up and how fast the transfers are completing, there may be several
events processed for each interrupt.  This patch makes the hardware event
ring dequeue pointer be written only once per interrupt.

Make the transfer event handler and port status event handler only write
the software event ring dequeue pointer.  Move the updating of the
hardware event ring dequeue pointer into the interrupt function.  Move the
contents of xhci_set_hc_event_deq() into the interrupt handler.  The
interrupt handler must clear the event handler busy flag, so it might as
well also write the dequeue pointer to the same register.  This eliminates
two 32-bit PCI reads and two 32-bit PCI writes.

Reported-by: Andiry Xu <andiry.xu@amd.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/host/xhci-ring.c

index 3b962ba5ef085490e4f12e4a4819ac3ff5b239f1..7dfd17707d53b34c7caca04d35e5465bc8315d28 100644 (file)
@@ -1184,7 +1184,6 @@ static void handle_port_status(struct xhci_hcd *xhci,
 
        /* Update event ring dequeue pointer before dropping the lock */
        inc_deq(xhci, xhci->event_ring, true);
-       xhci_set_hc_event_deq(xhci);
 
        spin_unlock(&xhci->lock);
        /* Pass this up to the core */
@@ -1924,7 +1923,6 @@ cleanup:
                 */
                if (trb_comp_code == COMP_MISSED_INT || !ep->skip) {
                        inc_deq(xhci, xhci->event_ring, true);
-                       xhci_set_hc_event_deq(xhci);
                }
 
                if (ret) {
@@ -2022,11 +2020,10 @@ static void xhci_handle_event(struct xhci_hcd *xhci)
                return;
        }
 
-       if (update_ptrs) {
-               /* Update SW and HC event ring dequeue pointer */
+       if (update_ptrs)
+               /* Update SW event ring dequeue pointer */
                inc_deq(xhci, xhci->event_ring, true);
-               xhci_set_hc_event_deq(xhci);
-       }
+
        /* Are there more items on the event ring? */
        xhci_handle_event(xhci);
 }
@@ -2042,6 +2039,8 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
        u32 status, irq_pending;
        union xhci_trb *trb;
        u64 temp_64;
+       union xhci_trb *event_ring_deq;
+       dma_addr_t deq;
 
        spin_lock(&xhci->lock);
        trb = xhci->event_ring->dequeue;
@@ -2090,18 +2089,43 @@ hw_died:
        irq_pending |= 0x3;
        xhci_writel(xhci, irq_pending, &xhci->ir_set->irq_pending);
 
-       if (xhci->xhc_state & XHCI_STATE_DYING)
+       if (xhci->xhc_state & XHCI_STATE_DYING) {
                xhci_dbg(xhci, "xHCI dying, ignoring interrupt. "
                                "Shouldn't IRQs be disabled?\n");
-       else
-               /* FIXME this should be a delayed service routine
-                * that clears the EHB.
+               /* Clear the event handler busy flag (RW1C);
+                * the event ring should be empty.
                 */
-               xhci_handle_event(xhci);
+               temp_64 = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue);
+               xhci_write_64(xhci, temp_64 | ERST_EHB,
+                               &xhci->ir_set->erst_dequeue);
+               spin_unlock(&xhci->lock);
+
+               return IRQ_HANDLED;
+       }
+
+       event_ring_deq = xhci->event_ring->dequeue;
+       /* FIXME this should be a delayed service routine
+        * that clears the EHB.
+        */
+       xhci_handle_event(xhci);
 
-       /* Clear the event handler busy flag (RW1C); event ring is empty. */
        temp_64 = xhci_read_64(xhci, &xhci->ir_set->erst_dequeue);
-       xhci_write_64(xhci, temp_64 | ERST_EHB, &xhci->ir_set->erst_dequeue);
+       /* If necessary, update the HW's version of the event ring deq ptr. */
+       if (event_ring_deq != xhci->event_ring->dequeue) {
+               deq = xhci_trb_virt_to_dma(xhci->event_ring->deq_seg,
+                               xhci->event_ring->dequeue);
+               if (deq == 0)
+                       xhci_warn(xhci, "WARN something wrong with SW event "
+                                       "ring dequeue ptr.\n");
+               /* Update HC event ring dequeue pointer */
+               temp_64 &= ERST_PTR_MASK;
+               temp_64 |= ((u64) deq & (u64) ~ERST_PTR_MASK);
+       }
+
+       /* Clear the event handler busy flag (RW1C); event ring is empty. */
+       temp_64 |= ERST_EHB;
+       xhci_write_64(xhci, temp_64, &xhci->ir_set->erst_dequeue);
+
        spin_unlock(&xhci->lock);
 
        return IRQ_HANDLED;