From 6d0183dd1824774f47e6cc995d25a237fddf2cb8 Mon Sep 17 00:00:00 2001 From: Eliyahu Stern Date: Thu, 22 Dec 2022 15:00:14 +0200 Subject: Support switching to different "modes" using vendor specific control messages. Use USBMUXD_DEFAULT_DEVICE_MODE env. var. to let the user control desired mode. --- src/usb.c | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- src/usb.h | 4 +++ 2 files changed, 113 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/usb.c b/src/usb.c index 4ff2d07..ca941bc 100644 --- a/src/usb.c +++ b/src/usb.c @@ -65,6 +65,15 @@ struct usb_device { struct libusb_device_descriptor devdesc; }; +struct mode_user_data { + uint8_t bus, address; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + unsigned int timeout; +}; + static struct collection device_list; static struct timeval next_dev_poll_time; @@ -357,6 +366,77 @@ static void get_langid_callback(struct libusb_transfer *transfer) } } +static int submit_vendor_specific(struct libusb_device_handle *handle, struct mode_user_data *user_data, libusb_transfer_cb_fn callback) { + struct libusb_transfer* ctrl_transfer = libusb_alloc_transfer(0); + unsigned char* buffer = malloc(LIBUSB_CONTROL_SETUP_SIZE); + uint8_t bRequestType = LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN | LIBUSB_RECIPIENT_DEVICE; + libusb_fill_control_setup(buffer, bRequestType, user_data->bRequest, user_data->wValue, user_data->wIndex, user_data->wLength); + + ctrl_transfer->flags = LIBUSB_TRANSFER_FREE_BUFFER | LIBUSB_TRANSFER_FREE_TRANSFER; + libusb_fill_control_transfer(ctrl_transfer, handle, buffer, callback, user_data, user_data->timeout); + + return libusb_submit_transfer(ctrl_transfer); +} + +static void switch_mode_cb(struct libusb_transfer* transfer) { + struct mode_user_data* user_data = transfer->user_data; + + if(transfer->status != LIBUSB_TRANSFER_COMPLETED) { + usbmuxd_log(LL_ERROR, "Failed to request mode switch for device %i-%i (%i)", user_data->bus, user_data->address, transfer->status); + free(transfer->user_data); + return; + } + + unsigned char *data = libusb_control_transfer_get_data(transfer); + + usbmuxd_log(LL_INFO, "Received response %i for switch mode %i for device %i-%i", data[0], user_data->wIndex, user_data->bus, user_data->address); + free(transfer->user_data); +} + +static void get_mode_cb(struct libusb_transfer* transfer) { + struct mode_user_data* user_data = transfer->user_data; + int res; + + if(transfer->status != LIBUSB_TRANSFER_COMPLETED) { + usbmuxd_log(LL_ERROR, "Failed to request get mode for device %i-%i (%i)", user_data->bus, user_data->address, transfer->status); + free(transfer->user_data); + return; + } + + unsigned char *data = libusb_control_transfer_get_data(transfer); + + char* desired_mode = getenv(ENV_DEVICE_MODE); + if (!desired_mode) { + user_data->wIndex = 0x1; + } + else if (!strncmp(desired_mode, "2", 1)) { + user_data->wIndex = 0x2; + } + else if (!strncmp(desired_mode, "3", 1)) { + user_data->wIndex = 0x3; + } + // Response is 3:3:3 for initial mode, 5:3:3 otherwise. + // In later commit, should infer the mode from available configurations and interfaces. + usbmuxd_log(LL_INFO, "Received response %i:%i:%i for get_mode request for device %i-%i", data[0], data[1], data[2], user_data->bus, user_data->address); + if (user_data->wIndex > 1 && data[0] == 3 && data[1] == 3 && data[2] == 3) { + // 3:3:3 means the initial mode + usbmuxd_log(LL_WARNING, "Switching device %i-%i mode to %i", user_data->bus, user_data->address, user_data->wIndex); + + user_data->bRequest = APPLE_VEND_SPECIFIC_SET_MODE; + user_data->wValue = 0; + user_data->wLength = 1; + + if ((res = submit_vendor_specific(transfer->dev_handle, user_data, switch_mode_cb)) != 0) { + usbmuxd_log(LL_WARNING, "Could not request to switch mode %i for device %i-%i (%i)", user_data->wIndex, user_data->bus, user_data->address, res); + } + } + else { + // in other modes, usually 5:3:3 + usbmuxd_log(LL_WARNING, "Skipping switch device %i-%i mode", user_data->bus, user_data->address); + free(transfer->user_data); + } +} + static int usb_device_add(libusb_device* dev) { int j, res; @@ -397,6 +477,25 @@ static int usb_device_add(libusb_device* dev) return -1; } + // On top of configurations, Apple have multiple "modes" for devices, namely: + // 1: An "initial" mode with 4 configurations + // 2: "Valeria" mode, where configuration 5 is included with interface for H.265 video capture (activated when recording screen with QuickTime in macOS) + // 3: "CDC NCM" mode, where configuration 5 is included with interface for Ethernet/USB (activated using internet-sharing feature in macOS) + // Request current mode asynchroniously, so it can be changed in callback if needed + usbmuxd_log(LL_INFO, "Requesting current mode from device %i-%i", bus, address); + struct mode_user_data* user_data = malloc(sizeof(struct mode_user_data)); + user_data->bus = bus; + user_data->address = address; + user_data->bRequest = APPLE_VEND_SPECIFIC_GET_MODE; + user_data->wValue = 0; + user_data->wIndex = 0; + user_data->wLength = 4; + user_data->timeout = 1000; + + if (submit_vendor_specific(handle, user_data, get_mode_cb) != 0) { + usbmuxd_log(LL_WARNING, "Could not request current mode from device %d-%d", bus, address); + } + // Potentially, the rest of this function can be factored out and called from get_mode_callback/switch_mode_callback (where desired mode is known) int desired_config = devdesc.bNumConfigurations; if (desired_config > 4) { if (desired_config > 5) { @@ -412,14 +511,16 @@ static int usb_device_add(libusb_device* dev) desired_config = 4; break; } - if (config->bNumInterfaces != 3) { - usbmuxd_log(LL_WARNING, "Device %d-%d: Ignoring possibly bad configuration 5, choosing configuration 4 instead.", bus, address); - desired_config = 4; - break; - } - intf = &config->interface[2].altsetting[0]; - if (intf->bInterfaceClass != 0xFF || intf->bInterfaceSubClass != 0x2A || intf->bInterfaceProtocol != 0xFF) { - usbmuxd_log(LL_WARNING, "Device %d-%d: Ignoring possibly bad configuration 5, choosing configuration 4 instead.", bus, address); + // In Valeria mode, there are 3 interfaces and usbmuxd is at 2 + // In CDC NCM mode, there are 4 interfaces and usbmuxd is at 1 + // Otherwize, 0 is expected to be of a different class. + int usbmux_intf_index = config->bNumInterfaces == 3 ? 2 : config->bNumInterfaces == 4 ? 1 : 0; + intf = &config->interface[usbmux_intf_index].altsetting[0]; + if ( + intf->bInterfaceClass != INTERFACE_CLASS || + intf->bInterfaceSubClass != INTERFACE_SUBCLASS || + intf->bInterfaceProtocol != INTERFACE_PROTOCOL) { + usbmuxd_log(LL_WARNING, "Device %d-%d: can't find usbmux interface in configuration 5, choosing configuration 4 instead.", bus, address); desired_config = 4; break; } diff --git a/src/usb.h b/src/usb.h index b1b9bb9..4e44cce 100644 --- a/src/usb.h +++ b/src/usb.h @@ -50,6 +50,10 @@ #define PID_APPLE_SILICON_RESTORE_LOW 0x1901 #define PID_APPLE_SILICON_RESTORE_MAX 0x1905 +#define ENV_DEVICE_MODE "USBMUXD_DEFAULT_DEVICE_MODE" +#define APPLE_VEND_SPECIFIC_GET_MODE 0x45 +#define APPLE_VEND_SPECIFIC_SET_MODE 0x52 + struct usb_device; int usb_init(void); -- cgit v1.1-32-gdbae