From 42d8fa899c6a6f136daa761e7718003f08f723e7 Mon Sep 17 00:00:00 2001 From: Austin Phillips Date: Sat, 7 Apr 2018 16:06:03 +1000 Subject: [PATCH] stlink_usb: Submit multiple USB URBs at once to improve performance Commands to stlink devices are typically comprised of multiple transactions with each transaction completing before moving to the next. This change allows for multiple USB transactions to be issued at once followed by a check that all transactions completed successfully. This improves performance on some machines where there is a large turn-around time between USB transfers such as is seen on some virtual machines. This change is only supported when compiled with libusb1 as libusb1 supports and asynchronous interface. Multi-transaction queueing introduced in this change paves the way for improving speed of other transactions in the future such as memory and register reads where multiple USB transactions in succession are required to complete a command. Multiple USB transactions can be submitted at once using jtag_libusb_bulk_transfer_n function. Change-Id: I924e049217a789ef445b14e00aa1983576970fbf Signed-off-by: Austin Phillips Reviewed-on: http://openocd.zylin.com/4484 Tested-by: jenkins Reviewed-by: Andreas Bolsch Reviewed-by: Spencer Oliver Reviewed-by: Antonio Borneo --- src/jtag/drivers/stlink_usb.c | 203 +++++++++++++++++++++++++++++++++- 1 file changed, 201 insertions(+), 2 deletions(-) diff --git a/src/jtag/drivers/stlink_usb.c b/src/jtag/drivers/stlink_usb.c index aed30b6f73..1a3cc6154b 100644 --- a/src/jtag/drivers/stlink_usb.c +++ b/src/jtag/drivers/stlink_usb.c @@ -41,6 +41,10 @@ #include "libusb_common.h" +#ifdef HAVE_LIBUSB1 +#define USE_LIBUSB_ASYNCIO +#endif + #define ENDPOINT_IN 0x80 #define ENDPOINT_OUT 0x00 @@ -351,6 +355,160 @@ static unsigned int stlink_usb_block(void *handle) return STLINK_MAX_RW8; } + + +#ifdef USE_LIBUSB_ASYNCIO + +static LIBUSB_CALL void sync_transfer_cb(struct libusb_transfer *transfer) +{ + int *completed = transfer->user_data; + *completed = 1; + /* caller interprets result and frees transfer */ +} + + +static void sync_transfer_wait_for_completion(struct libusb_transfer *transfer) +{ + int r, *completed = transfer->user_data; + + /* Assuming a single libusb context exists. There no existing interface into this + * module to pass a libusb context. + */ + struct libusb_context *ctx = NULL; + + while (!*completed) { + r = libusb_handle_events_completed(ctx, completed); + if (r < 0) { + if (r == LIBUSB_ERROR_INTERRUPTED) + continue; + libusb_cancel_transfer(transfer); + continue; + } + } +} + + +static int transfer_error_status(const struct libusb_transfer *transfer) +{ + int r = 0; + + switch (transfer->status) { + case LIBUSB_TRANSFER_COMPLETED: + r = 0; + break; + case LIBUSB_TRANSFER_TIMED_OUT: + r = LIBUSB_ERROR_TIMEOUT; + break; + case LIBUSB_TRANSFER_STALL: + r = LIBUSB_ERROR_PIPE; + break; + case LIBUSB_TRANSFER_OVERFLOW: + r = LIBUSB_ERROR_OVERFLOW; + break; + case LIBUSB_TRANSFER_NO_DEVICE: + r = LIBUSB_ERROR_NO_DEVICE; + break; + case LIBUSB_TRANSFER_ERROR: + case LIBUSB_TRANSFER_CANCELLED: + r = LIBUSB_ERROR_IO; + break; + default: + r = LIBUSB_ERROR_OTHER; + break; + } + + return r; +} + +struct jtag_xfer { + int ep; + uint8_t *buf; + size_t size; + /* Internal */ + int retval; + int completed; + size_t transfer_size; + struct libusb_transfer *transfer; +}; + +static int jtag_libusb_bulk_transfer_n( + jtag_libusb_device_handle * dev_handle, + struct jtag_xfer *transfers, + size_t n_transfers, + int timeout) +{ + int retval = 0; + int returnval = ERROR_OK; + + + for (size_t i = 0; i < n_transfers; ++i) { + transfers[i].retval = 0; + transfers[i].completed = 0; + transfers[i].transfer_size = 0; + transfers[i].transfer = libusb_alloc_transfer(0); + + if (transfers[i].transfer == NULL) { + for (size_t j = 0; j < i; ++j) + libusb_free_transfer(transfers[j].transfer); + + LOG_DEBUG("ERROR, failed to alloc usb transfers"); + for (size_t k = 0; k < n_transfers; ++k) + transfers[k].retval = LIBUSB_ERROR_NO_MEM; + return ERROR_FAIL; + } + } + + for (size_t i = 0; i < n_transfers; ++i) { + libusb_fill_bulk_transfer( + transfers[i].transfer, + dev_handle, + transfers[i].ep, transfers[i].buf, transfers[i].size, + sync_transfer_cb, &transfers[i].completed, timeout); + transfers[i].transfer->type = LIBUSB_TRANSFER_TYPE_BULK; + + retval = libusb_submit_transfer(transfers[i].transfer); + if (retval < 0) { + LOG_DEBUG("ERROR, failed to submit transfer %zu, error %d", i, retval); + + /* Probably no point continuing to submit transfers once a submission fails. + * As a result, tag all remaining transfers as errors. + */ + for (size_t j = i; j < n_transfers; ++j) + transfers[j].retval = retval; + + returnval = ERROR_FAIL; + break; + } + } + + /* Wait for every submitted USB transfer to complete. + */ + for (size_t i = 0; i < n_transfers; ++i) { + if (transfers[i].retval == 0) { + sync_transfer_wait_for_completion(transfers[i].transfer); + + retval = transfer_error_status(transfers[i].transfer); + if (retval) { + returnval = ERROR_FAIL; + transfers[i].retval = retval; + LOG_DEBUG("ERROR, transfer %zu failed, error %d", i, retval); + } else { + /* Assuming actual_length is only valid if there is no transfer error. + */ + transfers[i].transfer_size = transfers[i].transfer->actual_length; + } + } + + libusb_free_transfer(transfers[i].transfer); + transfers[i].transfer = NULL; + } + + return returnval; +} + +#endif + + /** */ static int stlink_usb_xfer_v1_get_status(void *handle) { @@ -384,7 +542,45 @@ static int stlink_usb_xfer_v1_get_status(void *handle) return ERROR_OK; } -/** */ +#ifdef USE_LIBUSB_ASYNCIO +static int stlink_usb_xfer_rw(void *handle, int cmdsize, const uint8_t *buf, int size) +{ + struct stlink_usb_handle_s *h = handle; + + assert(handle != NULL); + + size_t n_transfers = 0; + struct jtag_xfer transfers[2]; + + memset(transfers, 0, sizeof(transfers)); + + transfers[0].ep = h->tx_ep; + transfers[0].buf = h->cmdbuf; + transfers[0].size = cmdsize; + + ++n_transfers; + + if (h->direction == h->tx_ep && size) { + transfers[1].ep = h->tx_ep; + transfers[1].buf = (uint8_t *)buf; + transfers[1].size = size; + + ++n_transfers; + } else if (h->direction == h->rx_ep && size) { + transfers[1].ep = h->rx_ep; + transfers[1].buf = (uint8_t *)buf; + transfers[1].size = size; + + ++n_transfers; + } + + return jtag_libusb_bulk_transfer_n( + h->fd, + transfers, + n_transfers, + STLINK_WRITE_TIMEOUT); +} +#else static int stlink_usb_xfer_rw(void *handle, int cmdsize, const uint8_t *buf, int size) { struct stlink_usb_handle_s *h = handle; @@ -412,6 +608,7 @@ static int stlink_usb_xfer_rw(void *handle, int cmdsize, const uint8_t *buf, int return ERROR_OK; } +#endif /** */ static int stlink_usb_xfer_v1_get_sense(void *handle) @@ -589,7 +786,9 @@ static int stlink_cmd_allow_retry(void *handle, const uint8_t *buf, int size) res = stlink_usb_error_check(handle); if (res == ERROR_WAIT && retries < MAX_WAIT_RETRIES) { - usleep((1<