mirror of https://github.com/xqemu/xqemu.git
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
This commit is contained in:
parent
7512b13dd7
commit
94b037f2a4
|
@ -21,6 +21,7 @@
|
||||||
#include "qemu/osdep.h"
|
#include "qemu/osdep.h"
|
||||||
#include "hw/hw.h"
|
#include "hw/hw.h"
|
||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
|
#include "qemu/queue.h"
|
||||||
#include "hw/usb.h"
|
#include "hw/usb.h"
|
||||||
#include "hw/pci/pci.h"
|
#include "hw/pci/pci.h"
|
||||||
#include "hw/pci/msi.h"
|
#include "hw/pci/msi.h"
|
||||||
|
@ -46,8 +47,6 @@
|
||||||
#define MAXSLOTS 64
|
#define MAXSLOTS 64
|
||||||
#define MAXINTRS 16
|
#define MAXINTRS 16
|
||||||
|
|
||||||
#define TD_QUEUE 24
|
|
||||||
|
|
||||||
/* Very pessimistic, let's hope it's enough for all cases */
|
/* Very pessimistic, let's hope it's enough for all cases */
|
||||||
#define EV_QUEUE (((3 * 24) + 16) * MAXSLOTS)
|
#define EV_QUEUE (((3 * 24) + 16) * MAXSLOTS)
|
||||||
/* Do not deliver ER Full events. NEC's driver does some things not bound
|
/* Do not deliver ER Full events. NEC's driver does some things not bound
|
||||||
|
@ -346,6 +345,7 @@ typedef struct XHCIPort {
|
||||||
|
|
||||||
typedef struct XHCITransfer {
|
typedef struct XHCITransfer {
|
||||||
XHCIState *xhci;
|
XHCIState *xhci;
|
||||||
|
XHCIEPContext *epctx;
|
||||||
USBPacket packet;
|
USBPacket packet;
|
||||||
QEMUSGList sgl;
|
QEMUSGList sgl;
|
||||||
bool running_async;
|
bool running_async;
|
||||||
|
@ -361,7 +361,6 @@ typedef struct XHCITransfer {
|
||||||
bool timed_xfer;
|
bool timed_xfer;
|
||||||
|
|
||||||
unsigned int trb_count;
|
unsigned int trb_count;
|
||||||
unsigned int trb_alloced;
|
|
||||||
XHCITRB *trbs;
|
XHCITRB *trbs;
|
||||||
|
|
||||||
TRBCCode status;
|
TRBCCode status;
|
||||||
|
@ -371,6 +370,8 @@ typedef struct XHCITransfer {
|
||||||
unsigned int cur_pkt;
|
unsigned int cur_pkt;
|
||||||
|
|
||||||
uint64_t mfindex_kick;
|
uint64_t mfindex_kick;
|
||||||
|
|
||||||
|
QTAILQ_ENTRY(XHCITransfer) next;
|
||||||
} XHCITransfer;
|
} XHCITransfer;
|
||||||
|
|
||||||
struct XHCIStreamContext {
|
struct XHCIStreamContext {
|
||||||
|
@ -385,8 +386,8 @@ struct XHCIEPContext {
|
||||||
unsigned int epid;
|
unsigned int epid;
|
||||||
|
|
||||||
XHCIRing ring;
|
XHCIRing ring;
|
||||||
unsigned int next_xfer;
|
uint32_t xfer_count;
|
||||||
XHCITransfer transfers[TD_QUEUE];
|
QTAILQ_HEAD(, XHCITransfer) transfers;
|
||||||
XHCITransfer *retry;
|
XHCITransfer *retry;
|
||||||
EPType type;
|
EPType type;
|
||||||
dma_addr_t pctx;
|
dma_addr_t pctx;
|
||||||
|
@ -1370,19 +1371,13 @@ static XHCIEPContext *xhci_alloc_epctx(XHCIState *xhci,
|
||||||
unsigned int epid)
|
unsigned int epid)
|
||||||
{
|
{
|
||||||
XHCIEPContext *epctx;
|
XHCIEPContext *epctx;
|
||||||
int i;
|
|
||||||
|
|
||||||
epctx = g_new0(XHCIEPContext, 1);
|
epctx = g_new0(XHCIEPContext, 1);
|
||||||
epctx->xhci = xhci;
|
epctx->xhci = xhci;
|
||||||
epctx->slotid = slotid;
|
epctx->slotid = slotid;
|
||||||
epctx->epid = epid;
|
epctx->epid = epid;
|
||||||
|
|
||||||
for (i = 0; i < ARRAY_SIZE(epctx->transfers); i++) {
|
QTAILQ_INIT(&epctx->transfers);
|
||||||
epctx->transfers[i].xhci = xhci;
|
|
||||||
epctx->transfers[i].slotid = slotid;
|
|
||||||
epctx->transfers[i].epid = epid;
|
|
||||||
usb_packet_init(&epctx->transfers[i].packet);
|
|
||||||
}
|
|
||||||
epctx->kick_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, xhci_ep_kick_timer, epctx);
|
epctx->kick_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, xhci_ep_kick_timer, epctx);
|
||||||
|
|
||||||
return epctx;
|
return epctx;
|
||||||
|
@ -1443,6 +1438,41 @@ static TRBCCode xhci_enable_ep(XHCIState *xhci, unsigned int slotid,
|
||||||
return CC_SUCCESS;
|
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)
|
static int xhci_ep_nuke_one_xfer(XHCITransfer *t, TRBCCode report)
|
||||||
{
|
{
|
||||||
int killed = 0;
|
int killed = 0;
|
||||||
|
@ -1469,7 +1499,7 @@ static int xhci_ep_nuke_one_xfer(XHCITransfer *t, TRBCCode report)
|
||||||
g_free(t->trbs);
|
g_free(t->trbs);
|
||||||
|
|
||||||
t->trbs = NULL;
|
t->trbs = NULL;
|
||||||
t->trb_count = t->trb_alloced = 0;
|
t->trb_count = 0;
|
||||||
|
|
||||||
return killed;
|
return killed;
|
||||||
}
|
}
|
||||||
|
@ -1479,7 +1509,8 @@ static int xhci_ep_nuke_xfers(XHCIState *xhci, unsigned int slotid,
|
||||||
{
|
{
|
||||||
XHCISlot *slot;
|
XHCISlot *slot;
|
||||||
XHCIEPContext *epctx;
|
XHCIEPContext *epctx;
|
||||||
int i, xferi, killed = 0;
|
XHCITransfer *xfer;
|
||||||
|
int killed = 0;
|
||||||
USBEndpoint *ep = NULL;
|
USBEndpoint *ep = NULL;
|
||||||
assert(slotid >= 1 && slotid <= xhci->numslots);
|
assert(slotid >= 1 && slotid <= xhci->numslots);
|
||||||
assert(epid >= 1 && epid <= 31);
|
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];
|
epctx = slot->eps[epid-1];
|
||||||
|
|
||||||
xferi = epctx->next_xfer;
|
for (;;) {
|
||||||
for (i = 0; i < TD_QUEUE; i++) {
|
xfer = QTAILQ_FIRST(&epctx->transfers);
|
||||||
killed += xhci_ep_nuke_one_xfer(&epctx->transfers[xferi], report);
|
if (xfer == NULL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
killed += xhci_ep_nuke_one_xfer(xfer, report);
|
||||||
if (killed) {
|
if (killed) {
|
||||||
report = 0; /* Only report once */
|
report = 0; /* Only report once */
|
||||||
}
|
}
|
||||||
epctx->transfers[xferi].packet.ep = NULL;
|
xhci_ep_free_xfer(xfer);
|
||||||
xferi = (xferi + 1) % TD_QUEUE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ep = xhci_epid_to_usbep(xhci, slotid, epid);
|
ep = xhci_epid_to_usbep(xhci, slotid, epid);
|
||||||
|
@ -1516,7 +1549,6 @@ static TRBCCode xhci_disable_ep(XHCIState *xhci, unsigned int slotid,
|
||||||
{
|
{
|
||||||
XHCISlot *slot;
|
XHCISlot *slot;
|
||||||
XHCIEPContext *epctx;
|
XHCIEPContext *epctx;
|
||||||
int i;
|
|
||||||
|
|
||||||
trace_usb_xhci_ep_disable(slotid, epid);
|
trace_usb_xhci_ep_disable(slotid, epid);
|
||||||
assert(slotid >= 1 && slotid <= xhci->numslots);
|
assert(slotid >= 1 && slotid <= xhci->numslots);
|
||||||
|
@ -1537,10 +1569,6 @@ static TRBCCode xhci_disable_ep(XHCIState *xhci, unsigned int slotid,
|
||||||
xhci_free_streams(epctx);
|
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 */
|
/* only touch guest RAM if we're not resetting the HC */
|
||||||
if (xhci->dcbaap_low || xhci->dcbaap_high) {
|
if (xhci->dcbaap_low || xhci->dcbaap_high) {
|
||||||
xhci_set_ep_state(xhci, epctx, NULL, EP_DISABLED);
|
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;
|
XHCIStreamContext *stctx;
|
||||||
XHCIEPContext *epctx;
|
XHCIEPContext *epctx;
|
||||||
|
XHCITransfer *xfer;
|
||||||
XHCIRing *ring;
|
XHCIRing *ring;
|
||||||
USBEndpoint *ep = NULL;
|
USBEndpoint *ep = NULL;
|
||||||
uint64_t mfindex;
|
uint64_t mfindex;
|
||||||
|
@ -2168,6 +2197,7 @@ static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid,
|
||||||
xhci_complete_packet(xfer);
|
xhci_complete_packet(xfer);
|
||||||
}
|
}
|
||||||
assert(!xfer->running_retry);
|
assert(!xfer->running_retry);
|
||||||
|
xhci_ep_free_xfer(epctx->retry);
|
||||||
epctx->retry = NULL;
|
epctx->retry = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2193,27 +2223,14 @@ static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid,
|
||||||
assert(ring->dequeue != 0);
|
assert(ring->dequeue != 0);
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
XHCITransfer *xfer = &epctx->transfers[epctx->next_xfer];
|
|
||||||
if (xfer->running_async || xfer->running_retry) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
length = xhci_ring_chain_length(xhci, ring);
|
length = xhci_ring_chain_length(xhci, ring);
|
||||||
if (length < 0) {
|
if (length <= 0) {
|
||||||
break;
|
|
||||||
} else if (length == 0) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (xfer->trbs && xfer->trb_alloced < length) {
|
xfer = xhci_ep_alloc_xfer(epctx, length);
|
||||||
xfer->trb_count = 0;
|
if (xfer == NULL) {
|
||||||
xfer->trb_alloced = 0;
|
break;
|
||||||
g_free(xfer->trbs);
|
|
||||||
xfer->trbs = NULL;
|
|
||||||
}
|
}
|
||||||
if (!xfer->trbs) {
|
|
||||||
xfer->trbs = g_new(XHCITRB, length);
|
|
||||||
xfer->trb_alloced = length;
|
|
||||||
}
|
|
||||||
xfer->trb_count = length;
|
|
||||||
|
|
||||||
for (i = 0; i < length; i++) {
|
for (i = 0; i < length; i++) {
|
||||||
TRBType type;
|
TRBType type;
|
||||||
|
@ -2223,25 +2240,19 @@ static void xhci_kick_ep(XHCIState *xhci, unsigned int slotid,
|
||||||
xfer->streamid = streamid;
|
xfer->streamid = streamid;
|
||||||
|
|
||||||
if (epid == 1) {
|
if (epid == 1) {
|
||||||
if (xhci_fire_ctl_transfer(xhci, xfer) >= 0) {
|
xhci_fire_ctl_transfer(xhci, xfer);
|
||||||
epctx->next_xfer = (epctx->next_xfer + 1) % TD_QUEUE;
|
|
||||||
} else {
|
} else {
|
||||||
DPRINTF("xhci: error firing CTL transfer\n");
|
xhci_fire_transfer(xhci, xfer, epctx);
|
||||||
}
|
|
||||||
} 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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (xfer->complete) {
|
||||||
|
xhci_ep_free_xfer(xfer);
|
||||||
|
xfer = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (epctx->state == EP_HALTED) {
|
if (epctx->state == EP_HALTED) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (xfer->running_retry) {
|
if (xfer != NULL && xfer->running_retry) {
|
||||||
DPRINTF("xhci: xfer nacked, stopping schedule\n");
|
DPRINTF("xhci: xfer nacked, stopping schedule\n");
|
||||||
epctx->retry = xfer;
|
epctx->retry = xfer;
|
||||||
break;
|
break;
|
||||||
|
@ -3480,6 +3491,9 @@ static void xhci_complete(USBPort *port, USBPacket *packet)
|
||||||
}
|
}
|
||||||
xhci_complete_packet(xfer);
|
xhci_complete_packet(xfer);
|
||||||
xhci_kick_ep(xfer->xhci, xfer->slotid, xfer->epid, xfer->streamid);
|
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)
|
static void xhci_child_detach(USBPort *uport, USBDevice *child)
|
||||||
|
|
Loading…
Reference in New Issue