From b752fbc1dd92238a34c888fbaef921aa802c4a82 Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Tue, 11 Nov 2014 14:47:22 +0100 Subject: usb: Implement device discovery using libusb hotplug events --- src/usb.c | 470 +++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 268 insertions(+), 202 deletions(-) diff --git a/src/usb.c b/src/usb.c index f5e8092..3ce2abb 100644 --- a/src/usb.c +++ b/src/usb.c @@ -36,6 +36,10 @@ #include "device.h" #include "utils.h" +#if (defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102)) || (defined(LIBUSBX_API_VERSION) && (LIBUSBX_API_VERSION >= 0x01000102)) +#define HAVE_LIBUSB_HOTPLUG_API 1 +#endif + // interval for device connection/disconnection polling, in milliseconds // we need this because there is currently no asynchronous device discovery mechanism in libusb #define DEVICE_POLL_TIME 1000 @@ -65,6 +69,7 @@ static struct timeval next_dev_poll_time; static int devlist_failures; static int device_polling; +static int device_hotplug = 1; static void usb_disconnect(struct usb_device *dev) { @@ -253,9 +258,217 @@ static int start_rx_loop(struct usb_device *dev) return 0; } +static int usb_device_add(libusb_device* dev) +{ + int j, res; + // the following are non-blocking operations on the device list + uint8_t bus = libusb_get_bus_number(dev); + uint8_t address = libusb_get_device_address(dev); + struct libusb_device_descriptor devdesc; + int found = 0; + FOREACH(struct usb_device *usbdev, &device_list) { + if(usbdev->bus == bus && usbdev->address == address) { + usbdev->alive = 1; + found = 1; + break; + } + } ENDFOREACH + if(found) + return 0; //device already found + + if((res = libusb_get_device_descriptor(dev, &devdesc)) != 0) { + usbmuxd_log(LL_WARNING, "Could not get device descriptor for device %d-%d: %d", bus, address, res); + return -1; + } + if(devdesc.idVendor != VID_APPLE) + return -1; + if((devdesc.idProduct < PID_RANGE_LOW) || + (devdesc.idProduct > PID_RANGE_MAX)) + return -1; + libusb_device_handle *handle; + usbmuxd_log(LL_INFO, "Found new device with v/p %04x:%04x at %d-%d", devdesc.idVendor, devdesc.idProduct, bus, address); + // potentially blocking operations follow; they will only run when new devices are detected, which is acceptable + if((res = libusb_open(dev, &handle)) != 0) { + usbmuxd_log(LL_WARNING, "Could not open device %d-%d: %d", bus, address, res); + return -1; + } + + int current_config = 0; + if((res = libusb_get_configuration(handle, ¤t_config)) != 0) { + usbmuxd_log(LL_WARNING, "Could not get configuration for device %d-%d: %d", bus, address, res); + libusb_close(handle); + return -1; + } + if (current_config != devdesc.bNumConfigurations) { + struct libusb_config_descriptor *config; + if((res = libusb_get_active_config_descriptor(dev, &config)) != 0) { + usbmuxd_log(LL_NOTICE, "Could not get old configuration descriptor for device %d-%d: %d", bus, address, res); + } else { + for(j=0; jbNumInterfaces; j++) { + const struct libusb_interface_descriptor *intf = &config->interface[j].altsetting[0]; + if((res = libusb_kernel_driver_active(handle, intf->bInterfaceNumber)) < 0) { + usbmuxd_log(LL_NOTICE, "Could not check kernel ownership of interface %d for device %d-%d: %d", intf->bInterfaceNumber, bus, address, res); + continue; + } + if(res == 1) { + usbmuxd_log(LL_INFO, "Detaching kernel driver for device %d-%d, interface %d", bus, address, intf->bInterfaceNumber); + if((res = libusb_detach_kernel_driver(handle, intf->bInterfaceNumber)) < 0) { + usbmuxd_log(LL_WARNING, "Could not detach kernel driver (%d), configuration change will probably fail!", res); + continue; + } + } + } + libusb_free_config_descriptor(config); + } + + usbmuxd_log(LL_INFO, "Setting configuration for device %d-%d, from %d to %d", bus, address, current_config, devdesc.bNumConfigurations); + if((res = libusb_set_configuration(handle, devdesc.bNumConfigurations)) != 0) { + usbmuxd_log(LL_WARNING, "Could not set configuration %d for device %d-%d: %d", devdesc.bNumConfigurations, bus, address, res); + libusb_close(handle); + return -1; + } + } + + struct libusb_config_descriptor *config; + if((res = libusb_get_active_config_descriptor(dev, &config)) != 0) { + usbmuxd_log(LL_WARNING, "Could not get configuration descriptor for device %d-%d: %d", bus, address, res); + libusb_close(handle); + return -1; + } + + struct usb_device *usbdev; + usbdev = malloc(sizeof(struct usb_device)); + memset(usbdev, 0, sizeof(*usbdev)); + + for(j=0; jbNumInterfaces; j++) { + const struct libusb_interface_descriptor *intf = &config->interface[j].altsetting[0]; + if(intf->bInterfaceClass != INTERFACE_CLASS || + intf->bInterfaceSubClass != INTERFACE_SUBCLASS || + intf->bInterfaceProtocol != INTERFACE_PROTOCOL) + continue; + if(intf->bNumEndpoints != 2) { + usbmuxd_log(LL_WARNING, "Endpoint count mismatch for interface %d of device %d-%d", intf->bInterfaceNumber, bus, address); + continue; + } + if((intf->endpoint[0].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_OUT && + (intf->endpoint[1].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) { + usbdev->interface = intf->bInterfaceNumber; + usbdev->ep_out = intf->endpoint[0].bEndpointAddress; + usbdev->ep_in = intf->endpoint[1].bEndpointAddress; + usbmuxd_log(LL_INFO, "Found interface %d with endpoints %02x/%02x for device %d-%d", usbdev->interface, usbdev->ep_out, usbdev->ep_in, bus, address); + break; + } else if((intf->endpoint[1].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_OUT && + (intf->endpoint[0].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) { + usbdev->interface = intf->bInterfaceNumber; + usbdev->ep_out = intf->endpoint[1].bEndpointAddress; + usbdev->ep_in = intf->endpoint[0].bEndpointAddress; + usbmuxd_log(LL_INFO, "Found interface %d with swapped endpoints %02x/%02x for device %d-%d", usbdev->interface, usbdev->ep_out, usbdev->ep_in, bus, address); + break; + } else { + usbmuxd_log(LL_WARNING, "Endpoint type mismatch for interface %d of device %d-%d", intf->bInterfaceNumber, bus, address); + } + } + + if(j == config->bNumInterfaces) { + usbmuxd_log(LL_WARNING, "Could not find a suitable USB interface for device %d-%d", bus, address); + libusb_free_config_descriptor(config); + libusb_close(handle); + free(usbdev); + return -1; + } + + libusb_free_config_descriptor(config); + + if((res = libusb_claim_interface(handle, usbdev->interface)) != 0) { + usbmuxd_log(LL_WARNING, "Could not claim interface %d for device %d-%d: %d", usbdev->interface, bus, address, res); + libusb_close(handle); + free(usbdev); + return -1; + } + + if((res = libusb_get_string_descriptor_ascii(handle, devdesc.iSerialNumber, (uint8_t *)usbdev->serial, 256)) <= 0) { + usbmuxd_log(LL_WARNING, "Could not get serial number for device %d-%d: %d", bus, address, res); + libusb_release_interface(handle, usbdev->interface); + libusb_close(handle); + free(usbdev); + return -1; + } + usbdev->serial[res] = 0; + usbdev->bus = bus; + usbdev->address = address; + usbdev->vid = devdesc.idVendor; + usbdev->pid = devdesc.idProduct; + usbdev->speed = 480000000; + usbdev->dev = handle; + usbdev->alive = 1; + usbdev->wMaxPacketSize = libusb_get_max_packet_size(dev, usbdev->ep_out); + if (usbdev->wMaxPacketSize <= 0) { + usbmuxd_log(LL_ERROR, "Could not determine wMaxPacketSize for device %d-%d, setting to 64", usbdev->bus, usbdev->address); + usbdev->wMaxPacketSize = 64; + } else { + usbmuxd_log(LL_INFO, "Using wMaxPacketSize=%d for device %d-%d", usbdev->wMaxPacketSize, usbdev->bus, usbdev->address); + } + + switch (libusb_get_device_speed(dev)) { + case LIBUSB_SPEED_LOW: + usbdev->speed = 1500000; + break; + case LIBUSB_SPEED_FULL: + usbdev->speed = 12000000; + break; + case LIBUSB_SPEED_SUPER: + usbdev->speed = 5000000000; + break; + case LIBUSB_SPEED_HIGH: + case LIBUSB_SPEED_UNKNOWN: + default: + usbdev->speed = 480000000; + break; + } + + usbmuxd_log(LL_INFO, "USB Speed is %g MBit/s for device %d-%d", (double)(usbdev->speed / 1000000.0), usbdev->bus, usbdev->address); + + collection_init(&usbdev->tx_xfers); + collection_init(&usbdev->rx_xfers); + + collection_add(&device_list, usbdev); + + if(device_add(usbdev) < 0) { + usb_disconnect(usbdev); + return -1; + } + + // Spin up NUM_RX_LOOPS parallel usb data retrieval loops + // Old usbmuxds used only 1 rx loop, but that leaves the + // USB port sleeping most of the time + int rx_loops = NUM_RX_LOOPS; + for (rx_loops = NUM_RX_LOOPS; rx_loops > 0; rx_loops--) { + if(start_rx_loop(usbdev) < 0) { + usbmuxd_log(LL_WARNING, "Failed to start RX loop number %d", NUM_RX_LOOPS - rx_loops); + } + } + + // Ensure we have at least 1 RX loop going + if (rx_loops == NUM_RX_LOOPS) { + usbmuxd_log(LL_FATAL, "Failed to start any RX loop for device %d-%d", + usbdev->bus, usbdev->address); + device_remove(usbdev); + usb_disconnect(usbdev); + return -1; + } else if (rx_loops > 0) { + usbmuxd_log(LL_WARNING, "Failed to start all %d RX loops. Going on with %d loops. " + "This may have negative impact on device read speed.", + NUM_RX_LOOPS, NUM_RX_LOOPS - rx_loops); + } else { + usbmuxd_log(LL_DEBUG, "All %d RX loops started successfully", NUM_RX_LOOPS); + } + + return 0; +} + int usb_discover(void) { - int cnt, i, j, res; + int cnt, i; int valid_count = 0; libusb_device **devs; @@ -288,209 +501,10 @@ int usb_discover(void) // Enumerate all USB devices and mark the ones we already know // about as live, again for(i=0; ibus == bus && usbdev->address == address) { - valid_count++; - usbdev->alive = 1; - found = 1; - break; - } - } ENDFOREACH - if(found) - continue; //device already found - if((res = libusb_get_device_descriptor(dev, &devdesc)) != 0) { - usbmuxd_log(LL_WARNING, "Could not get device descriptor for device %d-%d: %d", bus, address, res); - continue; - } - if(devdesc.idVendor != VID_APPLE) - continue; - if((devdesc.idProduct < PID_RANGE_LOW) || - (devdesc.idProduct > PID_RANGE_MAX)) - continue; - libusb_device_handle *handle; - usbmuxd_log(LL_INFO, "Found new device with v/p %04x:%04x at %d-%d", devdesc.idVendor, devdesc.idProduct, bus, address); - // potentially blocking operations follow; they will only run when new devices are detected, which is acceptable - if((res = libusb_open(dev, &handle)) != 0) { - usbmuxd_log(LL_WARNING, "Could not open device %d-%d: %d", bus, address, res); + if (usb_device_add(dev) < 0) { continue; } - - int current_config = 0; - if((res = libusb_get_configuration(handle, ¤t_config)) != 0) { - usbmuxd_log(LL_WARNING, "Could not get configuration for device %d-%d: %d", bus, address, res); - libusb_close(handle); - continue; - } - if (current_config != devdesc.bNumConfigurations) { - struct libusb_config_descriptor *config; - if((res = libusb_get_active_config_descriptor(dev, &config)) != 0) { - usbmuxd_log(LL_NOTICE, "Could not get old configuration descriptor for device %d-%d: %d", bus, address, res); - } else { - for(j=0; jbNumInterfaces; j++) { - const struct libusb_interface_descriptor *intf = &config->interface[j].altsetting[0]; - if((res = libusb_kernel_driver_active(handle, intf->bInterfaceNumber)) < 0) { - usbmuxd_log(LL_NOTICE, "Could not check kernel ownership of interface %d for device %d-%d: %d", intf->bInterfaceNumber, bus, address, res); - continue; - } - if(res == 1) { - usbmuxd_log(LL_INFO, "Detaching kernel driver for device %d-%d, interface %d", bus, address, intf->bInterfaceNumber); - if((res = libusb_detach_kernel_driver(handle, intf->bInterfaceNumber)) < 0) { - usbmuxd_log(LL_WARNING, "Could not detach kernel driver (%d), configuration change will probably fail!", res); - continue; - } - } - } - libusb_free_config_descriptor(config); - } - - usbmuxd_log(LL_INFO, "Setting configuration for device %d-%d, from %d to %d", bus, address, current_config, devdesc.bNumConfigurations); - if((res = libusb_set_configuration(handle, devdesc.bNumConfigurations)) != 0) { - usbmuxd_log(LL_WARNING, "Could not set configuration %d for device %d-%d: %d", devdesc.bNumConfigurations, bus, address, res); - libusb_close(handle); - continue; - } - } - - struct libusb_config_descriptor *config; - if((res = libusb_get_active_config_descriptor(dev, &config)) != 0) { - usbmuxd_log(LL_WARNING, "Could not get configuration descriptor for device %d-%d: %d", bus, address, res); - libusb_close(handle); - continue; - } - - struct usb_device *usbdev; - usbdev = malloc(sizeof(struct usb_device)); - memset(usbdev, 0, sizeof(*usbdev)); - - for(j=0; jbNumInterfaces; j++) { - const struct libusb_interface_descriptor *intf = &config->interface[j].altsetting[0]; - if(intf->bInterfaceClass != INTERFACE_CLASS || - intf->bInterfaceSubClass != INTERFACE_SUBCLASS || - intf->bInterfaceProtocol != INTERFACE_PROTOCOL) - continue; - if(intf->bNumEndpoints != 2) { - usbmuxd_log(LL_WARNING, "Endpoint count mismatch for interface %d of device %d-%d", intf->bInterfaceNumber, bus, address); - continue; - } - if((intf->endpoint[0].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_OUT && - (intf->endpoint[1].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) { - usbdev->interface = intf->bInterfaceNumber; - usbdev->ep_out = intf->endpoint[0].bEndpointAddress; - usbdev->ep_in = intf->endpoint[1].bEndpointAddress; - usbmuxd_log(LL_INFO, "Found interface %d with endpoints %02x/%02x for device %d-%d", usbdev->interface, usbdev->ep_out, usbdev->ep_in, bus, address); - break; - } else if((intf->endpoint[1].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_OUT && - (intf->endpoint[0].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) { - usbdev->interface = intf->bInterfaceNumber; - usbdev->ep_out = intf->endpoint[1].bEndpointAddress; - usbdev->ep_in = intf->endpoint[0].bEndpointAddress; - usbmuxd_log(LL_INFO, "Found interface %d with swapped endpoints %02x/%02x for device %d-%d", usbdev->interface, usbdev->ep_out, usbdev->ep_in, bus, address); - break; - } else { - usbmuxd_log(LL_WARNING, "Endpoint type mismatch for interface %d of device %d-%d", intf->bInterfaceNumber, bus, address); - } - } - - if(j == config->bNumInterfaces) { - usbmuxd_log(LL_WARNING, "Could not find a suitable USB interface for device %d-%d", bus, address); - libusb_free_config_descriptor(config); - libusb_close(handle); - free(usbdev); - continue; - } - - libusb_free_config_descriptor(config); - - if((res = libusb_claim_interface(handle, usbdev->interface)) != 0) { - usbmuxd_log(LL_WARNING, "Could not claim interface %d for device %d-%d: %d", usbdev->interface, bus, address, res); - libusb_close(handle); - free(usbdev); - continue; - } - - if((res = libusb_get_string_descriptor_ascii(handle, devdesc.iSerialNumber, (uint8_t *)usbdev->serial, 256)) <= 0) { - usbmuxd_log(LL_WARNING, "Could not get serial number for device %d-%d: %d", bus, address, res); - libusb_release_interface(handle, usbdev->interface); - libusb_close(handle); - free(usbdev); - continue; - } - usbdev->serial[res] = 0; - usbdev->bus = bus; - usbdev->address = address; - usbdev->vid = devdesc.idVendor; - usbdev->pid = devdesc.idProduct; - usbdev->speed = 480000000; - usbdev->dev = handle; - usbdev->alive = 1; - usbdev->wMaxPacketSize = libusb_get_max_packet_size(dev, usbdev->ep_out); - if (usbdev->wMaxPacketSize <= 0) { - usbmuxd_log(LL_ERROR, "Could not determine wMaxPacketSize for device %d-%d, setting to 64", usbdev->bus, usbdev->address); - usbdev->wMaxPacketSize = 64; - } else { - usbmuxd_log(LL_INFO, "Using wMaxPacketSize=%d for device %d-%d", usbdev->wMaxPacketSize, usbdev->bus, usbdev->address); - } - - switch (libusb_get_device_speed(dev)) { - case LIBUSB_SPEED_LOW: - usbdev->speed = 1500000; - break; - case LIBUSB_SPEED_FULL: - usbdev->speed = 12000000; - break; - case LIBUSB_SPEED_SUPER: - usbdev->speed = 5000000000; - break; - case LIBUSB_SPEED_HIGH: - case LIBUSB_SPEED_UNKNOWN: - default: - usbdev->speed = 480000000; - break; - } - - usbmuxd_log(LL_INFO, "USB Speed is %g MBit/s for device %d-%d", (double)(usbdev->speed / 1000000.0), usbdev->bus, usbdev->address); - - collection_init(&usbdev->tx_xfers); - collection_init(&usbdev->rx_xfers); - - collection_add(&device_list, usbdev); - - if(device_add(usbdev) < 0) { - usb_disconnect(usbdev); - continue; - } - - // Spin up NUM_RX_LOOPS parallel usb data retrieval loops - // Old usbmuxds used only 1 rx loop, but that leaves the - // USB port sleeping most of the time - int rx_loops = NUM_RX_LOOPS; - for (rx_loops = NUM_RX_LOOPS; rx_loops > 0; rx_loops--) { - if(start_rx_loop(usbdev) < 0) { - usbmuxd_log(LL_WARNING, "Failed to start RX loop number %d", NUM_RX_LOOPS - rx_loops); - } - } - - // Ensure we have at least 1 RX loop going - if (rx_loops == NUM_RX_LOOPS) { - usbmuxd_log(LL_FATAL, "Failed to start any RX loop for device %d-%d", - usbdev->bus, usbdev->address); - device_remove(usbdev); - usb_disconnect(usbdev); - continue; - } else if (rx_loops > 0) { - usbmuxd_log(LL_WARNING, "Failed to start all %d RX loops. Going on with %d loops. " - "This may have negative impact on device read speed.", - NUM_RX_LOOPS, NUM_RX_LOOPS - rx_loops); - } else { - usbmuxd_log(LL_DEBUG, "All %d RX loops started successfully", NUM_RX_LOOPS); - } - valid_count++; } @@ -560,6 +574,7 @@ void usb_autodiscover(int enable) { usbmuxd_log(LL_DEBUG, "usb polling enable: %d", enable); device_polling = enable; + device_hotplug = enable; } static int dev_poll_remain_ms(void) @@ -649,6 +664,32 @@ int usb_process_timeout(int msec) return 0; } +#ifdef HAVE_LIBUSB_HOTPLUG_API +static libusb_hotplug_callback_handle usb_hotplug_cb_handle; + +static int usb_hotplug_cb(libusb_context *ctx, libusb_device *device, libusb_hotplug_event event, void *user_data) +{ + if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) { + if (device_hotplug) { + usb_device_add(device); + } + } else if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == event) { + uint8_t bus = libusb_get_bus_number(device); + uint8_t address = libusb_get_device_address(device); + FOREACH(struct usb_device *usbdev, &device_list) { + if(usbdev->bus == bus && usbdev->address == address) { + usbdev->alive = 0; + device_remove(usbdev); + break; + } + } ENDFOREACH + } else { + usbmuxd_log(LL_ERROR, "Unhandled event %d", event); + } + return 0; +} +#endif + int usb_init(void) { int res; @@ -665,12 +706,37 @@ int usb_init(void) collection_init(&device_list); - return usb_discover(); +#ifdef HAVE_LIBUSB_HOTPLUG_API + if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + usbmuxd_log(LL_INFO, "Registering for libusb hotplug events"); + res = libusb_hotplug_register_callback(NULL, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, LIBUSB_HOTPLUG_ENUMERATE, VID_APPLE, LIBUSB_HOTPLUG_MATCH_ANY, 0, usb_hotplug_cb, NULL, &usb_hotplug_cb_handle); + if (res == LIBUSB_SUCCESS) { + device_polling = 0; + } else { + usbmuxd_log(LL_ERROR, "ERROR: Could not register for libusb hotplug events (%d)", res); + } + } else { + usbmuxd_log(LL_ERROR, "libusb does not support hotplug events"); + } +#endif + if (device_polling) { + res = usb_discover(); + if (res >= 0) { + } + } else { + res = collection_count(&device_list); + } + return res; } void usb_shutdown(void) { usbmuxd_log(LL_DEBUG, "usb_shutdown"); + +#ifdef HAVE_LIBUSB_HOTPLUG_API + libusb_hotplug_deregister_callback(NULL, usb_hotplug_cb_handle); +#endif + FOREACH(struct usb_device *usbdev, &device_list) { device_remove(usbdev); usb_disconnect(usbdev); -- cgit v1.1-32-gdbae