From b4b4138a31b2548681ad8af29758e6767c75ae7b Mon Sep 17 00:00:00 2001 From: Nathan Hjelm Date: Tue, 30 Nov 2021 15:10:01 -0700 Subject: [PATCH] darwin: fix behavior of libusb_set_interface_alt_setting when it stalls the pipe In some versions of macOS a pipe stall returned when setting the alternate interface causes the interface to become unusable. To handle this case the backend was always re-claiming the interface before clearing the pipe stall. In macOS Monterey unconditionally re-claiming the interface leads to an error due to the process already having exclusive access. To resolve this issue we attempt to clear the halt and only re-claim the interface if clearing the pipe stall returns kIOUSBUnknownPipeErr. Tested with 12.0.1 and 10.13 and get the expected results in both cases with a custom USB device that has this behavior. Fixes #1011 Closes #1031 Signed-off-by: Nathan Hjelm --- libusb/os/darwin_usb.c | 58 +++++++++++++++++++++++++++++++++----------------- libusb/version_nano.h | 2 +- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/libusb/os/darwin_usb.c b/libusb/os/darwin_usb.c index 15457f7..5f2fa3c 100644 --- a/libusb/os/darwin_usb.c +++ b/libusb/os/darwin_usb.c @@ -1,8 +1,8 @@ /* -*- Mode: C; indent-tabs-mode:nil -*- */ /* * darwin backend for libusb 1.0 - * Copyright © 2008-2020 Nathan Hjelm - * Copyright © 2019-2020 Google LLC. All rights reserved. + * Copyright © 2008-2021 Nathan Hjelm + * Copyright © 2019-2021 Google LLC. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -160,11 +160,12 @@ static enum libusb_error darwin_to_libusb (IOReturn result) { return LIBUSB_ERROR_INVALID_PARAM; case kIOUSBTransactionTimeout: return LIBUSB_ERROR_TIMEOUT; + case kIOUSBUnknownPipeErr: + return LIBUSB_ERROR_NOT_FOUND; case kIOReturnNotResponding: case kIOReturnAborted: case kIOReturnError: case kIOUSBNoAsyncPortErr: - case kIOUSBUnknownPipeErr: default: return LIBUSB_ERROR_OTHER; } @@ -1586,12 +1587,34 @@ static int darwin_release_interface(struct libusb_device_handle *dev_handle, uin return darwin_to_libusb (kresult); } +static int check_alt_setting_and_clear_halt(struct libusb_device_handle *dev_handle, uint8_t altsetting, struct darwin_interface *cInterface) { + enum libusb_error ret; + IOReturn kresult; + uint8_t current_alt_setting; + + kresult = (*(cInterface->interface))->GetAlternateSetting (cInterface->interface, ¤t_alt_setting); + if (kresult == kIOReturnSuccess && altsetting != current_alt_setting) { + return LIBUSB_ERROR_PIPE; + } + + for (int i = 0 ; i < cInterface->num_endpoints ; i++) { + ret = darwin_clear_halt(dev_handle, cInterface->endpoint_addrs[i]); + if (LIBUSB_SUCCESS != ret) { + usbi_warn(HANDLE_CTX (dev_handle), "error clearing pipe halt for endpoint %d", i); + if (LIBUSB_ERROR_NOT_FOUND == ret) { + /* may need to re-open the interface */ + return ret; + } + } + } + + return LIBUSB_SUCCESS; +} + static int darwin_set_interface_altsetting(struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting) { struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); IOReturn kresult; enum libusb_error ret; - int i; - uint8_t old_alt_setting; /* current interface */ struct darwin_interface *cInterface = &priv->interfaces[iface]; @@ -1623,23 +1646,18 @@ static int darwin_set_interface_altsetting(struct libusb_device_handle *dev_hand Mimic the behaviour in e.g. the Linux kernel: in such case, reset all endpoints of the interface (as would have been done per 9.1.1.5) and return success. */ - /* For some reason we need to reclaim the interface after the pipe error */ - ret = darwin_claim_interface (dev_handle, iface); - - if (ret) { - darwin_release_interface (dev_handle, iface); - usbi_err (HANDLE_CTX (dev_handle), "could not reclaim interface"); + ret = check_alt_setting_and_clear_halt(dev_handle, altsetting, cInterface); + if (LIBUSB_ERROR_NOT_FOUND == ret) { + /* For some reason we need to reclaim the interface after the pipe error with some versions of macOS */ + ret = darwin_claim_interface (dev_handle, iface); + if (LIBUSB_SUCCESS != ret) { + darwin_release_interface (dev_handle, iface); + usbi_err (HANDLE_CTX (dev_handle), "could not reclaim interface: %s", darwin_error_str(kresult)); + } + ret = check_alt_setting_and_clear_halt(dev_handle, altsetting, cInterface); } - /* Return error if a change to another value was attempted */ - kresult = (*(cInterface->interface))->GetAlternateSetting (cInterface->interface, &old_alt_setting); - if (kresult == kIOReturnSuccess && altsetting != old_alt_setting) - return LIBUSB_ERROR_PIPE; - - for (i = 0 ; i < cInterface->num_endpoints ; i++) - darwin_clear_halt(dev_handle, cInterface->endpoint_addrs[i]); - - return LIBUSB_SUCCESS; + return ret; } static int darwin_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) { diff --git a/libusb/version_nano.h b/libusb/version_nano.h index 04fa88c..09bf359 100644 --- a/libusb/version_nano.h +++ b/libusb/version_nano.h @@ -1 +1 @@ -#define LIBUSB_NANO 11679 +#define LIBUSB_NANO 11680 -- 2.7.4