diff options
author | Gerd Hoffmann <kraxel@redhat.com> | 2016-09-27 10:32:48 +0200 |
---|---|---|
committer | Gerd Hoffmann <kraxel@redhat.com> | 2016-10-12 12:37:31 +0200 |
commit | 94b037f2a451b3dc855f9f2c346e5049a361bd55 (patch) | |
tree | 393fd9925b78a355d507cbf194fa5538f2d217c2 /hw/usb | |
parent | 7512b13dd7f77c3e93a5b856eddf78378bddcc7f (diff) |
xhci: use linked list for transfers
xhci has a fixed number of 24 (TD_QUEUE) XHCITransfer structs per
endpoint, which turns out to be a problem for usb3 devices with 32 (or
more) bulk streams. xhci re-checks the trb rings on every finished
transfer to make sure it'll pick up any pending work. But that scheme
breaks in case the first transfer of a ring can't be started because we
ran out of XHCITransfer structs already.
So remove static XHCITransfer array from XHCIEPContext. Use a linked
list instead, and allocate/free XHCITransfer as needed. Add helper
functions to allocate & initialize and to cleanup & release
XHCITransfer structs. That also simplifies trb management, we never
have to realloc XHCITransfer->trbs because we don't reuse XHCITransfer
structs any more.
New dynamic limit for in-flight xhci transfers per endpoint is
number-of-streams + 16.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Message-id: 1474965172-30321-5-git-send-email-kraxel@redhat.com
Diffstat (limited to 'hw/usb')
-rw-r--r-- | hw/usb/hcd-xhci.c | 122 |
1 files changed, 68 insertions, 54 deletions
diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c index 3a035c8177..e14c2a8164 100644 --- a/hw/usb/hcd-xhci.c +++ b/hw/usb/hcd-xhci.c @@ -21,6 +21,7 @@ #include "qemu/osdep.h" #include "hw/hw.h" #include "qemu/timer.h" +#include "qemu/queue.h" #include "hw/usb.h" #include "hw/pci/pci.h" #include "hw/pci/msi.h" @@ -46,8 +47,6 @@ #define MAXSLOTS 64 #define MAXINTRS 16 -#define TD_QUEUE 24 - /* Very pessimistic, let's hope it's enough for all cases */ #define EV_QUEUE (((3 * 24) + 16) * MAXSLOTS) /* Do not deliver ER Full events. NEC's driver does some things not bound @@ -346,6 +345,7 @@ typedef struct XHCIPort { typedef struct XHCITransfer { XHCIState *xhci; + XHCIEPContext *epctx; USBPacket packet; QEMUSGList sgl; bool running_async; @@ -361,7 +361,6 @@ typedef struct XHCITransfer { bool timed_xfer; unsigned int trb_count; - unsigned int trb_alloced; XHCITRB *trbs; TRBCCode status; @@ -371,6 +370,8 @@ typedef struct XHCITransfer { unsigned int cur_pkt; uint64_t mfindex_kick; + + QTAILQ_ENTRY(XHCITransfer) next; } XHCITransfer; struct XHCIStreamContext { @@ -385,8 +386,8 @@ struct XHCIEPContext { unsigned int epid; XHCIRing ring; - unsigned int next_xfer; - XHCITransfer transfers[TD_QUEUE]; + uint32_t xfer_count; + QTAILQ_HEAD(, XHCITransfer) transfers; XHCITransfer *retry; EPType type; dma_addr_t pctx; @@ -1370,19 +1371,13 @@ static XHCIEPContext *xhci_alloc_epctx(XHCIState *xhci, unsigned int epid) { XHCIEPContext *epctx; - int i; epctx = g_new0(XHCIEPContext, 1); epctx->xhci = xhci; epctx->slotid = slotid; epctx->epid = epid; - for (i = 0; i < ARRAY_SIZE(epctx->transfers); i++) { - epctx->transfers[i].xhci = xhci; - epctx->transfers[i].slotid = slotid; - epctx->transfers[i].epid = epid; - usb_packet_init(&epctx->transfers[i].packet); - } + QTAILQ_INIT(&epctx->transfers); epctx->kick_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, xhci_ep_kick_timer, epctx); return epctx; @@ -1443,6 +1438,41 @@ static TRBCCode xhci_enable_ep(XHCIState *xhci, unsigned int slotid, return CC_SUCCESS; } +static XHCITransfer *xhci_ep_alloc_xfer(XHCIEPContext *epctx, + uint32_t length) +{ + uint32_t limit = epctx->nr_pstreams + 16; + XHCITransfer *xfer; + + if (epctx->xfer_count >= limit) { + return NULL; + } + + xfer = g_new0(XHCITransfer, 1); + xfer->xhci = epctx->xhci; + xfer->epctx = epctx; + xfer->slotid = epctx->slotid; + xfer->epid = epctx->epid; + xfer->trbs = g_new(XHCITRB, length); + xfer->trb_count = length; + usb_packet_init(&xfer->packet); + + QTAILQ_INSERT_TAIL(&epctx->transfers, xfer, next); + epctx->xfer_count++; + + return xfer; +} + +static void xhci_ep_free_xfer(XHCITransfer *xfer) +{ + QTAILQ_REMOVE(&xfer->epctx->transfers, xfer, next); + xfer->epctx->xfer_count--; + + usb_packet_cleanup(&xfer->packet); + g_free(xfer->trbs); + g_free(xfer); +} + static int xhci_ep_nuke_one_xfer(XHCITransfer *t, TRBCCode report) { int killed = 0; @@ -1469,7 +1499,7 @@ static int xhci_ep_nuke_one_xfer(XHCITransfer *t, TRBCCode report) g_free(t->trbs); t->trbs = NULL; - t->trb_count = t->trb_alloced = 0; + t->trb_count = 0; return killed; } @@ -1479,7 +1509,8 @@ static int xhci_ep_nuke_xfers(XHCIState *xhci, unsigned int slotid, { XHCISlot *slot; XHCIEPContext *epctx; - int i, xferi, killed = 0; + XHCITransfer *xfer; + int killed = 0; USBEndpoint *ep = NULL; assert(slotid >= 1 && slotid <= xhci->numslots); assert(epid >= 1 && epid <= 31); @@ -1494,14 +1525,16 @@ static int xhci_ep_nuke_xfers(XHCIState *xhci, unsigned int slotid, epctx = slot->eps[epid-1]; - xferi = epctx->next_xfer; - for (i = 0; i < TD_QUEUE; i++) { - killed += xhci_ep_nuke_one_xfer(&epctx->transfers[xferi], report); + for (;;) { + xfer = QTAILQ_FIRST(&epctx->transfers); + if (xfer == NULL) { + break; + } + killed += xhci_ep_nuke_one_xfer(xfer, report); if (killed) { report = 0; /* Only report once */ } - epctx->transfers[xferi].packet.ep = NULL; - xferi = (xferi + 1) % TD_QUEUE; + xhci_ep_free_xfer(xfer); } ep = xhci_epid_to_usbep(xhci, slotid, epid); @@ -1516,7 +1549,6 @@ static TRBCCode xhci_disable_ep(XHCIState *xhci, unsigned int slotid, { XHCISlot *slot; XHCIEPContext *epctx; - int i; trace_usb_xhci_ep_disable(slotid, epid); assert(slotid >= 1 && slotid <= xhci->numslots); @@ -1537,10 +1569,6 @@ static TRBCCode xhci_disable_ep(XHCIState *xhci, unsigned int slotid, xhci_free_streams(epctx); } - for (i = 0; i < ARRAY_SIZE(epctx->transfers); i++) { - usb_packet_cleanup(&epctx->transfers[i].packet); - } - /* only touch guest RAM if we're not resetting the HC */ if (xhci->dcbaap_low || xhci->dcbaap_high) { xhci_set_ep_state(xhci, epctx, NULL, EP_DISABLED); @@ -2104,6 +2132,7 @@ static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, { XHCIStreamContext *stctx; XHCIEPContext *epctx; + XHCITransfer *xfer; XHCIRing *ring; USBEndpoint *ep = NULL; uint64_t mfindex; @@ -2168,6 +2197,7 @@ static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, xhci_complete_packet(xfer); } assert(!xfer->running_retry); + xhci_ep_free_xfer(epctx->retry); epctx->retry = NULL; } @@ -2193,27 +2223,14 @@ static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, assert(ring->dequeue != 0); while (1) { - XHCITransfer *xfer = &epctx->transfers[epctx->next_xfer]; - if (xfer->running_async || xfer->running_retry) { - break; - } length = xhci_ring_chain_length(xhci, ring); - if (length < 0) { + if (length <= 0) { break; - } else if (length == 0) { - break; - } - if (xfer->trbs && xfer->trb_alloced < length) { - xfer->trb_count = 0; - xfer->trb_alloced = 0; - g_free(xfer->trbs); - xfer->trbs = NULL; } - if (!xfer->trbs) { - xfer->trbs = g_new(XHCITRB, length); - xfer->trb_alloced = length; + xfer = xhci_ep_alloc_xfer(epctx, length); + if (xfer == NULL) { + break; } - xfer->trb_count = length; for (i = 0; i < length; i++) { TRBType type; @@ -2223,25 +2240,19 @@ static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid, xfer->streamid = streamid; if (epid == 1) { - if (xhci_fire_ctl_transfer(xhci, xfer) >= 0) { - epctx->next_xfer = (epctx->next_xfer + 1) % TD_QUEUE; - } else { - DPRINTF("xhci: error firing CTL transfer\n"); - } + xhci_fire_ctl_transfer(xhci, xfer); } else { - if (xhci_fire_transfer(xhci, xfer, epctx) >= 0) { - epctx->next_xfer = (epctx->next_xfer + 1) % TD_QUEUE; - } else { - if (!xfer->timed_xfer) { - DPRINTF("xhci: error firing data transfer\n"); - } - } + xhci_fire_transfer(xhci, xfer, epctx); + } + if (xfer->complete) { + xhci_ep_free_xfer(xfer); + xfer = NULL; } if (epctx->state == EP_HALTED) { break; } - if (xfer->running_retry) { + if (xfer != NULL && xfer->running_retry) { DPRINTF("xhci: xfer nacked, stopping schedule\n"); epctx->retry = xfer; break; @@ -3480,6 +3491,9 @@ static void xhci_complete(USBPort *port, USBPacket *packet) } xhci_complete_packet(xfer); xhci_kick_ep(xfer->xhci, xfer->slotid, xfer->epid, xfer->streamid); + if (xfer->complete) { + xhci_ep_free_xfer(xfer); + } } static void xhci_child_detach(USBPort *uport, USBDevice *child) |