aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hw/usb/hcd-ehci.c39
1 files changed, 28 insertions, 11 deletions
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index e7c36f4155..35eb441f1d 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -780,6 +780,14 @@ static void ehci_cancel_queue(EHCIQueue *q)
} while ((p = QTAILQ_FIRST(&q->packets)) != NULL);
}
+static void ehci_reset_queue(EHCIQueue *q)
+{
+ trace_usb_ehci_queue_action(q, "reset");
+ ehci_cancel_queue(q);
+ q->dev = NULL;
+ q->qtdaddr = 0;
+}
+
static void ehci_free_queue(EHCIQueue *q)
{
EHCIQueueHead *head = q->async ? &q->ehci->aqueues : &q->ehci->pqueues;
@@ -1755,8 +1763,9 @@ out:
static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
{
EHCIPacket *p;
- uint32_t entry, devaddr;
+ uint32_t entry, devaddr, endp;
EHCIQueue *q;
+ EHCIqh qh;
entry = ehci_get_fetch_addr(ehci, async);
q = ehci_find_queue_by_qh(ehci, entry, async);
@@ -1774,17 +1783,25 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
}
get_dwords(ehci, NLPTR_GET(q->qhaddr),
- (uint32_t *) &q->qh, sizeof(EHCIqh) >> 2);
- ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &q->qh);
+ (uint32_t *) &qh, sizeof(EHCIqh) >> 2);
+ ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &qh);
+
+ /*
+ * The overlay area of the qh should never be changed by the guest,
+ * except when idle, in which case the reset is a nop.
+ */
+ devaddr = get_field(qh.epchar, QH_EPCHAR_DEVADDR);
+ endp = get_field(qh.epchar, QH_EPCHAR_EP);
+ if ((devaddr != get_field(q->qh.epchar, QH_EPCHAR_DEVADDR)) ||
+ (endp != get_field(q->qh.epchar, QH_EPCHAR_EP)) ||
+ (memcmp(&qh.current_qtd, &q->qh.current_qtd,
+ 9 * sizeof(uint32_t)) != 0) ||
+ (q->dev != NULL && q->dev->addr != devaddr)) {
+ ehci_reset_queue(q);
+ p = NULL;
+ }
+ q->qh = qh;
- devaddr = get_field(q->qh.epchar, QH_EPCHAR_DEVADDR);
- if (q->dev != NULL && q->dev->addr != devaddr) {
- if (!QTAILQ_EMPTY(&q->packets)) {
- /* should not happen (guest bug) */
- ehci_cancel_queue(q);
- }
- q->dev = NULL;
- }
if (q->dev == NULL) {
q->dev = ehci_find_device(q->ehci, devaddr);
}