diff options
| author | 2014-03-20 10:01:53 +0100 | |
|---|---|---|
| committer | 2014-03-24 17:01:30 +0100 | |
| commit | 8ba560fdd177f107c5a0cf667d4e4ab3b0c59f4a (patch) | |
| tree | 1d9a1707cab915103074849cc6f5fb8b234f5c20 | |
| parent | d06ea10f14a806f2770507cd179b62c0d4eda6a2 (diff) | |
| download | usbmuxd-8ba560fdd177f107c5a0cf667d4e4ab3b0c59f4a.tar.gz usbmuxd-8ba560fdd177f107c5a0cf667d4e4ab3b0c59f4a.tar.bz2 | |
usb-linux: massive read perf improvement with 3 parallel transfers
By maintaining 3 parallel usb trasfers when reading we get 2-3x more
throughput when reading. Without this the usb port is mostly just idling.
I get 23mb/s on my system compared to a clean Apple stack that gives me
17mb/s.
3 was chosen because it is simple to hard code, gives very good performance,
and have very little impact on out resource consumption.
| -rw-r--r-- | src/usb-linux.c | 107 |
1 files changed, 73 insertions, 34 deletions
diff --git a/src/usb-linux.c b/src/usb-linux.c index 94db8f2..40bf502 100644 --- a/src/usb-linux.c +++ b/src/usb-linux.c | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | Copyright (C) 2009 Hector Martin "marcan" <hector@marcansoft.com> | 4 | Copyright (C) 2009 Hector Martin "marcan" <hector@marcansoft.com> |
| 5 | Copyright (C) 2009 Nikias Bassen <nikias@gmx.li> | 5 | Copyright (C) 2009 Nikias Bassen <nikias@gmx.li> |
| 6 | Copyright (C) 2009 Martin Szulecki <opensuse@sukimashita.com> | 6 | Copyright (C) 2009 Martin Szulecki <opensuse@sukimashita.com> |
| 7 | Copyright (C) 2014 Mikkel Kamstrup Erlandsen <mikkel.kamstrup@xamarin.com> | ||
| 7 | 8 | ||
| 8 | This program is free software; you can redistribute it and/or modify | 9 | This program is free software; you can redistribute it and/or modify |
| 9 | it under the terms of the GNU General Public License as published by | 10 | it under the terms of the GNU General Public License as published by |
| @@ -39,6 +40,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |||
| 39 | // we need this because there is currently no asynchronous device discovery mechanism in libusb | 40 | // we need this because there is currently no asynchronous device discovery mechanism in libusb |
| 40 | #define DEVICE_POLL_TIME 1000 | 41 | #define DEVICE_POLL_TIME 1000 |
| 41 | 42 | ||
| 43 | // Number of parallel bulk transfers we have running for reading data from the device. | ||
| 44 | // Older versions of usbmuxd kept only 1, which leads to a mostly dormant USB port. | ||
| 45 | // 3 seems to be an all round sensible number - giving better read perf than | ||
| 46 | // Apples usbmuxd, at least. | ||
| 47 | #define NUM_RX_LOOPS 3 | ||
| 48 | |||
| 42 | struct usb_device { | 49 | struct usb_device { |
| 43 | libusb_device_handle *dev; | 50 | libusb_device_handle *dev; |
| 44 | uint8_t bus, address; | 51 | uint8_t bus, address; |
| @@ -46,7 +53,7 @@ struct usb_device { | |||
| 46 | char serial[256]; | 53 | char serial[256]; |
| 47 | int alive; | 54 | int alive; |
| 48 | uint8_t interface, ep_in, ep_out; | 55 | uint8_t interface, ep_in, ep_out; |
| 49 | struct libusb_transfer *rx_xfer; | 56 | struct collection rx_xfers; |
| 50 | struct collection tx_xfers; | 57 | struct collection tx_xfers; |
| 51 | int wMaxPacketSize; | 58 | int wMaxPacketSize; |
| 52 | }; | 59 | }; |
| @@ -64,17 +71,20 @@ static void usb_disconnect(struct usb_device *dev) | |||
| 64 | return; | 71 | return; |
| 65 | } | 72 | } |
| 66 | 73 | ||
| 67 | // kill the rx xfer and tx xfers and try to make sure the callbacks get called before we free the device | 74 | // kill the rx xfer and tx xfers and try to make sure the callbacks |
| 68 | if(dev->rx_xfer) { | 75 | // get called before we free the device |
| 69 | usbmuxd_log(LL_DEBUG, "usb_disconnect: cancelling RX xfer"); | 76 | FOREACH(struct libusb_transfer *xfer, &dev->rx_xfers) { |
| 70 | libusb_cancel_transfer(dev->rx_xfer); | 77 | usbmuxd_log(LL_DEBUG, "usb_disconnect: cancelling RX xfer %p", xfer); |
| 71 | } | 78 | libusb_cancel_transfer(xfer); |
| 79 | } ENDFOREACH | ||
| 80 | |||
| 72 | FOREACH(struct libusb_transfer *xfer, &dev->tx_xfers) { | 81 | FOREACH(struct libusb_transfer *xfer, &dev->tx_xfers) { |
| 73 | usbmuxd_log(LL_DEBUG, "usb_disconnect: cancelling TX xfer %p", xfer); | 82 | usbmuxd_log(LL_DEBUG, "usb_disconnect: cancelling TX xfer %p", xfer); |
| 74 | libusb_cancel_transfer(xfer); | 83 | libusb_cancel_transfer(xfer); |
| 75 | } ENDFOREACH | 84 | } ENDFOREACH |
| 76 | 85 | ||
| 77 | while(dev->rx_xfer || collection_count(&dev->tx_xfers)) { | 86 | // Busy-wait until all xfers are closed |
| 87 | while(collection_count(&dev->rx_xfers) || collection_count(&dev->tx_xfers)) { | ||
| 78 | struct timeval tv; | 88 | struct timeval tv; |
| 79 | int res; | 89 | int res; |
| 80 | 90 | ||
| @@ -85,7 +95,9 @@ static void usb_disconnect(struct usb_device *dev) | |||
| 85 | break; | 95 | break; |
| 86 | } | 96 | } |
| 87 | } | 97 | } |
| 98 | |||
| 88 | collection_free(&dev->tx_xfers); | 99 | collection_free(&dev->tx_xfers); |
| 100 | collection_free(&dev->rx_xfers); | ||
| 89 | libusb_release_interface(dev->dev, dev->interface); | 101 | libusb_release_interface(dev->dev, dev->interface); |
| 90 | libusb_close(dev->dev); | 102 | libusb_close(dev->dev); |
| 91 | dev->dev = NULL; | 103 | dev->dev = NULL; |
| @@ -93,6 +105,15 @@ static void usb_disconnect(struct usb_device *dev) | |||
| 93 | free(dev); | 105 | free(dev); |
| 94 | } | 106 | } |
| 95 | 107 | ||
| 108 | static void reap_dead_devices(void) { | ||
| 109 | FOREACH(struct usb_device *usbdev, &device_list) { | ||
| 110 | if(!usbdev->alive) { | ||
| 111 | device_remove(usbdev); | ||
| 112 | usb_disconnect(usbdev); | ||
| 113 | } | ||
| 114 | } ENDFOREACH | ||
| 115 | } | ||
| 116 | |||
| 96 | // Callback from write operation | 117 | // Callback from write operation |
| 97 | static void tx_callback(struct libusb_transfer *xfer) | 118 | static void tx_callback(struct libusb_transfer *xfer) |
| 98 | { | 119 | { |
| @@ -201,9 +222,11 @@ static void rx_callback(struct libusb_transfer *xfer) | |||
| 201 | // this should never be reached. | 222 | // this should never be reached. |
| 202 | break; | 223 | break; |
| 203 | } | 224 | } |
| 225 | |||
| 204 | free(xfer->buffer); | 226 | free(xfer->buffer); |
| 205 | dev->rx_xfer = NULL; | 227 | collection_remove(&dev->rx_xfers, xfer); |
| 206 | libusb_free_transfer(xfer); | 228 | libusb_free_transfer(xfer); |
| 229 | |||
| 207 | // we can't usb_disconnect here due to a deadlock, so instead mark it as dead and reap it after processing events | 230 | // we can't usb_disconnect here due to a deadlock, so instead mark it as dead and reap it after processing events |
| 208 | // we'll do device_remove there too | 231 | // we'll do device_remove there too |
| 209 | dev->alive = 0; | 232 | dev->alive = 0; |
| @@ -211,19 +234,21 @@ static void rx_callback(struct libusb_transfer *xfer) | |||
| 211 | } | 234 | } |
| 212 | 235 | ||
| 213 | // Start a read-callback loop for this device | 236 | // Start a read-callback loop for this device |
| 214 | static int start_rx(struct usb_device *dev) | 237 | static int start_rx_loop(struct usb_device *dev) |
| 215 | { | 238 | { |
| 216 | int res; | 239 | int res; |
| 217 | void *buf; | 240 | void *buf; |
| 218 | dev->rx_xfer = libusb_alloc_transfer(0); | 241 | struct libusb_transfer *xfer = libusb_alloc_transfer(0); |
| 219 | buf = malloc(USB_MRU); | 242 | buf = malloc(USB_MRU); |
| 220 | libusb_fill_bulk_transfer(dev->rx_xfer, dev->dev, dev->ep_in, buf, USB_MRU, rx_callback, dev, 0); | 243 | libusb_fill_bulk_transfer(xfer, dev->dev, dev->ep_in, buf, USB_MRU, rx_callback, dev, 0); |
| 221 | if((res = libusb_submit_transfer(dev->rx_xfer)) != 0) { | 244 | if((res = libusb_submit_transfer(xfer)) != 0) { |
| 222 | usbmuxd_log(LL_ERROR, "Failed to submit RX transfer to device %d-%d: %d", dev->bus, dev->address, res); | 245 | usbmuxd_log(LL_ERROR, "Failed to submit RX transfer to device %d-%d: %d", dev->bus, dev->address, res); |
| 223 | libusb_free_transfer(dev->rx_xfer); | 246 | libusb_free_transfer(xfer); |
| 224 | dev->rx_xfer = NULL; | ||
| 225 | return res; | 247 | return res; |
| 226 | } | 248 | } |
| 249 | |||
| 250 | collection_add(&dev->rx_xfers, xfer); | ||
| 251 | |||
| 227 | return 0; | 252 | return 0; |
| 228 | } | 253 | } |
| 229 | 254 | ||
| @@ -253,10 +278,14 @@ int usb_discover(void) | |||
| 253 | 278 | ||
| 254 | usbmuxd_log(LL_SPEW, "usb_discover: scanning %d devices", cnt); | 279 | usbmuxd_log(LL_SPEW, "usb_discover: scanning %d devices", cnt); |
| 255 | 280 | ||
| 281 | // Mark all devices as dead, and do a mark-sweep like | ||
| 282 | // collection of dead devices | ||
| 256 | FOREACH(struct usb_device *usbdev, &device_list) { | 283 | FOREACH(struct usb_device *usbdev, &device_list) { |
| 257 | usbdev->alive = 0; | 284 | usbdev->alive = 0; |
| 258 | } ENDFOREACH | 285 | } ENDFOREACH |
| 259 | 286 | ||
| 287 | // Enumerate all USB devices and mark the ones we already know | ||
| 288 | // about as live, again | ||
| 260 | for(i=0; i<cnt; i++) { | 289 | for(i=0; i<cnt; i++) { |
| 261 | // the following are non-blocking operations on the device list | 290 | // the following are non-blocking operations on the device list |
| 262 | libusb_device *dev = devs[i]; | 291 | libusb_device *dev = devs[i]; |
| @@ -407,6 +436,7 @@ int usb_discover(void) | |||
| 407 | } | 436 | } |
| 408 | 437 | ||
| 409 | collection_init(&usbdev->tx_xfers); | 438 | collection_init(&usbdev->tx_xfers); |
| 439 | collection_init(&usbdev->rx_xfers); | ||
| 410 | 440 | ||
| 411 | collection_add(&device_list, usbdev); | 441 | collection_add(&device_list, usbdev); |
| 412 | 442 | ||
| @@ -414,19 +444,37 @@ int usb_discover(void) | |||
| 414 | usb_disconnect(usbdev); | 444 | usb_disconnect(usbdev); |
| 415 | continue; | 445 | continue; |
| 416 | } | 446 | } |
| 417 | if(start_rx(usbdev) < 0) { | 447 | |
| 448 | // Spin up NUM_RX_LOOPS parallel usb data retrieval loops | ||
| 449 | // Old usbmuxds used only 1 rx loop, but that leaves the | ||
| 450 | // USB port sleeping most of the time | ||
| 451 | int rx_loops = NUM_RX_LOOPS; | ||
| 452 | for (rx_loops = NUM_RX_LOOPS; rx_loops > 0; rx_loops--) { | ||
| 453 | if(start_rx_loop(usbdev) < 0) { | ||
| 454 | usbmuxd_log(LL_WARNING, "Failed to start RX loop number %d", NUM_RX_LOOPS - rx_loops); | ||
| 455 | } | ||
| 456 | } | ||
| 457 | |||
| 458 | // Ensure we have at least 1 RX loop going | ||
| 459 | if (rx_loops == NUM_RX_LOOPS) { | ||
| 460 | usbmuxd_log(LL_FATAL, "Failed to start any RX loop for device %d-%d", | ||
| 461 | usbdev->bus, usbdev->address); | ||
| 418 | device_remove(usbdev); | 462 | device_remove(usbdev); |
| 419 | usb_disconnect(usbdev); | 463 | usb_disconnect(usbdev); |
| 420 | continue; | 464 | continue; |
| 465 | } else if (rx_loops > 0) { | ||
| 466 | usbmuxd_log(LL_WARNING, "Failed to start all %d RX loops. Going on with %d loops. " | ||
| 467 | "This may have negative impact on device read speed.", | ||
| 468 | NUM_RX_LOOPS, NUM_RX_LOOPS - rx_loops); | ||
| 469 | } else { | ||
| 470 | usbmuxd_log(LL_DEBUG, "All %d RX loops started successfully", NUM_RX_LOOPS); | ||
| 421 | } | 471 | } |
| 472 | |||
| 422 | valid_count++; | 473 | valid_count++; |
| 423 | } | 474 | } |
| 424 | FOREACH(struct usb_device *usbdev, &device_list) { | 475 | |
| 425 | if(!usbdev->alive) { | 476 | // Clean out any device we didn't mark back as live |
| 426 | device_remove(usbdev); | 477 | reap_dead_devices(); |
| 427 | usb_disconnect(usbdev); | ||
| 428 | } | ||
| 429 | } ENDFOREACH | ||
| 430 | 478 | ||
| 431 | libusb_free_device_list(devs, 1); | 479 | libusb_free_device_list(devs, 1); |
| 432 | 480 | ||
| @@ -530,13 +578,9 @@ int usb_process(void) | |||
| 530 | usbmuxd_log(LL_ERROR, "libusb_handle_events_timeout failed: %d", res); | 578 | usbmuxd_log(LL_ERROR, "libusb_handle_events_timeout failed: %d", res); |
| 531 | return res; | 579 | return res; |
| 532 | } | 580 | } |
| 581 | |||
| 533 | // reap devices marked dead due to an RX error | 582 | // reap devices marked dead due to an RX error |
| 534 | FOREACH(struct usb_device *usbdev, &device_list) { | 583 | reap_dead_devices(); |
| 535 | if(!usbdev->alive) { | ||
| 536 | device_remove(usbdev); | ||
| 537 | usb_disconnect(usbdev); | ||
| 538 | } | ||
| 539 | } ENDFOREACH | ||
| 540 | 584 | ||
| 541 | if(dev_poll_remain_ms() <= 0) { | 585 | if(dev_poll_remain_ms() <= 0) { |
| 542 | res = usb_discover(); | 586 | res = usb_discover(); |
| @@ -570,13 +614,8 @@ int usb_process_timeout(int msec) | |||
| 570 | return res; | 614 | return res; |
| 571 | } | 615 | } |
| 572 | // reap devices marked dead due to an RX error | 616 | // reap devices marked dead due to an RX error |
| 573 | FOREACH(struct usb_device *usbdev, &device_list) { | 617 | reap_dead_devices(); |
| 574 | if(!usbdev->alive) { | 618 | gettimeofday(&tcur, NULL); |
| 575 | device_remove(usbdev); | ||
| 576 | usb_disconnect(usbdev); | ||
| 577 | } | ||
| 578 | } ENDFOREACH | ||
| 579 | gettimeofday(&tcur, NULL); | ||
| 580 | } | 619 | } |
| 581 | return 0; | 620 | return 0; |
| 582 | } | 621 | } |
