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