summaryrefslogtreecommitdiffstats
path: root/usb-linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'usb-linux.c')
-rw-r--r--usb-linux.c152
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 {
uint8_t bus, address;
char serial[256];
int alive;
+ struct libusb_transfer *rx_xfer;
};
-int num_devs;
-int device_id;
-struct usb_device *device_list;
+static int num_devs;
+static struct usb_device *device_list;
-struct timeval next_dev_poll_time;
+static struct timeval next_dev_poll_time;
static int alloc_device(void)
{
@@ -68,11 +68,130 @@ static void usb_disconnect(struct usb_device *dev)
if(!dev->dev) {
return;
}
+ if(dev->rx_xfer) {
+ // kill the rx xfer and try to make sure the rx callback gets called before we free the device
+ struct timeval tv;
+ int res;
+ // TODO: BUG: outstanding TX xfers are not listed but we need to free them
+ libusb_cancel_transfer(dev->rx_xfer);
+ tv.tv_sec = tv.tv_usec = 0;
+ if((res = libusb_handle_events_timeout(NULL, &tv)) < 0) {
+ usbmuxd_log(LL_ERROR, "libusb_handle_events_timeout for device removal failed: %d", res);
+ }
+ }
libusb_release_interface(dev->dev, USB_INTERFACE);
libusb_close(dev->dev);
dev->dev = NULL;
}
+static void tx_callback(struct libusb_transfer *xfer)
+{
+ struct usb_device *dev = xfer->user_data;
+ 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);
+ if(xfer->status != LIBUSB_TRANSFER_COMPLETED) {
+ switch(xfer->status) {
+ case LIBUSB_TRANSFER_COMPLETED: //shut up compiler
+ case LIBUSB_TRANSFER_ERROR:
+ // funny, this happens when we disconnect the device while waiting for a transfer, sometimes
+ usbmuxd_log(LL_INFO, "Device %d-%d TX aborted due to error or disconnect", dev->bus, dev->address);
+ break;
+ case LIBUSB_TRANSFER_TIMED_OUT:
+ usbmuxd_log(LL_ERROR, "TX transfer timed out for device %d-%d", dev->bus, dev->address);
+ break;
+ case LIBUSB_TRANSFER_CANCELLED:
+ usbmuxd_log(LL_ERROR, "TX transfer cancelled for device %d-%d", dev->bus, dev->address);
+ break;
+ case LIBUSB_TRANSFER_STALL:
+ usbmuxd_log(LL_ERROR, "TX transfer stalled for device %d-%d", dev->bus, dev->address);
+ break;
+ case LIBUSB_TRANSFER_NO_DEVICE:
+ // other times, this happens, and also even when we abort the transfer after device removal
+ usbmuxd_log(LL_INFO, "Device %d-%d TX aborted due to disconnect", dev->bus, dev->address);
+ break;
+ case LIBUSB_TRANSFER_OVERFLOW:
+ usbmuxd_log(LL_ERROR, "TX transfer overflow for device %d-%d", dev->bus, dev->address);
+ break;
+ // and nothing happens (this never gets called) if the device is freed after a disconnect! (bad)
+ }
+ // we can't usb_disconnect here due to a deadlock, so instead mark it as dead and reap it after processing events
+ // we'll do device_remove there too
+ dev->alive = 0;
+ }
+ free(xfer->buffer);
+ libusb_free_transfer(xfer);
+}
+
+int usb_send(struct usb_device *dev, const unsigned char *buf, int length)
+{
+ int res;
+ struct libusb_transfer *xfer = libusb_alloc_transfer(0);
+ libusb_fill_bulk_transfer(xfer, dev->dev, BULK_OUT, (void*)buf, length, tx_callback, dev, 0);
+ xfer->flags = LIBUSB_TRANSFER_SHORT_NOT_OK;
+ if((res = libusb_submit_transfer(xfer)) < 0) {
+ usbmuxd_log(LL_ERROR, "Failed to submit TX transfer %p len %d to device %d-%d: %d", buf, length, dev->bus, dev->address, res);
+ libusb_free_transfer(xfer);
+ return res;
+ }
+ return 0;
+}
+
+static void rx_callback(struct libusb_transfer *xfer)
+{
+ struct usb_device *dev = xfer->user_data;
+ usbmuxd_log(LL_SPEW, "RX callback dev %d-%d len %d status %d", dev->bus, dev->address, xfer->actual_length, xfer->status);
+ if(xfer->status == LIBUSB_TRANSFER_COMPLETED) {
+ device_data_input(dev, xfer->buffer, xfer->actual_length);
+ libusb_submit_transfer(xfer);
+ } else {
+ switch(xfer->status) {
+ case LIBUSB_TRANSFER_COMPLETED: //shut up compiler
+ case LIBUSB_TRANSFER_ERROR:
+ // funny, this happens when we disconnect the device while waiting for a transfer, sometimes
+ usbmuxd_log(LL_INFO, "Device %d-%d RX aborted due to error or disconnect", dev->bus, dev->address);
+ break;
+ case LIBUSB_TRANSFER_TIMED_OUT:
+ usbmuxd_log(LL_ERROR, "RX transfer timed out for device %d-%d", dev->bus, dev->address);
+ break;
+ case LIBUSB_TRANSFER_CANCELLED:
+ usbmuxd_log(LL_ERROR, "RX transfer cancelled for device %d-%d", dev->bus, dev->address);
+ break;
+ case LIBUSB_TRANSFER_STALL:
+ usbmuxd_log(LL_ERROR, "RX transfer stalled for device %d-%d", dev->bus, dev->address);
+ break;
+ case LIBUSB_TRANSFER_NO_DEVICE:
+ // other times, this happens, and also even when we abort the transfer after device removal
+ usbmuxd_log(LL_INFO, "Device %d-%d RX aborted due to disconnect", dev->bus, dev->address);
+ break;
+ case LIBUSB_TRANSFER_OVERFLOW:
+ usbmuxd_log(LL_ERROR, "RX transfer overflow for device %d-%d", dev->bus, dev->address);
+ break;
+ // and nothing happens (this never gets called) if the device is freed after a disconnect! (bad)
+ }
+ free(xfer->buffer);
+ dev->rx_xfer = NULL;
+ libusb_free_transfer(xfer);
+ // we can't usb_disconnect here due to a deadlock, so instead mark it as dead and reap it after processing events
+ // we'll do device_remove there too
+ dev->alive = 0;
+ }
+}
+
+static int start_rx(struct usb_device *dev)
+{
+ int res;
+ void *buf;
+ dev->rx_xfer = libusb_alloc_transfer(0);
+ buf = malloc(USB_MTU);
+ libusb_fill_bulk_transfer(dev->rx_xfer, dev->dev, BULK_IN, buf, USB_MTU, rx_callback, dev, 0);
+ if((res = libusb_submit_transfer(dev->rx_xfer)) != 0) {
+ usbmuxd_log(LL_ERROR, "Failed to submit RX transfer to device %d-%d: %d", dev->bus, dev->address, res);
+ libusb_free_transfer(dev->rx_xfer);
+ dev->rx_xfer = NULL;
+ return res;
+ }
+ return 0;
+}
+
static int usb_discover(void)
{
int cnt, i, j, res;
@@ -135,7 +254,8 @@ static int usb_discover(void)
int idx = alloc_device();
if((res = libusb_get_string_descriptor_ascii(handle, devdesc.iSerialNumber, (uint8_t *)device_list[idx].serial, 256)) <= 0) {
- usbmuxd_log(LL_WARNING, "Could not get serial number for device %d-%d: %d", USB_INTERFACE, bus, address, res);
+ usbmuxd_log(LL_WARNING, "Could not get serial number for device %d-%d: %d", bus, address, res);
+ libusb_release_interface(handle, USB_INTERFACE);
libusb_close(handle);
continue;
}
@@ -145,7 +265,15 @@ static int usb_discover(void)
device_list[idx].dev = handle;
device_list[idx].alive = 1;
- device_add(&device_list[idx]);
+ if(device_add(&device_list[idx]) < 0) {
+ usb_disconnect(&device_list[j]);
+ continue;
+ }
+ if(start_rx(&device_list[idx]) < 0) {
+ device_remove(&device_list[j]);
+ usb_disconnect(&device_list[j]);
+ continue;
+ }
valid_count++;
}
for(j=0; j<num_devs; j++) {
@@ -232,7 +360,7 @@ int usb_get_timeout(void)
int usb_process(void)
{
- int res;
+ int i, res;
struct timeval tv;
tv.tv_sec = tv.tv_usec = 0;
res = libusb_handle_events_timeout(NULL, &tv);
@@ -240,6 +368,14 @@ int usb_process(void)
usbmuxd_log(LL_ERROR, "libusb_handle_events_timeout failed: %d", res);
return res;
}
+ // reap devices marked dead due to an RX error
+ for(i=0; i<num_devs; i++) {
+ if(device_list[i].dev && !device_list[i].alive) {
+ device_remove(&device_list[i]);
+ usb_disconnect(&device_list[i]);
+ }
+ }
+
if(dev_poll_remain_ms() <= 0) {
res = usb_discover();
if(res < 0) {
@@ -256,12 +392,12 @@ int usb_init(void)
usbmuxd_log(LL_DEBUG, "usb_init for linux / libusb 1.0");
res = libusb_init(NULL);
+ //libusb_set_debug(NULL, 3);
if(res != 0) {
usbmuxd_log(LL_FATAL, "libusb_init failed: %d", res);
return -1;
}
- device_id = 1;
num_devs = 1;
device_list = malloc(sizeof(*device_list) * num_devs);
memset(device_list, 0, sizeof(*device_list) * num_devs);