stlink_usb: Submit multiple USB URBs at once to improve performance 84/4484/5
authorAustin Phillips <austin_phillips@hotmail.com>
Sat, 7 Apr 2018 06:06:03 +0000 (16:06 +1000)
committerTomas Vanek <vanekt@fbl.cz>
Tue, 8 Jan 2019 10:05:36 +0000 (10:05 +0000)
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 <austin_phillips@hotmail.com>
Reviewed-on: http://openocd.zylin.com/4484
Tested-by: jenkins
Reviewed-by: Andreas Bolsch <hyphen0break@gmail.com>
Reviewed-by: Spencer Oliver <spen@spen-soft.co.uk>
Reviewed-by: Antonio Borneo <borneo.antonio@gmail.com>
src/jtag/drivers/stlink_usb.c

index aed30b6f7360b8fa1b038f79e2340f2bc326bedf..1a3cc6154b5bbac47c0cca10acc11f7df0caa22e 100644 (file)
 
 #include "libusb_common.h"
 
 
 #include "libusb_common.h"
 
+#ifdef HAVE_LIBUSB1
+#define USE_LIBUSB_ASYNCIO
+#endif
+
 #define ENDPOINT_IN  0x80
 #define ENDPOINT_OUT 0x00
 
 #define ENDPOINT_IN  0x80
 #define ENDPOINT_OUT 0x00
 
@@ -351,6 +355,160 @@ static unsigned int stlink_usb_block(void *handle)
                return STLINK_MAX_RW8;
 }
 
                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)
 {
 /** */
 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;
 }
 
        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;
 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;
 }
 
        return ERROR_OK;
 }
+#endif
 
 /** */
 static int stlink_usb_xfer_v1_get_sense(void *handle)
 
 /** */
 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) {
 
                res = stlink_usb_error_check(handle);
                if (res == ERROR_WAIT && retries < MAX_WAIT_RETRIES) {
-                       usleep((1<<retries++) * 1000);
+                       useconds_t delay_us = (1<<retries++) * 1000;
+                       LOG_DEBUG("stlink_cmd_allow_retry ERROR_WAIT, retry %d, delaying %u microseconds", retries, delay_us);
+                       usleep(delay_us);
                        continue;
                }
                return res;
                        continue;
                }
                return res;

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)