1b1995d
From 35562fb521547e081e732453a6395fc00d9ee9e4 Mon Sep 17 00:00:00 2001
1b1995d
From: Hans de Goede <hdegoede@redhat.com>
1b1995d
Date: Thu, 1 Mar 2012 15:20:17 +0100
1b1995d
Subject: [PATCH 130/140] usb-ehci: Drop cached qhs when the doorbell gets
1b1995d
 rung
1b1995d
1b1995d
The purpose of the IAAD bit / the doorbell is to make the ehci controller
1b1995d
forget about cached qhs, this is mainly used when cancelling transactions,
1b1995d
the qh is unlinked from the async schedule and then the doorbell gets rung,
1b1995d
once the doorbell is acked by the controller the hcd knows that the qh is
1b1995d
no longer in use and that it can do something else with the memory, such
1b1995d
as re-use it for a new qh! But we keep our struct representing this qh around
1b1995d
for circa 250 ms. This allows for a (mightily large) race window where the
1b1995d
following could happen:
1b1995d
-hcd submits a qh at address 0xdeadbeef
1b1995d
-our ehci code sees the qh, sends a request to a usb-device, gets a result
1b1995d
 of USB_RET_ASYNC, sets the async_state of the qh to EHCI_ASYNC_INFLIGHT
1b1995d
-hcd unlinks the qh at address 0xdeadbeef
1b1995d
-hcd rings the doorbell, wait for us to ack it
1b1995d
-hcd re-uses the qh at address 0xdeadbeef
1b1995d
-our ehci code sees the qh, looks in the async_queue, sees there already is
1b1995d
 a qh at address 0xdeadbeef there with async_state of EHCI_ASYNC_INFLIGHT,
1b1995d
 does nothing
1b1995d
-the *original* (which the hcd thinks it has cancelled) transaction finishes
1b1995d
-our ehci code sees the qh on yet another pass through the async list,
1b1995d
 looks in the async_queue, sees there already is a qh at address 0xdeadbeef
1b1995d
 there with async_state of EHCI_ASYNC_COMPLETED, and finished the transaction
1b1995d
 with the results of the *original* transaction.
1b1995d
1b1995d
Not good (tm), this patch fixes this race by removing all qhs which have not
1b1995d
been seen during the last cycle through the async list immidiately when the
1b1995d
doorbell is rung.
1b1995d
1b1995d
Note this patch does not fix any actually observed problem, but upon
1b1995d
reading of the EHCI spec it became apparent to me that the above race could
1b1995d
happen and the usb-ehci behavior from before this patch is not good.
1b1995d
1b1995d
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
1b1995d
---
1b1995d
 hw/usb-ehci.c |   31 ++++++++++++++++---------------
1b1995d
 1 file changed, 16 insertions(+), 15 deletions(-)
1b1995d
1b1995d
diff --git a/hw/usb-ehci.c b/hw/usb-ehci.c
1b1995d
index 422afc8..b8ba483 100644
1b1995d
--- a/hw/usb-ehci.c
1b1995d
+++ b/hw/usb-ehci.c
1b1995d
@@ -697,7 +697,7 @@ static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr,
1b1995d
     return NULL;
1b1995d
 }
1b1995d
 
1b1995d
-static void ehci_queues_rip_unused(EHCIState *ehci, int async)
1b1995d
+static void ehci_queues_rip_unused(EHCIState *ehci, int async, int flush)
1b1995d
 {
1b1995d
     EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
1b1995d
     EHCIQueue *q, *tmp;
1b1995d
@@ -708,7 +708,7 @@ static void ehci_queues_rip_unused(EHCIState *ehci, int async)
1b1995d
             q->ts = ehci->last_run_ns;
1b1995d
             continue;
1b1995d
         }
1b1995d
-        if (ehci->last_run_ns < q->ts + 250000000) {
1b1995d
+        if (!flush && ehci->last_run_ns < q->ts + 250000000) {
1b1995d
             /* allow 0.25 sec idle */
1b1995d
             continue;
1b1995d
         }
1b1995d
@@ -1565,7 +1565,7 @@ static int ehci_state_waitlisthead(EHCIState *ehci,  int async)
1b1995d
         ehci_set_usbsts(ehci, USBSTS_REC);
1b1995d
     }
1b1995d
 
1b1995d
-    ehci_queues_rip_unused(ehci, async);
1b1995d
+    ehci_queues_rip_unused(ehci, async, 0);
1b1995d
 
1b1995d
     /*  Find the head of the list (4.9.1.1) */
1b1995d
     for(i = 0; i < MAX_QH; i++) {
1b1995d
@@ -2121,18 +2121,7 @@ static void ehci_advance_async_state(EHCIState *ehci)
1b1995d
             break;
1b1995d
         }
1b1995d
 
1b1995d
-        /* If the doorbell is set, the guest wants to make a change to the
1b1995d
-         * schedule. The host controller needs to release cached data.
1b1995d
-         * (section 4.8.2)
1b1995d
-         */
1b1995d
-        if (ehci->usbcmd & USBCMD_IAAD) {
1b1995d
-            DPRINTF("ASYNC: doorbell request acknowledged\n");
1b1995d
-            ehci->usbcmd &= ~USBCMD_IAAD;
1b1995d
-            ehci_set_interrupt(ehci, USBSTS_IAA);
1b1995d
-            break;
1b1995d
-        }
1b1995d
-
1b1995d
-        /* make sure guest has acknowledged */
1b1995d
+        /* make sure guest has acknowledged the doorbell interrupt */
1b1995d
         /* TO-DO: is this really needed? */
1b1995d
         if (ehci->usbsts & USBSTS_IAA) {
1b1995d
             DPRINTF("IAA status bit still set.\n");
1b1995d
@@ -2146,6 +2135,18 @@ static void ehci_advance_async_state(EHCIState *ehci)
1b1995d
 
1b1995d
         ehci_set_state(ehci, async, EST_WAITLISTHEAD);
1b1995d
         ehci_advance_state(ehci, async);
1b1995d
+
1b1995d
+        /* If the doorbell is set, the guest wants to make a change to the
1b1995d
+         * schedule. The host controller needs to release cached data.
1b1995d
+         * (section 4.8.2)
1b1995d
+         */
1b1995d
+        if (ehci->usbcmd & USBCMD_IAAD) {
1b1995d
+            /* Remove all unseen qhs from the async qhs queue */
1b1995d
+            ehci_queues_rip_unused(ehci, async, 1);
1b1995d
+            DPRINTF("ASYNC: doorbell request acknowledged\n");
1b1995d
+            ehci->usbcmd &= ~USBCMD_IAAD;
1b1995d
+            ehci_set_interrupt(ehci, USBSTS_IAA);
1b1995d
+        }
1b1995d
         break;
1b1995d
 
1b1995d
     default:
1b1995d
-- 
1b1995d
1.7.9.3
1b1995d