aboutsummaryrefslogtreecommitdiff
path: root/hw/usb
diff options
context:
space:
mode:
authorGerd Hoffmann <kraxel@redhat.com>2013-08-28 11:38:44 +0200
committerGerd Hoffmann <kraxel@redhat.com>2013-09-02 11:06:19 +0200
commit4d7a81c06f5f17e019a2d3a18300500bd64f6f40 (patch)
tree910cf09dbb02014a641f5ffb3a079e8d6f6bd58c /hw/usb
parentca7162782a293f525633e5816470498dd86a51cf (diff)
xhci: emulate intr endpoint intervals correctly
Respect the interval for interrupt endpoints, so we don't finish transfers as fast as possible but at the rate configured by the guest. Fixes guest deadlocks triggered by interrupt storms. Cc: Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Diffstat (limited to 'hw/usb')
-rw-r--r--hw/usb/hcd-xhci.c44
1 files changed, 37 insertions, 7 deletions
diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c
index 38269793b3..2e2eb55d06 100644
--- a/hw/usb/hcd-xhci.c
+++ b/hw/usb/hcd-xhci.c
@@ -355,6 +355,7 @@ typedef struct XHCITransfer {
unsigned int streamid;
bool in_xfer;
bool iso_xfer;
+ bool timed_xfer;
unsigned int trb_count;
unsigned int trb_alloced;
@@ -1820,6 +1821,7 @@ static int xhci_fire_ctl_transfer(XHCIState *xhci, XHCITransfer *xfer)
xfer->in_xfer = bmRequestType & USB_DIR_IN;
xfer->iso_xfer = false;
+ xfer->timed_xfer = false;
if (xhci_setup_packet(xfer) < 0) {
return -1;
@@ -1835,6 +1837,17 @@ static int xhci_fire_ctl_transfer(XHCIState *xhci, XHCITransfer *xfer)
return 0;
}
+static void xhci_calc_intr_kick(XHCIState *xhci, XHCITransfer *xfer,
+ XHCIEPContext *epctx, uint64_t mfindex)
+{
+ uint64_t asap = ((mfindex + epctx->interval - 1) &
+ ~(epctx->interval-1));
+ uint64_t kick = epctx->mfindex_last + epctx->interval;
+
+ assert(epctx->interval != 0);
+ xfer->mfindex_kick = MAX(asap, kick);
+}
+
static void xhci_calc_iso_kick(XHCIState *xhci, XHCITransfer *xfer,
XHCIEPContext *epctx, uint64_t mfindex)
{
@@ -1857,8 +1870,8 @@ static void xhci_calc_iso_kick(XHCIState *xhci, XHCITransfer *xfer,
}
}
-static void xhci_check_iso_kick(XHCIState *xhci, XHCITransfer *xfer,
- XHCIEPContext *epctx, uint64_t mfindex)
+static void xhci_check_intr_iso_kick(XHCIState *xhci, XHCITransfer *xfer,
+ XHCIEPContext *epctx, uint64_t mfindex)
{
if (xfer->mfindex_kick > mfindex) {
timer_mod(epctx->kick_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
@@ -1883,18 +1896,30 @@ static int xhci_submit(XHCIState *xhci, XHCITransfer *xfer, XHCIEPContext *epctx
switch(epctx->type) {
case ET_INTR_OUT:
case ET_INTR_IN:
+ xfer->pkts = 0;
+ xfer->iso_xfer = false;
+ xfer->timed_xfer = true;
+ mfindex = xhci_mfindex_get(xhci);
+ xhci_calc_intr_kick(xhci, xfer, epctx, mfindex);
+ xhci_check_intr_iso_kick(xhci, xfer, epctx, mfindex);
+ if (xfer->running_retry) {
+ return -1;
+ }
+ break;
case ET_BULK_OUT:
case ET_BULK_IN:
xfer->pkts = 0;
xfer->iso_xfer = false;
+ xfer->timed_xfer = false;
break;
case ET_ISO_OUT:
case ET_ISO_IN:
xfer->pkts = 1;
xfer->iso_xfer = true;
+ xfer->timed_xfer = true;
mfindex = xhci_mfindex_get(xhci);
xhci_calc_iso_kick(xhci, xfer, epctx, mfindex);
- xhci_check_iso_kick(xhci, xfer, epctx, mfindex);
+ xhci_check_intr_iso_kick(xhci, xfer, epctx, mfindex);
if (xfer->running_retry) {
return -1;
}
@@ -1955,13 +1980,18 @@ static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid,
trace_usb_xhci_xfer_retry(xfer);
assert(xfer->running_retry);
- if (xfer->iso_xfer) {
- /* retry delayed iso transfer */
+ if (xfer->timed_xfer) {
+ /* time to kick the transfer? */
mfindex = xhci_mfindex_get(xhci);
- xhci_check_iso_kick(xhci, xfer, epctx, mfindex);
+ xhci_check_intr_iso_kick(xhci, xfer, epctx, mfindex);
if (xfer->running_retry) {
return;
}
+ xfer->timed_xfer = 0;
+ xfer->running_retry = 1;
+ }
+ if (xfer->iso_xfer) {
+ /* retry iso transfer */
if (xhci_setup_packet(xfer) < 0) {
return;
}
@@ -2047,7 +2077,7 @@ static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid,
epctx->next_xfer = (epctx->next_xfer + 1) % TD_QUEUE;
ep = xfer->packet.ep;
} else {
- if (!xfer->iso_xfer) {
+ if (!xfer->timed_xfer) {
fprintf(stderr, "xhci: error firing data transfer\n");
}
}