DM USB: xHCI: use new isoch transfer implementation

The old implementation processes isoch TRB one by one, this method
can't support scenario which needs high performance, such as real
time USB camera video.

New implementions will compose all the isoch TRBs for one Door Bell
Ring, and give them to libusb as a single request. The test result
shows that this method could greatly improve the porfermance.

Tracked-On: #3054
Signed-off-by: Xiaoguang Wu <xiaoguang.wu@intel.com>
Acked-by: Yu Wang <yu1.wang@intel.com>
This commit is contained in:
Xiaoguang Wu 2019-06-05 15:26:39 +08:00 committed by ACRN System Integration
parent b57f6f9243
commit 7dbde27615
2 changed files with 160 additions and 121 deletions

View File

@ -2586,6 +2586,7 @@ pci_xhci_xfer_complete(struct pci_xhci_vdev *xdev,
uint32_t edtla;
uint32_t i;
int err = XHCI_TRB_ERROR_SUCCESS;
int rem_len = 0;
dev_ctx = pci_xhci_get_dev_ctx(xdev, slot);
@ -2626,11 +2627,12 @@ pci_xhci_xfer_complete(struct pci_xhci_vdev *xdev,
trbflags = trb->dwTrb3;
UPRINTF(LDBG, "xfer[%d] done?%u:%d trb %x %016lx %x "
"(err %d) IOC?%d\r\n",
"(err %d) IOC?%d, chained %d\r\n",
i, xfer->data[i].processed, xfer->data[i].blen,
XHCI_TRB_3_TYPE_GET(trbflags), evtrb.qwTrb0,
trbflags, err,
trb->dwTrb3 & XHCI_TRB_3_IOC_BIT ? 1 : 0);
trb->dwTrb3 & XHCI_TRB_3_IOC_BIT ? 1 : 0,
xfer->data[i].chained);
if (xfer->data[i].processed < USB_XFER_BLK_HANDLED) {
xfer->head = (int)i;
@ -2643,6 +2645,21 @@ pci_xhci_xfer_complete(struct pci_xhci_vdev *xdev,
edtla += xfer->data[i].bdone;
trb->dwTrb3 = (trb->dwTrb3 & ~0x1) | (xfer->data[i].ccs);
if (xfer->data[i].chained == 1) {
rem_len += xfer->data[i].blen;
i = (i + 1) % USB_MAX_XFER_BLOCKS;
/* When the chained == 1, this 'continue' will delay
* the IOC behavior which could decrease the number
* of virtual interrupts. This could GREATLY improve
* the performance especially under ISOCH scenario.
*/
continue;
} else
rem_len += xfer->data[i].blen;
if (rem_len > 0)
err = XHCI_TRB_ERROR_SHORT_PKT;
/* Only interrupt if IOC or short packet */
if (!(trb->dwTrb3 & XHCI_TRB_3_IOC_BIT) &&
@ -2654,7 +2671,7 @@ pci_xhci_xfer_complete(struct pci_xhci_vdev *xdev,
}
evtrb.dwTrb2 = XHCI_TRB_2_ERROR_SET(err) |
XHCI_TRB_2_REM_SET(xfer->data[i].blen);
XHCI_TRB_2_REM_SET(rem_len);
evtrb.dwTrb3 = XHCI_TRB_3_TYPE_SET(XHCI_TRB_EVENT_TRANSFER) |
XHCI_TRB_3_SLOT_SET(slot) | XHCI_TRB_3_EP_SET(epid);
@ -2675,6 +2692,7 @@ pci_xhci_xfer_complete(struct pci_xhci_vdev *xdev,
break;
i = (i + 1) % USB_MAX_XFER_BLOCKS;
rem_len = 0;
}
return err;
@ -2782,6 +2800,7 @@ pci_xhci_handle_transfer(struct pci_xhci_vdev *xdev,
uint32_t trbflags;
int do_intr, err;
int do_retry;
bool is_isoch = false;
ep_ctx->dwEpCtx0 = FIELD_REPLACE(ep_ctx->dwEpCtx0,
XHCI_ST_EPCTX_RUNNING, 0x7, 0);
@ -2858,8 +2877,11 @@ retry:
xfer_block->processed = USB_XFER_BLK_HANDLED;
break;
case XHCI_TRB_TYPE_NORMAL:
case XHCI_TRB_TYPE_ISOCH:
is_isoch = true;
/* fall through */
case XHCI_TRB_TYPE_NORMAL:
if (setup_trb != NULL) {
UPRINTF(LWRN, "trb not supposed to be in "
"ctl scope\r\n");
@ -2931,6 +2953,10 @@ retry:
xfer_block->trbnext, xfer_block->ccs);
}
if (is_isoch == true || XHCI_TRB_3_TYPE_GET(trbflags) ==
XHCI_TRB_TYPE_EVENT_DATA /* win10 needs it */)
continue;
/* handle current batch that requires interrupt on complete */
if (trbflags & XHCI_TRB_3_IOC_BIT) {
UPRINTF(LDBG, "trb IOC bit set\r\n");

View File

@ -175,16 +175,18 @@ usb_dev_comp_req(struct libusb_transfer *trn)
struct usb_dev_req *r;
struct usb_data_xfer *xfer;
struct usb_data_xfer_block *block;
int len, do_intr = 0, short_data = 0;
int i, idx, buf_idx, done;
int do_intr = 0;
int i, j, idx, buf_idx, done;
int bstart, bcount;
int is_stalled = 0;
int framelen = 0;
uint16_t maxp;
uint8_t *buf;
assert(trn);
/* async request */
r = trn->user_data;
len = trn->actual_length;
assert(r);
assert(r->udev);
@ -193,14 +195,19 @@ usb_dev_comp_req(struct libusb_transfer *trn)
assert(xfer);
assert(xfer->dev);
maxp = usb_dev_get_ep_maxp(r->udev, r->in, xfer->epid / 2);
if (trn->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
/* got the isoc frame length */
framelen = USB_EP_MAXP_SZ(maxp) * (1 + USB_EP_MAXP_MT(maxp));
UPRINTF(LDBG, "iso maxp %u framelen %d\r\n", maxp, framelen);
}
bstart = r->blk_start;
bcount = r->blk_count;
UPRINTF(LDBG, "%s: actual_length %d ep%d-transfer (%d-%d %d) request-%d"
" (%d-%d %d) status %d\r\n", __func__, len, xfer->epid,
UPRINTF(LDBG, "%s: actlen %d ep%d-xfr [%d-%d %d] rq-%d [%d-%d %d] st %d"
"\r\n", __func__, trn->actual_length, xfer->epid,
xfer->head, (xfer->tail - 1) % USB_MAX_XFER_BLOCKS,
xfer->ndata, r->seq, bstart, (bstart + bcount - 1) %
USB_MAX_XFER_BLOCKS, r->buf_length,
trn->status);
USB_MAX_XFER_BLOCKS, r->buf_length, trn->status);
/* lock for protecting the transfer */
USB_DATA_XFER_LOCK(xfer);
@ -235,63 +242,57 @@ usb_dev_comp_req(struct libusb_transfer *trn)
break;
}
if (trn->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
for (i = 0; i < trn->num_iso_packets; i++) {
struct libusb_iso_packet_descriptor *p =
&trn->iso_packet_desc[i];
len += p->actual_length;
UPRINTF(LDBG, "packet%u length %u actual_length %u\n",
i, p->length, p->actual_length);
}
}
for (i = 0; i < trn->num_iso_packets; i++)
UPRINTF(LDBG, "iso_frame %d len %u act_len %u\n", i,
trn->iso_packet_desc[i].length,
trn->iso_packet_desc[i].actual_length);
/* handle the blocks belong to this request */
i = 0;
i = j = 0;
buf_idx = 0;
idx = r->blk_start;
buf = r->buffer;
done = trn->actual_length;
while (i < r->blk_count) {
done = 0;
block = &xfer->data[idx % USB_MAX_XFER_BLOCKS];
/* Link TRB need to be skipped */
if (!block->buf || !block->blen) {
/* FIXME: should change hard coded USB_MAX_XFER_BLOCKS
* to dynamically mechanism to avoid dead loop.
*/
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
continue;
}
if (len >= buf_idx) {
done = block->blen;
if (done > len - buf_idx) {
done = len - buf_idx;
short_data = 1;
}
if (r->in)
memcpy(block->buf, &r->buffer[buf_idx], done);
}
assert(block->processed);
buf_idx += done;
block->bdone = done;
block->blen -= done;
block->processed = USB_XFER_BLK_HANDLED;
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
if (trn->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
/* For isoc OUT transfer, the libusb_xfer->actual_length
* always return zero, so here set block->blen = 0
* forcely and native xhci driver will not complain
* about short packet.
*/
if (!r->in) {
block->bdone = done;
block->blen = 0;
}
buf_idx = 0;
buf = libusb_get_iso_packet_buffer_simple(trn, j);
done = trn->iso_packet_desc[j].actual_length;
j++;
}
i++;
do {
int d;
if (i >= r->blk_count)
break;
block = &xfer->data[idx % USB_MAX_XFER_BLOCKS];
assert(block->processed);
d = done;
if (d > block->blen)
d = block->blen;
if (block->buf) {
if (r->in == TOKEN_IN) {
memcpy(block->buf, buf + buf_idx, d);
buf_idx += d;
}
} else {
/* Link TRB */
i--;
j--;
}
done -= d;
block->blen -= d;
block->bdone = d;
block->processed = USB_XFER_BLK_HANDLED;
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
i++;
} while (block->chained == 1);
}
stall_out:
@ -302,9 +303,6 @@ stall_out:
}
}
if (short_data)
xfer->status = USB_ERR_SHORT_XFER;
out:
/* notify the USB core this transfer is over */
if (g_ctx.notify_cb)
@ -773,10 +771,12 @@ usb_dev_data(void *pdata, struct usb_data_xfer *xfer, int dir, int epctx)
int rc = 0, epid;
uint8_t type;
int blk_start, data_size, blk_count;
int retries = 3, i, j, buf_idx;
int i, j, idx, buf_idx;
struct usb_data_xfer_block *b;
static const char * const type_str[] = {"CTRL", "ISO", "BULK", "INT"};
static const char * const dir_str[] = {"OUT", "IN"};
int framelen = 0, framecnt = 0;
uint16_t maxp;
udev = pdata;
assert(udev);
@ -787,31 +787,52 @@ usb_dev_data(void *pdata, struct usb_data_xfer *xfer, int dir, int epctx)
goto done;
type = usb_dev_get_ep_type(udev, dir ? TOKEN_IN : TOKEN_OUT, epctx);
epid = dir ? (0x80 | epctx) : epctx;
if (!(dir == USB_XFER_IN || dir == USB_XFER_OUT) ||
type > USB_ENDPOINT_INT) {
if (type > USB_ENDPOINT_INT) {
xfer->status = USB_ERR_IOERROR;
goto done;
}
epid = dir ? (0x80 | epctx) : epctx;
if (!(dir == USB_XFER_IN || dir == USB_XFER_OUT)) {
xfer->status = USB_ERR_IOERROR;
goto done;
}
maxp = usb_dev_get_ep_maxp(udev, dir, epctx);
if (type == USB_ENDPOINT_ISOC) {
/* need to double check it, there might be some non-spec
* compatible usb devices in the market.
*/
framelen = USB_EP_MAXP_SZ(maxp) * (1 + USB_EP_MAXP_MT(maxp));
UPRINTF(LDBG, "iso maxp %u framelen %d\r\n", maxp, framelen);
for (i = 0, idx = blk_start; i < blk_count; i++) {
if (xfer->data[idx].blen > framelen)
UPRINTF(LFTL, "err framelen %d\r\n", framelen);
if (xfer->data[idx].blen <= 0) {
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
i--;
continue;
}
if (xfer->data[idx].chained == 1) {
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
continue;
}
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
framecnt++;
}
UPRINTF(LDBG, "iso maxp %u framelen %d, framecnt %d\r\n", maxp,
framelen, framecnt);
}
if (data_size <= 0)
goto done;
/* TODO:
* need to check performance effect of 'type == USB_ENDPOINT_ISOC'.
* With this implementation, there should be some performance loss.
*
* In the native OS, the driver dose it this way:
* Chunk of Data -> 1 URB (with multi-TRBs) -> physical device
* With the current design, it works according the following way:
* Chunk of Data (UOS) -> 1 URB (with multi-TRBs) (UOS) -> DM ->
* multi-URBs (SOS) -> physical device.
* Currently, this design works fine for playback and record of USB
* headset, need to do more analysis.
*/
r = usb_dev_alloc_req(udev, xfer, dir, data_size, type ==
USB_ENDPOINT_ISOC ? 1 : 0);
USB_ENDPOINT_ISOC ? framecnt : 0);
if (!r) {
xfer->status = USB_ERR_IOERROR;
goto done;
@ -838,51 +859,42 @@ usb_dev_data(void *pdata, struct usb_data_xfer *xfer, int dir, int epctx)
}
}
if (type == USB_ENDPOINT_ISOC) {
for (i = 0, j = 0, idx = blk_start; i < blk_count; ++i) {
int len = xfer->data[idx].blen;
if (len <= 0) {
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
i--;
continue;
}
if (xfer->data[idx].chained == 1) {
r->trn->iso_packet_desc[j].length += len;
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
continue;
}
r->trn->iso_packet_desc[j].length += len;
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
UPRINTF(LDBG, "desc[%d].length %d\r\n", j,
r->trn->iso_packet_desc[j].length);
j++;
}
}
if (type == USB_ENDPOINT_BULK) {
/*
* give data to physical device through libusb.
* This is an asynchronous process, data is sent to libusb.so,
* and it may be not sent to physical device instantly, but
* just return here. After the data is really received by the
* physical device, the callback function usb_dev_comp_req
* will be triggered.
*/
/*
* TODO: Is there any risk of data missing?
*/
libusb_fill_bulk_transfer(r->trn,
udev->handle, epid,
r->buffer,
data_size,
usb_dev_comp_req,
r,
0);
do {
rc = libusb_submit_transfer(r->trn);
} while (rc && retries--);
libusb_fill_bulk_transfer(r->trn, udev->handle, epid,
r->buffer, data_size, usb_dev_comp_req, r, 0);
} else if (type == USB_ENDPOINT_INT) {
/* give data to physical device through libusb */
libusb_fill_interrupt_transfer(r->trn,
udev->handle,
epid,
r->buffer,
data_size,
usb_dev_comp_req,
r,
0);
rc = libusb_submit_transfer(r->trn);
libusb_fill_interrupt_transfer(r->trn, udev->handle, epid,
r->buffer, data_size, usb_dev_comp_req, r, 0);
} else if (type == USB_ENDPOINT_ISOC) {
/* TODO: Current design is to convert every UOS trb into SOS
* urb. It works fine, but potential issues and performance
* effect should be investigated in detail.
*/
libusb_fill_iso_transfer(r->trn, udev->handle,
epid, r->buffer, data_size, 1,
libusb_fill_iso_transfer(r->trn, udev->handle, epid,
r->buffer, data_size, framecnt,
usb_dev_comp_req, r, 0);
libusb_set_iso_packet_lengths(r->trn, data_size);
rc = libusb_submit_transfer(r->trn);
} else {
UPRINTF(LFTL, "%s: wrong endpoint type %d\r\n", __func__, type);
@ -894,6 +906,7 @@ usb_dev_data(void *pdata, struct usb_data_xfer *xfer, int dir, int epctx)
xfer->status = USB_ERR_INVAL;
}
rc = libusb_submit_transfer(r->trn);
if (rc) {
xfer->status = USB_ERR_IOERROR;
UPRINTF(LDBG, "libusb_submit_transfer fail: %d\n", rc);