diff options
Diffstat (limited to 'usb-linux.c')
| -rw-r--r-- | usb-linux.c | 152 |
1 files changed, 144 insertions, 8 deletions
diff --git a/usb-linux.c b/usb-linux.c index 0820ed9..27a7bb1 100644 --- a/usb-linux.c +++ b/usb-linux.c | |||
| @@ -42,13 +42,13 @@ struct usb_device { | |||
| 42 | uint8_t bus, address; | 42 | uint8_t bus, address; |
| 43 | char serial[256]; | 43 | char serial[256]; |
| 44 | int alive; | 44 | int alive; |
| 45 | struct libusb_transfer *rx_xfer; | ||
| 45 | }; | 46 | }; |
| 46 | 47 | ||
| 47 | int num_devs; | 48 | static int num_devs; |
| 48 | int device_id; | 49 | static struct usb_device *device_list; |
| 49 | struct usb_device *device_list; | ||
| 50 | 50 | ||
| 51 | struct timeval next_dev_poll_time; | 51 | static struct timeval next_dev_poll_time; |
| 52 | 52 | ||
| 53 | static int alloc_device(void) | 53 | static int alloc_device(void) |
| 54 | { | 54 | { |
| @@ -68,11 +68,130 @@ static void usb_disconnect(struct usb_device *dev) | |||
| 68 | if(!dev->dev) { | 68 | if(!dev->dev) { |
| 69 | return; | 69 | return; |
| 70 | } | 70 | } |
| 71 | if(dev->rx_xfer) { | ||
| 72 | // kill the rx xfer and try to make sure the rx callback gets called before we free the device | ||
| 73 | struct timeval tv; | ||
| 74 | int res; | ||
| 75 | // TODO: BUG: outstanding TX xfers are not listed but we need to free them | ||
| 76 | libusb_cancel_transfer(dev->rx_xfer); | ||
| 77 | tv.tv_sec = tv.tv_usec = 0; | ||
| 78 | if((res = libusb_handle_events_timeout(NULL, &tv)) < 0) { | ||
| 79 | usbmuxd_log(LL_ERROR, "libusb_handle_events_timeout for device removal failed: %d", res); | ||
| 80 | } | ||
| 81 | } | ||
| 71 | libusb_release_interface(dev->dev, USB_INTERFACE); | 82 | libusb_release_interface(dev->dev, USB_INTERFACE); |
| 72 | libusb_close(dev->dev); | 83 | libusb_close(dev->dev); |
| 73 | dev->dev = NULL; | 84 | dev->dev = NULL; |
| 74 | } | 85 | } |
| 75 | 86 | ||
| 87 | static void tx_callback(struct libusb_transfer *xfer) | ||
| 88 | { | ||
| 89 | struct usb_device *dev = xfer->user_data; | ||
| 90 | usbmuxd_log(LL_SPEW, "TX callback dev %d-%d len %d -> %d status %d", dev->bus, dev->address, xfer->length, xfer->actual_length, xfer->status); | ||
| 91 | if(xfer->status != LIBUSB_TRANSFER_COMPLETED) { | ||
| 92 | switch(xfer->status) { | ||
| 93 | case LIBUSB_TRANSFER_COMPLETED: //shut up compiler | ||
| 94 | case LIBUSB_TRANSFER_ERROR: | ||
| 95 | // funny, this happens when we disconnect the device while waiting for a transfer, sometimes | ||
| 96 | usbmuxd_log(LL_INFO, "Device %d-%d TX aborted due to error or disconnect", dev->bus, dev->address); | ||
| 97 | break; | ||
| 98 | case LIBUSB_TRANSFER_TIMED_OUT: | ||
| 99 | usbmuxd_log(LL_ERROR, "TX transfer timed out for device %d-%d", dev->bus, dev->address); | ||
| 100 | break; | ||
| 101 | case LIBUSB_TRANSFER_CANCELLED: | ||
| 102 | usbmuxd_log(LL_ERROR, "TX transfer cancelled for device %d-%d", dev->bus, dev->address); | ||
| 103 | break; | ||
| 104 | case LIBUSB_TRANSFER_STALL: | ||
| 105 | usbmuxd_log(LL_ERROR, "TX transfer stalled for device %d-%d", dev->bus, dev->address); | ||
| 106 | break; | ||
| 107 | case LIBUSB_TRANSFER_NO_DEVICE: | ||
| 108 | // other times, this happens, and also even when we abort the transfer after device removal | ||
| 109 | usbmuxd_log(LL_INFO, "Device %d-%d TX aborted due to disconnect", dev->bus, dev->address); | ||
| 110 | break; | ||
| 111 | case LIBUSB_TRANSFER_OVERFLOW: | ||
| 112 | usbmuxd_log(LL_ERROR, "TX transfer overflow for device %d-%d", dev->bus, dev->address); | ||
| 113 | break; | ||
| 114 | // and nothing happens (this never gets called) if the device is freed after a disconnect! (bad) | ||
| 115 | } | ||
| 116 | // we can't usb_disconnect here due to a deadlock, so instead mark it as dead and reap it after processing events | ||
| 117 | // we'll do device_remove there too | ||
| 118 | dev->alive = 0; | ||
| 119 | } | ||
| 120 | free(xfer->buffer); | ||
| 121 | libusb_free_transfer(xfer); | ||
| 122 | } | ||
| 123 | |||
| 124 | int usb_send(struct usb_device *dev, const unsigned char *buf, int length) | ||
| 125 | { | ||
| 126 | int res; | ||
| 127 | struct libusb_transfer *xfer = libusb_alloc_transfer(0); | ||
| 128 | libusb_fill_bulk_transfer(xfer, dev->dev, BULK_OUT, (void*)buf, length, tx_callback, dev, 0); | ||
| 129 | xfer->flags = LIBUSB_TRANSFER_SHORT_NOT_OK; | ||
| 130 | if((res = libusb_submit_transfer(xfer)) < 0) { | ||
| 131 | usbmuxd_log(LL_ERROR, "Failed to submit TX transfer %p len %d to device %d-%d: %d", buf, length, dev->bus, dev->address, res); | ||
| 132 | libusb_free_transfer(xfer); | ||
| 133 | return res; | ||
| 134 | } | ||
| 135 | return 0; | ||
| 136 | } | ||
| 137 | |||
| 138 | static void rx_callback(struct libusb_transfer *xfer) | ||
| 139 | { | ||
| 140 | struct usb_device *dev = xfer->user_data; | ||
| 141 | usbmuxd_log(LL_SPEW, "RX callback dev %d-%d len %d status %d", dev->bus, dev->address, xfer->actual_length, xfer->status); | ||
| 142 | if(xfer->status == LIBUSB_TRANSFER_COMPLETED) { | ||
| 143 | device_data_input(dev, xfer->buffer, xfer->actual_length); | ||
| 144 | libusb_submit_transfer(xfer); | ||
| 145 | } else { | ||
| 146 | switch(xfer->status) { | ||
| 147 | case LIBUSB_TRANSFER_COMPLETED: //shut up compiler | ||
| 148 | case LIBUSB_TRANSFER_ERROR: | ||
| 149 | // funny, this happens when we disconnect the device while waiting for a transfer, sometimes | ||
| 150 | usbmuxd_log(LL_INFO, "Device %d-%d RX aborted due to error or disconnect", dev->bus, dev->address); | ||
| 151 | break; | ||
| 152 | case LIBUSB_TRANSFER_TIMED_OUT: | ||
| 153 | usbmuxd_log(LL_ERROR, "RX transfer timed out for device %d-%d", dev->bus, dev->address); | ||
| 154 | break; | ||
| 155 | case LIBUSB_TRANSFER_CANCELLED: | ||
| 156 | usbmuxd_log(LL_ERROR, "RX transfer cancelled for device %d-%d", dev->bus, dev->address); | ||
| 157 | break; | ||
| 158 | case LIBUSB_TRANSFER_STALL: | ||
| 159 | usbmuxd_log(LL_ERROR, "RX transfer stalled for device %d-%d", dev->bus, dev->address); | ||
| 160 | break; | ||
| 161 | case LIBUSB_TRANSFER_NO_DEVICE: | ||
| 162 | // other times, this happens, and also even when we abort the transfer after device removal | ||
| 163 | usbmuxd_log(LL_INFO, "Device %d-%d RX aborted due to disconnect", dev->bus, dev->address); | ||
| 164 | break; | ||
| 165 | case LIBUSB_TRANSFER_OVERFLOW: | ||
| 166 | usbmuxd_log(LL_ERROR, "RX transfer overflow for device %d-%d", dev->bus, dev->address); | ||
| 167 | break; | ||
| 168 | // and nothing happens (this never gets called) if the device is freed after a disconnect! (bad) | ||
| 169 | } | ||
| 170 | free(xfer->buffer); | ||
| 171 | dev->rx_xfer = NULL; | ||
| 172 | libusb_free_transfer(xfer); | ||
| 173 | // we can't usb_disconnect here due to a deadlock, so instead mark it as dead and reap it after processing events | ||
| 174 | // we'll do device_remove there too | ||
| 175 | dev->alive = 0; | ||
| 176 | } | ||
| 177 | } | ||
| 178 | |||
| 179 | static int start_rx(struct usb_device *dev) | ||
| 180 | { | ||
| 181 | int res; | ||
| 182 | void *buf; | ||
| 183 | dev->rx_xfer = libusb_alloc_transfer(0); | ||
| 184 | buf = malloc(USB_MTU); | ||
| 185 | libusb_fill_bulk_transfer(dev->rx_xfer, dev->dev, BULK_IN, buf, USB_MTU, rx_callback, dev, 0); | ||
| 186 | if((res = libusb_submit_transfer(dev->rx_xfer)) != 0) { | ||
| 187 | usbmuxd_log(LL_ERROR, "Failed to submit RX transfer to device %d-%d: %d", dev->bus, dev->address, res); | ||
| 188 | libusb_free_transfer(dev->rx_xfer); | ||
| 189 | dev->rx_xfer = NULL; | ||
| 190 | return res; | ||
| 191 | } | ||
| 192 | return 0; | ||
| 193 | } | ||
| 194 | |||
| 76 | static int usb_discover(void) | 195 | static int usb_discover(void) |
| 77 | { | 196 | { |
| 78 | int cnt, i, j, res; | 197 | int cnt, i, j, res; |
| @@ -135,7 +254,8 @@ static int usb_discover(void) | |||
| 135 | int idx = alloc_device(); | 254 | int idx = alloc_device(); |
| 136 | 255 | ||
| 137 | if((res = libusb_get_string_descriptor_ascii(handle, devdesc.iSerialNumber, (uint8_t *)device_list[idx].serial, 256)) <= 0) { | 256 | if((res = libusb_get_string_descriptor_ascii(handle, devdesc.iSerialNumber, (uint8_t *)device_list[idx].serial, 256)) <= 0) { |
| 138 | usbmuxd_log(LL_WARNING, "Could not get serial number for device %d-%d: %d", USB_INTERFACE, bus, address, res); | 257 | usbmuxd_log(LL_WARNING, "Could not get serial number for device %d-%d: %d", bus, address, res); |
| 258 | libusb_release_interface(handle, USB_INTERFACE); | ||
| 139 | libusb_close(handle); | 259 | libusb_close(handle); |
| 140 | continue; | 260 | continue; |
| 141 | } | 261 | } |
| @@ -145,7 +265,15 @@ static int usb_discover(void) | |||
| 145 | device_list[idx].dev = handle; | 265 | device_list[idx].dev = handle; |
| 146 | device_list[idx].alive = 1; | 266 | device_list[idx].alive = 1; |
| 147 | 267 | ||
| 148 | device_add(&device_list[idx]); | 268 | if(device_add(&device_list[idx]) < 0) { |
| 269 | usb_disconnect(&device_list[j]); | ||
| 270 | continue; | ||
| 271 | } | ||
| 272 | if(start_rx(&device_list[idx]) < 0) { | ||
| 273 | device_remove(&device_list[j]); | ||
| 274 | usb_disconnect(&device_list[j]); | ||
| 275 | continue; | ||
| 276 | } | ||
| 149 | valid_count++; | 277 | valid_count++; |
| 150 | } | 278 | } |
| 151 | for(j=0; j<num_devs; j++) { | 279 | for(j=0; j<num_devs; j++) { |
| @@ -232,7 +360,7 @@ int usb_get_timeout(void) | |||
| 232 | 360 | ||
| 233 | int usb_process(void) | 361 | int usb_process(void) |
| 234 | { | 362 | { |
| 235 | int res; | 363 | int i, res; |
| 236 | struct timeval tv; | 364 | struct timeval tv; |
| 237 | tv.tv_sec = tv.tv_usec = 0; | 365 | tv.tv_sec = tv.tv_usec = 0; |
| 238 | res = libusb_handle_events_timeout(NULL, &tv); | 366 | res = libusb_handle_events_timeout(NULL, &tv); |
| @@ -240,6 +368,14 @@ int usb_process(void) | |||
| 240 | usbmuxd_log(LL_ERROR, "libusb_handle_events_timeout failed: %d", res); | 368 | usbmuxd_log(LL_ERROR, "libusb_handle_events_timeout failed: %d", res); |
| 241 | return res; | 369 | return res; |
| 242 | } | 370 | } |
| 371 | // reap devices marked dead due to an RX error | ||
| 372 | for(i=0; i<num_devs; i++) { | ||
| 373 | if(device_list[i].dev && !device_list[i].alive) { | ||
| 374 | device_remove(&device_list[i]); | ||
| 375 | usb_disconnect(&device_list[i]); | ||
| 376 | } | ||
| 377 | } | ||
| 378 | |||
| 243 | if(dev_poll_remain_ms() <= 0) { | 379 | if(dev_poll_remain_ms() <= 0) { |
| 244 | res = usb_discover(); | 380 | res = usb_discover(); |
| 245 | if(res < 0) { | 381 | if(res < 0) { |
| @@ -256,12 +392,12 @@ int usb_init(void) | |||
| 256 | usbmuxd_log(LL_DEBUG, "usb_init for linux / libusb 1.0"); | 392 | usbmuxd_log(LL_DEBUG, "usb_init for linux / libusb 1.0"); |
| 257 | 393 | ||
| 258 | res = libusb_init(NULL); | 394 | res = libusb_init(NULL); |
| 395 | //libusb_set_debug(NULL, 3); | ||
| 259 | if(res != 0) { | 396 | if(res != 0) { |
| 260 | usbmuxd_log(LL_FATAL, "libusb_init failed: %d", res); | 397 | usbmuxd_log(LL_FATAL, "libusb_init failed: %d", res); |
| 261 | return -1; | 398 | return -1; |
| 262 | } | 399 | } |
| 263 | 400 | ||
| 264 | device_id = 1; | ||
| 265 | num_devs = 1; | 401 | num_devs = 1; |
| 266 | device_list = malloc(sizeof(*device_list) * num_devs); | 402 | device_list = malloc(sizeof(*device_list) * num_devs); |
| 267 | memset(device_list, 0, sizeof(*device_list) * num_devs); | 403 | memset(device_list, 0, sizeof(*device_list) * num_devs); |
