From 6e3aa5b006febe7628406e9fe214a59b6128da5d Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 13 May 2024 18:44:51 -0500 Subject: [PATCH] Handle SET_INTEFACE and SET_CONFIGURATION internally using Android APIs If we send SET_CONFIGURATION normally, the USB device will be completely removed from the bus. --- .../cgutman/usbip/service/UsbIpService.java | 51 ++++++++++++------- .../cgutman/usbip/usb/UsbControlHelper.java | 45 +++++++++++++++- 2 files changed, 76 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/cgutman/usbip/service/UsbIpService.java b/app/src/main/java/org/cgutman/usbip/service/UsbIpService.java index 13644b0..fc5855c 100644 --- a/app/src/main/java/org/cgutman/usbip/service/UsbIpService.java +++ b/app/src/main/java/org/cgutman/usbip/service/UsbIpService.java @@ -340,10 +340,15 @@ private UsbDeviceInfo getInfoForDevice(UsbDevice dev, UsbDeviceConnection devCon ipDev.bDeviceClass = (byte) dev.getDeviceClass(); ipDev.bDeviceSubClass = (byte) dev.getDeviceSubclass(); ipDev.bDeviceProtocol = (byte) dev.getDeviceProtocol(); - + ipDev.bConfigurationValue = 0; - ipDev.bNumConfigurations = 1; - + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ipDev.bNumConfigurations = (byte) dev.getConfigurationCount(); + } + else { + ipDev.bNumConfigurations = 1; + } + ipDev.bNumInterfaces = (byte) dev.getInterfaceCount(); info.dev = ipDev; @@ -575,21 +580,29 @@ public void submitUrbRequest(Socket s, UsbIpSubmitUrb msg) { context.activeMessages.add(msg); int res; - - do { - res = XferUtils.doControlTransfer(devConn, requestType, request, value, index, - (requestType & 0x80) != 0 ? reply.inData : msg.outData, length, 1000); - - if (context.requestPool.isShutdown()) { - // Bail if the queue is being torn down - return; - } - - if (!context.activeMessages.contains(msg)) { - // Somebody cancelled the URB, return without responding - return; - } - } while (res == -110); // ETIMEDOUT + + // We have to handle certain control requests (SET_CONFIGURATION/SET_INTERFACE) by calling + // Android APIs rather than just submitting the URB directly to the device + if (!UsbControlHelper.handleInternalControlTransfer(dev, devConn, requestType, request, value, index)) { + do { + res = XferUtils.doControlTransfer(devConn, requestType, request, value, index, + (requestType & 0x80) != 0 ? reply.inData : msg.outData, length, 1000); + + if (context.requestPool.isShutdown()) { + // Bail if the queue is being torn down + return; + } + + if (!context.activeMessages.contains(msg)) { + // Somebody cancelled the URB, return without responding + return; + } + } while (res == -110); // ETIMEDOUT + } + else { + // Handled the request internally + res = 0; + } if (res < 0) { reply.status = res; @@ -731,7 +744,7 @@ public boolean attachToDevice(Socket s, String busId) { // Claim all interfaces since we don't know which one the client wants for (int i = 0; i < dev.getInterfaceCount(); i++) { if (!devConn.claimInterface(dev.getInterface(i), true)) { - System.err.println("Unabled to claim interface "+dev.getInterface(i).getId()); + System.err.println("Unable to claim interface "+dev.getInterface(i).getId()); } } diff --git a/app/src/main/java/org/cgutman/usbip/usb/UsbControlHelper.java b/app/src/main/java/org/cgutman/usbip/usb/UsbControlHelper.java index 3bcc28a..576f60b 100644 --- a/app/src/main/java/org/cgutman/usbip/usb/UsbControlHelper.java +++ b/app/src/main/java/org/cgutman/usbip/usb/UsbControlHelper.java @@ -1,7 +1,11 @@ package org.cgutman.usbip.usb; +import android.hardware.usb.UsbConfiguration; +import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.os.Build; public class UsbControlHelper { @@ -13,7 +17,13 @@ public class UsbControlHelper { private static final int CLEAR_FEATURE_REQUEST_TYPE = 0x02; private static final int CLEAR_FEATURE_REQUEST = 0x01; - + + private static final int SET_CONFIGURATION_REQUEST_TYPE = 0x00; + private static final int SET_CONFIGURATION_REQUEST = 0x9; + + private static final int SET_INTERFACE_REQUEST_TYPE = 0x01; + private static final int SET_INTERFACE_REQUEST = 0xB; + private static final int FEATURE_VALUE_HALT = 0x00; private static final int DEVICE_DESCRIPTOR_TYPE = 1; @@ -60,4 +70,37 @@ public static boolean clearHaltCondition(UsbDeviceConnection devConn, UsbEndpoin return true; } + + public static boolean handleInternalControlTransfer(UsbDevice dev, UsbDeviceConnection devConn, int requestType, int request, int value, int index) { + // Mask out possible sign expansions + requestType &= 0xFF; + request &= 0xFF; + value &= 0xFFFF; + index &= 0xFFFF; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (requestType == SET_CONFIGURATION_REQUEST_TYPE && request == SET_CONFIGURATION_REQUEST) { + for (int i = 0; i < dev.getConfigurationCount(); i++) { + UsbConfiguration config = dev.getConfiguration(i); + if (config.getId() == value) { + devConn.setConfiguration(config); + System.out.println("Handled SET_CONFIGURATION via Android API"); + return true; + } + } + } + else if (requestType == SET_INTERFACE_REQUEST_TYPE && request == SET_INTERFACE_REQUEST) { + for (int i = 0; i < dev.getInterfaceCount(); i++) { + UsbInterface iface = dev.getInterface(i); + if (iface.getId() == index && iface.getAlternateSetting() == value) { + devConn.setInterface(iface); + System.out.println("Handled SET_INTERFACE via Android API"); + return true; + } + } + } + } + + return false; + } }