diff --git a/devicemodel/hw/pci/xhci.c b/devicemodel/hw/pci/xhci.c index d75bb9bff..4519cf3e6 100644 --- a/devicemodel/hw/pci/xhci.c +++ b/devicemodel/hw/pci/xhci.c @@ -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"); diff --git a/devicemodel/hw/platform/usb_pmapper.c b/devicemodel/hw/platform/usb_pmapper.c index 93f537b55..46654f8cd 100755 --- a/devicemodel/hw/platform/usb_pmapper.c +++ b/devicemodel/hw/platform/usb_pmapper.c @@ -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);