diff --git a/jni/Android.mk b/jni/Android.mk new file mode 100644 index 0000000..5053e7d --- /dev/null +++ b/jni/Android.mk @@ -0,0 +1 @@ +include $(call all-subdir-makefiles) diff --git a/jni/Application.mk b/jni/Application.mk new file mode 100644 index 0000000..1c97370 --- /dev/null +++ b/jni/Application.mk @@ -0,0 +1,10 @@ +# Application.mk for USB/IP Server + +# Our minimum version is Android 3.1 +APP_PLATFORM := android-12 + +# Build for all ABIs +APP_ABI := all + +# We want an optimized build +APP_OPTIM := release diff --git a/jni/errno/Android.mk b/jni/errno/Android.mk new file mode 100644 index 0000000..2031596 --- /dev/null +++ b/jni/errno/Android.mk @@ -0,0 +1,12 @@ +# Android.mk for errno +MY_LOCAL_PATH := $(call my-dir) + +include $(call all-subdir-makefiles) + +LOCAL_PATH := $(MY_LOCAL_PATH) + +include $(CLEAR_VARS) +LOCAL_MODULE := errno +LOCAL_SRC_FILES := errno_jni.c + +include $(BUILD_SHARED_LIBRARY) diff --git a/jni/errno/errno_jni.c b/jni/errno/errno_jni.c new file mode 100644 index 0000000..4195d31 --- /dev/null +++ b/jni/errno/errno_jni.c @@ -0,0 +1,11 @@ +#include +#include + +#include + +JNIEXPORT jint JNICALL +Java_org_cgutman_usbip_errno_Errno_getErrno( + JNIEnv *env, jobject this) +{ + return errno; +} diff --git a/libs/arm64-v8a/liberrno.so b/libs/arm64-v8a/liberrno.so new file mode 100755 index 0000000..6fec7e9 Binary files /dev/null and b/libs/arm64-v8a/liberrno.so differ diff --git a/libs/armeabi-v7a/liberrno.so b/libs/armeabi-v7a/liberrno.so new file mode 100755 index 0000000..084844a Binary files /dev/null and b/libs/armeabi-v7a/liberrno.so differ diff --git a/libs/armeabi/liberrno.so b/libs/armeabi/liberrno.so new file mode 100755 index 0000000..008766a Binary files /dev/null and b/libs/armeabi/liberrno.so differ diff --git a/libs/mips/liberrno.so b/libs/mips/liberrno.so new file mode 100755 index 0000000..ad41182 Binary files /dev/null and b/libs/mips/liberrno.so differ diff --git a/libs/mips64/liberrno.so b/libs/mips64/liberrno.so new file mode 100755 index 0000000..b3a06b4 Binary files /dev/null and b/libs/mips64/liberrno.so differ diff --git a/libs/x86/liberrno.so b/libs/x86/liberrno.so new file mode 100755 index 0000000..6b85cd4 Binary files /dev/null and b/libs/x86/liberrno.so differ diff --git a/libs/x86_64/liberrno.so b/libs/x86_64/liberrno.so new file mode 100755 index 0000000..5bd8eef Binary files /dev/null and b/libs/x86_64/liberrno.so differ diff --git a/src/org/cgutman/usbip/errno/Errno.java b/src/org/cgutman/usbip/errno/Errno.java new file mode 100644 index 0000000..b7d699d --- /dev/null +++ b/src/org/cgutman/usbip/errno/Errno.java @@ -0,0 +1,12 @@ +package org.cgutman.usbip.errno; + +public class Errno { + static { + System.loadLibrary("errno"); + } + + // This is a really nasty hack to try to grab the error + // from a USB API request. It may return an undefined result + // if say the GC runs before this gets called. + public static native int getErrno(); +} diff --git a/src/org/cgutman/usbip/server/UsbIpServer.java b/src/org/cgutman/usbip/server/UsbIpServer.java index 671e200..effd823 100644 --- a/src/org/cgutman/usbip/server/UsbIpServer.java +++ b/src/org/cgutman/usbip/server/UsbIpServer.java @@ -1,8 +1,6 @@ package org.cgutman.usbip.server; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.Map; @@ -15,6 +13,7 @@ import org.cgutman.usbip.server.protocol.cli.ImportDeviceRequest; import org.cgutman.usbip.server.protocol.dev.UsbIpDevicePacket; import org.cgutman.usbip.server.protocol.dev.UsbIpSubmitUrb; +import org.cgutman.usbip.server.protocol.dev.UsbIpUnlinkUrb; public class UsbIpServer { public static final int PORT = 3240; @@ -25,14 +24,19 @@ public class UsbIpServer { private ConcurrentHashMap connections = new ConcurrentHashMap(); // Returns true if a device is now attached - private boolean handleRequest(InputStream in, OutputStream out) throws IOException { - CommonPacket inMsg = CommonPacket.read(in); + private boolean handleRequest(Socket s) throws IOException { + CommonPacket inMsg = CommonPacket.read(s.getInputStream()); CommonPacket outMsg; + if (inMsg == null) { + s.close(); + return false; + } + boolean res = false; System.out.printf("In code: 0x%x\n", inMsg.code); if (inMsg.code == ProtoDefs.OP_REQ_DEVLIST) { - DevListReply dlReply = new DevListReply(); + DevListReply dlReply = new DevListReply(inMsg.version); dlReply.devInfoList = handler.getDevices(); if (dlReply.devInfoList == null) { dlReply.status = ProtoDefs.ST_NA; @@ -41,9 +45,9 @@ private boolean handleRequest(InputStream in, OutputStream out) throws IOExcepti } else if (inMsg.code == ProtoDefs.OP_REQ_IMPORT) { ImportDeviceRequest imReq = (ImportDeviceRequest)inMsg; - ImportDeviceReply imReply = new ImportDeviceReply(); + ImportDeviceReply imReply = new ImportDeviceReply(inMsg.version); - res = handler.attachToDevice(imReq.busid); + res = handler.attachToDevice(s, imReq.busid); if (res) { imReply.devInfo = handler.getDeviceByBusId(imReq.busid); if (imReply.devInfo == null) { @@ -64,7 +68,7 @@ else if (inMsg.code == ProtoDefs.OP_REQ_IMPORT) { } System.out.printf("Out code: 0x%x\n", outMsg.code); - out.write(outMsg.serialize()); + s.getOutputStream().write(outMsg.serialize()); return res; } @@ -74,6 +78,9 @@ private boolean handleDevRequest(Socket s) throws IOException { if (inMsg.command == UsbIpDevicePacket.USBIP_CMD_SUBMIT) { handler.submitUrbRequest(s, (UsbIpSubmitUrb) inMsg); } + else if (inMsg.command == UsbIpDevicePacket.USBIP_CMD_UNLINK) { + handler.abortUrbRequest(s, (UsbIpUnlinkUrb) inMsg); + } else { return false; } @@ -100,19 +107,19 @@ private void handleClient(final Socket s) { @Override public void run() { try { - // Disable Nagle s.setTcpNoDelay(true); + s.setKeepAlive(true); - InputStream in = s.getInputStream(); - OutputStream out = s.getOutputStream(); while (!isInterrupted()) { - if (handleRequest(in, out)) { + if (handleRequest(s)) { while (handleDevRequest(s)); } } } catch (IOException e) { System.out.println("Client disconnected"); } finally { + handler.cleanupSocket(s); + try { s.close(); } catch (IOException e) {} diff --git a/src/org/cgutman/usbip/server/UsbRequestHandler.java b/src/org/cgutman/usbip/server/UsbRequestHandler.java index 0fddafb..a714997 100644 --- a/src/org/cgutman/usbip/server/UsbRequestHandler.java +++ b/src/org/cgutman/usbip/server/UsbRequestHandler.java @@ -4,13 +4,17 @@ import java.util.List; import org.cgutman.usbip.server.protocol.dev.UsbIpSubmitUrb; +import org.cgutman.usbip.server.protocol.dev.UsbIpUnlinkUrb; public interface UsbRequestHandler { public List getDevices(); public UsbDeviceInfo getDeviceByBusId(String busId); - public boolean attachToDevice(String busId); - public void detachFromDevice(String busId); + public boolean attachToDevice(Socket s, String busId); + public void detachFromDevice(Socket s, String busId); public void submitUrbRequest(Socket s, UsbIpSubmitUrb msg); + public void abortUrbRequest(Socket s, UsbIpUnlinkUrb msg); + + public void cleanupSocket(Socket s); } diff --git a/src/org/cgutman/usbip/server/protocol/ProtoDefs.java b/src/org/cgutman/usbip/server/protocol/ProtoDefs.java index f44d19f..2c85761 100644 --- a/src/org/cgutman/usbip/server/protocol/ProtoDefs.java +++ b/src/org/cgutman/usbip/server/protocol/ProtoDefs.java @@ -1,7 +1,7 @@ package org.cgutman.usbip.server.protocol; public class ProtoDefs { - public static final short USBIP_VERSION = 0x0106; + /* public static final short USBIP_VERSION = 0x0111; */ public static final short OP_REQUEST = (short) (0x80 << 8); public static final short OP_REPLY = (0x00 << 8); diff --git a/src/org/cgutman/usbip/server/protocol/cli/CommonPacket.java b/src/org/cgutman/usbip/server/protocol/cli/CommonPacket.java index b5b791f..17e21f5 100644 --- a/src/org/cgutman/usbip/server/protocol/cli/CommonPacket.java +++ b/src/org/cgutman/usbip/server/protocol/cli/CommonPacket.java @@ -29,11 +29,11 @@ public CommonPacket(short version, short code, int status) { public static CommonPacket read(InputStream in) throws IOException { ByteBuffer bb = ByteBuffer.allocate(8); StreamUtils.readAll(in, bb.array()); - - short version = bb.getShort(); - if (version != ProtoDefs.USBIP_VERSION) { - throw new IOException("Unsupported protocol version: "+version); - } + + // We should check the version here, but it seems they like to + // increment it without actually changing the protocol, so I'm + // not going to. + bb.getShort(); CommonPacket pkt; short code = bb.getShort(); diff --git a/src/org/cgutman/usbip/server/protocol/cli/DevListReply.java b/src/org/cgutman/usbip/server/protocol/cli/DevListReply.java index 2522b0a..af25e48 100644 --- a/src/org/cgutman/usbip/server/protocol/cli/DevListReply.java +++ b/src/org/cgutman/usbip/server/protocol/cli/DevListReply.java @@ -13,8 +13,8 @@ public DevListReply(byte[] header) { super(header); } - public DevListReply() { - super(ProtoDefs.USBIP_VERSION, ProtoDefs.OP_REP_DEVLIST, ProtoDefs.ST_OK); + public DevListReply(short version) { + super(version, ProtoDefs.OP_REP_DEVLIST, ProtoDefs.ST_OK); } @Override diff --git a/src/org/cgutman/usbip/server/protocol/cli/ImportDeviceReply.java b/src/org/cgutman/usbip/server/protocol/cli/ImportDeviceReply.java index a7baaa9..a5e014f 100644 --- a/src/org/cgutman/usbip/server/protocol/cli/ImportDeviceReply.java +++ b/src/org/cgutman/usbip/server/protocol/cli/ImportDeviceReply.java @@ -10,8 +10,8 @@ public ImportDeviceReply(byte[] header) { super(header); } - public ImportDeviceReply() { - super(ProtoDefs.USBIP_VERSION, ProtoDefs.OP_REP_IMPORT, ProtoDefs.ST_OK); + public ImportDeviceReply(short version) { + super(version, ProtoDefs.OP_REP_IMPORT, ProtoDefs.ST_OK); } @Override diff --git a/src/org/cgutman/usbip/server/protocol/dev/UsbIpDevicePacket.java b/src/org/cgutman/usbip/server/protocol/dev/UsbIpDevicePacket.java index a1d24a1..9a2f71a 100644 --- a/src/org/cgutman/usbip/server/protocol/dev/UsbIpDevicePacket.java +++ b/src/org/cgutman/usbip/server/protocol/dev/UsbIpDevicePacket.java @@ -18,11 +18,11 @@ public abstract class UsbIpDevicePacket { public static final int USBIP_DIR_IN = 1; public static final int USBIP_STATUS_ENDPOINT_HALTED = -32; + public static final int USBIP_STATUS_URB_ABORTED = -54; public static final int USBIP_STATUS_DATA_OVERRUN = -75; + public static final int USBIP_STATUS_URB_TIMED_OUT = -110; public static final int USBIP_STATUS_SHORT_TRANSFER = -121; - - public static final int USBIP_SHORT_XFER_OKAY = 0x01; - + public static final int USBIP_HEADER_SIZE = 48; public int command; @@ -58,6 +58,8 @@ public static UsbIpDevicePacket read(InputStream in) throws IOException { { case USBIP_CMD_SUBMIT: return UsbIpSubmitUrb.read(bb.array(), in); + case USBIP_CMD_UNLINK: + return UsbIpUnlinkUrb.read(bb.array(), in); default: System.err.println("Unknown command: "+command); return null; diff --git a/src/org/cgutman/usbip/server/protocol/dev/UsbIpUnlinkUrb.java b/src/org/cgutman/usbip/server/protocol/dev/UsbIpUnlinkUrb.java new file mode 100644 index 0000000..cfa61ef --- /dev/null +++ b/src/org/cgutman/usbip/server/protocol/dev/UsbIpUnlinkUrb.java @@ -0,0 +1,48 @@ +package org.cgutman.usbip.server.protocol.dev; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.cgutman.usbip.utils.StreamUtils; + +public class UsbIpUnlinkUrb extends UsbIpDevicePacket { + public int seqNumToUnlink; + + public static final int WIRE_SIZE = 4; + + public UsbIpUnlinkUrb(byte[] header) { + super(header); + } + + public static UsbIpUnlinkUrb read(byte[] header, InputStream in) throws IOException { + UsbIpUnlinkUrb msg = new UsbIpUnlinkUrb(header); + + byte[] continuationHeader = new byte[WIRE_SIZE]; + StreamUtils.readAll(in, continuationHeader); + + ByteBuffer bb = ByteBuffer.wrap(continuationHeader).order(ByteOrder.BIG_ENDIAN); + msg.seqNumToUnlink = bb.getInt(); + + // Finish reading the remaining bytes of the header as padding + for (int i = 0; i < UsbIpDevicePacket.USBIP_HEADER_SIZE - (header.length + bb.position()); i++) { + in.read(); + } + + return msg; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(super.toString()); + sb.append(String.format("Sequence number to unlink: %d\n", seqNumToUnlink)); + return sb.toString(); + } + + @Override + protected byte[] serializeInternal() { + throw new UnsupportedOperationException("Serializing not supported"); + } +} diff --git a/src/org/cgutman/usbip/server/protocol/dev/UsbIpUnlinkUrbReply.java b/src/org/cgutman/usbip/server/protocol/dev/UsbIpUnlinkUrbReply.java new file mode 100644 index 0000000..adc3bd7 --- /dev/null +++ b/src/org/cgutman/usbip/server/protocol/dev/UsbIpUnlinkUrbReply.java @@ -0,0 +1,28 @@ +package org.cgutman.usbip.server.protocol.dev; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class UsbIpUnlinkUrbReply extends UsbIpDevicePacket { + public int status; + + public UsbIpUnlinkUrbReply(int seqNum, int devId, int dir, int ep) { + super(UsbIpDevicePacket.USBIP_RET_UNLINK, seqNum, devId, dir, ep); + } + + protected byte[] serializeInternal() { + ByteBuffer bb = ByteBuffer.allocate(UsbIpDevicePacket.USBIP_HEADER_SIZE - 20).order(ByteOrder.BIG_ENDIAN); + + bb.putInt(status); + + return bb.array(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(super.toString()); + sb.append(String.format("Status: 0x%x\n", status)); + return sb.toString(); + } +} diff --git a/src/org/cgutman/usbip/service/UsbIpService.java b/src/org/cgutman/usbip/service/UsbIpService.java index 5b5ac02..35c8085 100644 --- a/src/org/cgutman/usbip/service/UsbIpService.java +++ b/src/org/cgutman/usbip/service/UsbIpService.java @@ -5,6 +5,8 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; @@ -20,7 +22,9 @@ import org.cgutman.usbip.server.protocol.dev.UsbIpDevicePacket; import org.cgutman.usbip.server.protocol.dev.UsbIpSubmitUrb; import org.cgutman.usbip.server.protocol.dev.UsbIpSubmitUrbReply; -import org.cgutman.usbip.usb.DescriptorReader; +import org.cgutman.usbip.server.protocol.dev.UsbIpUnlinkUrb; +import org.cgutman.usbip.server.protocol.dev.UsbIpUnlinkUrbReply; +import org.cgutman.usbip.usb.UsbControlHelper; import org.cgutman.usbip.usb.UsbDeviceDescriptor; import org.cgutman.usbip.usb.XferUtils; import org.cgutman.usbipserverforandroid.R; @@ -39,10 +43,8 @@ import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; -import android.hardware.usb.UsbRequest; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.WifiLock; -import android.os.Build; import android.os.IBinder; import android.os.PowerManager; import android.os.PowerManager.WakeLock; @@ -54,6 +56,7 @@ public class UsbIpService extends Service implements UsbRequestHandler { private SparseArray connections; private SparseArray permission; + private HashMap socketMap; private UsbIpServer server; private WakeLock cpuWakeLock; private WifiLock wifiLock; @@ -112,6 +115,7 @@ public void onCreate() { usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); connections = new SparseArray(); permission = new SparseArray(); + socketMap = new HashMap(); usbPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); @@ -237,15 +241,48 @@ else if ((possibleSpeeds & FLAG_POSSIBLE_SPEED_HIGH) != 0) { return UsbIpDevice.USB_SPEED_UNKNOWN; } } + + private static int deviceIdToBusNum(int deviceId) { + return deviceId / 1000; + } + + private static int deviceIdToDevNum(int deviceId) { + return deviceId % 1000; + } + + private static int devIdToDeviceId(int devId) { + // This is the same algorithm as Android uses + return ((devId >> 16) & 0xFF) * 1000 + (devId & 0xFF); + } + + private static int busIdToBusNum(String busId) { + if (busId.indexOf('-') == -1) { + return -1; + } + + return Integer.parseInt(busId.substring(0, busId.indexOf('-'))); + } + + private static int busIdToDevNum(String busId) { + if (busId.indexOf('-') == -1) { + return -1; + } + + return Integer.parseInt(busId.substring(busId.indexOf('-')+1)); + } + + private static int busIdToDeviceId(String busId) { + return devIdToDeviceId(((busIdToBusNum(busId) << 16) & 0xFF0000) | busIdToDevNum(busId)); + } - private UsbDeviceInfo getInfoForDevice(UsbDevice dev) { + private UsbDeviceInfo getInfoForDevice(UsbDevice dev, UsbDeviceConnection devConn) { UsbDeviceInfo info = new UsbDeviceInfo(); UsbIpDevice ipDev = new UsbIpDevice(); ipDev.path = dev.getDeviceName(); - ipDev.busid = String.format("%d", dev.getDeviceId()); - ipDev.busnum = 0; - ipDev.devnum = dev.getDeviceId(); + ipDev.busnum = deviceIdToBusNum(dev.getDeviceId()); + ipDev.devnum = deviceIdToDevNum(dev.getDeviceId()); + ipDev.busid = String.format("%d-%d", ipDev.busnum, ipDev.devnum); ipDev.idVendor = (short) dev.getVendorId(); ipDev.idProduct = (short) dev.getProductId(); @@ -277,7 +314,7 @@ private UsbDeviceInfo getInfoForDevice(UsbDevice dev) { if (context != null) { // Since we're attached already, we can directly query the USB descriptors // to fill some information that Android's USB API doesn't expose - devDesc = DescriptorReader.readDeviceDescriptor(context.devConn); + devDesc = UsbControlHelper.readDeviceDescriptor(context.devConn); ipDev.bcdDevice = devDesc.bcdDevice; ipDev.bNumConfigurations = devDesc.bNumConfigurations; @@ -293,7 +330,13 @@ public List getDevices() { ArrayList list = new ArrayList(); for (UsbDevice dev : usbManager.getDeviceList().values()) { - list.add(getInfoForDevice(dev)); + AttachedDeviceContext context = connections.get(dev.getDeviceId()); + UsbDeviceConnection devConn = null; + if (context != null) { + devConn = context.devConn; + } + + list.add(getInfoForDevice(dev, devConn)); } return list; @@ -329,6 +372,18 @@ private static void sendReply(Socket s, UsbIpSubmitUrbReply reply, int status) { } } + private static void sendReply(Socket s, UsbIpUnlinkUrbReply reply, int status) { + reply.status = status; + try { + // We need to synchronize to avoid writing on top of ourselves + synchronized (s) { + s.getOutputStream().write(reply.serialize()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + // FIXME: This dispatching could use some refactoring so we don't have to pass // a million parameters to this guy private void dispatchRequest(final AttachedDeviceContext context, final Socket s, @@ -351,94 +406,79 @@ public void run() { selectedEndpoint.getEndpointNumber()); } - int res = XferUtils.doBulkTransfer(context.devConn,selectedEndpoint, buff.array(), msg.interval); + int res; + do { + res = XferUtils.doBulkTransfer(context.devConn, selectedEndpoint, buff.array(), 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 if (DEBUG) { System.out.printf("Bulk transfer complete with %d bytes (wanted %d)\n", res, msg.transferBufferLength); } - + if (res < 0) { - reply.status = ProtoDefs.ST_NA; + reply.status = res; } else { reply.actualLength = res; reply.status = ProtoDefs.ST_OK; } + + context.activeMessages.remove(msg); sendReply(s, reply, reply.status); } else if (selectedEndpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_INT) { - if (DEBUG) { System.out.printf("Interrupt transfer - %d bytes %s on EP %d\n", msg.transferBufferLength, msg.direction == UsbIpDevicePacket.USBIP_DIR_IN ? "in" : "out", selectedEndpoint.getEndpointNumber()); } - - UsbRequest req = new UsbRequest(); - req.initialize(context.devConn, selectedEndpoint); - - // Create a context for this request so we can identify it - // when we're done - UrbContext urbCtxt = new UrbContext(); - urbCtxt.originalMsg = msg; - urbCtxt.replyMsg = reply; - urbCtxt.buffer = buff; - req.setClientData(urbCtxt); - if (!req.queue(buff, msg.transferBufferLength)) { - System.err.println("Failed to queue request"); - sendReply(s, reply, ProtoDefs.ST_NA); - req.close(); - return; - } - // ---------------- msg is not safe to use below this point ----------- - - // This can return a different request than what we queued, - // so we have to lookup the correct reply for this guy - req = context.devConn.requestWait(); - urbCtxt = (UrbContext) req.getClientData(); - reply = urbCtxt.replyMsg; - UsbIpSubmitUrb originalMsg = urbCtxt.originalMsg; - - - // On Jelly Bean MR1 (4.2), they exposed the actual transfer length - // as the byte buffer's position. On previous platforms, we just assume - // the whole thing went through. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - // The byte buffer could have changed so we need to get it from - // the client data - reply.actualLength = urbCtxt.buffer.position(); - } - else { - reply.actualLength = originalMsg.transferBufferLength; - } - - req.close(); + int res; + do { + res = XferUtils.doInterruptTransfer(context.devConn, selectedEndpoint, buff.array(), 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 if (DEBUG) { System.out.printf("Interrupt transfer complete with %d bytes (wanted %d)\n", - reply.actualLength, originalMsg.transferBufferLength); + res, msg.transferBufferLength); } - if (reply.actualLength == 0) { - // Request actually failed - reply.status = ProtoDefs.ST_NA; + + if (res < 0) { + reply.status = res; } else { - // LGTM + reply.actualLength = res; reply.status = ProtoDefs.ST_OK; } - // Docs are conflicting on whether we need to fill this for non-iso - // transfers. Nothing in the client driver code seems to use it - // so I'm not going to... - reply.startFrame = 0; - + context.activeMessages.remove(msg); sendReply(s, reply, reply.status); } else { System.err.println("Unsupported endpoint type: "+selectedEndpoint.getType()); - sendReply(s, reply, ProtoDefs.ST_NA); + context.activeMessages.remove(msg); + server.killClient(s); } } }); @@ -449,18 +489,18 @@ public void submitUrbRequest(Socket s, UsbIpSubmitUrb msg) { UsbIpSubmitUrbReply reply = new UsbIpSubmitUrbReply(msg.seqNum, msg.devId, msg.direction, msg.ep); - UsbDevice dev = getDevice(msg.devId); + int deviceId = devIdToDeviceId(msg.devId); + + UsbDevice dev = getDevice(deviceId); if (dev == null) { // The device is gone, so terminate the client - cleanupDetachedDevice(msg.devId); server.killClient(s); return; } - AttachedDeviceContext context = connections.get(msg.devId); + AttachedDeviceContext context = connections.get(deviceId); if (context == null) { // This should never happen, but kill the connection if it does - cleanupDetachedDevice(msg.devId); server.killClient(s); return; } @@ -481,17 +521,36 @@ public void submitUrbRequest(Socket s, UsbIpSubmitUrb msg) { if (length != 0) { reply.inData = new byte[length]; } + + // This message is now active + context.activeMessages.add(msg); - int res = XferUtils.doControlTransfer(devConn, requestType, request, value, index, - (requestType & 0x80) != 0 ? reply.inData : msg.outData, length, msg.interval); + 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 + if (res < 0) { - reply.status = ProtoDefs.ST_NA; + reply.status = res; } else { reply.actualLength = res; reply.status = ProtoDefs.ST_OK; } + context.activeMessages.remove(msg); sendReply(s, reply, reply.status); return; } @@ -545,14 +604,17 @@ public void submitUrbRequest(Socket s, UsbIpSubmitUrb msg) { buff = ByteBuffer.wrap(msg.outData); } + // This message is now active + context.activeMessages.add(msg); + // Dispatch this request asynchronously dispatchRequest(context, s, selectedEndpoint, buff, msg); } } - private UsbDevice getDevice(int busId) { + private UsbDevice getDevice(int deviceId) { for (UsbDevice dev : usbManager.getDeviceList().values()) { - if (dev.getDeviceId() == busId) { + if (dev.getDeviceId() == deviceId) { return dev; } } @@ -561,15 +623,7 @@ private UsbDevice getDevice(int busId) { } private UsbDevice getDevice(String busId) { - int id; - - try { - id = Integer.parseInt(busId); - } catch (NumberFormatException e) { - return null; - } - - return getDevice(id); + return getDevice(busIdToDeviceId(busId)); } @Override @@ -579,16 +633,27 @@ public UsbDeviceInfo getDeviceByBusId(String busId) { return null; } - return getInfoForDevice(dev); + AttachedDeviceContext context = connections.get(dev.getDeviceId()); + UsbDeviceConnection devConn = null; + if (context != null) { + devConn = context.devConn; + } + + return getInfoForDevice(dev, devConn); } @Override - public boolean attachToDevice(String busId) { + public boolean attachToDevice(Socket s, String busId) { UsbDevice dev = getDevice(busId); if (dev == null) { return false; } + if (connections.get(dev.getDeviceId()) != null) { + // Already attached + return false; + } + if (!usbManager.hasPermission(dev)) { // Try to get permission from the user permission.put(dev.getDeviceId(), null); @@ -617,7 +682,7 @@ public boolean attachToDevice(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)) { - return false; + System.err.println("Unabled to claim interface "+dev.getInterface(i).getId()); } } @@ -626,7 +691,7 @@ public boolean attachToDevice(String busId) { context.devConn = devConn; context.device = dev; - // Count all endpoints on all intefaces + // Count all endpoints on all interfaces int endpointCount = 0; for (int i = 0; i < dev.getInterfaceCount(); i++) { endpointCount += dev.getInterface(i).getEndpointCount(); @@ -637,7 +702,11 @@ public boolean attachToDevice(String busId) { Long.MAX_VALUE, TimeUnit.DAYS, new LinkedBlockingQueue(), new ThreadPoolExecutor.DiscardPolicy()); + // Create the active message set + context.activeMessages = new HashSet(); + connections.put(dev.getDeviceId(), context); + socketMap.put(s, context); updateNotification(); return true; @@ -652,6 +721,9 @@ private void cleanupDetachedDevice(int deviceId) { // Clear the this attachment's context connections.remove(deviceId); + // Signal queue death + context.requestPool.shutdownNow(); + // Release our claim to the interfaces for (int i = 0; i < context.device.getInterfaceCount(); i++) { context.devConn.releaseInterface(context.device.getInterface(i)); @@ -659,12 +731,17 @@ private void cleanupDetachedDevice(int deviceId) { // Close the connection context.devConn.close(); + + // Wait for the queue to die + try { + context.requestPool.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); + } catch (InterruptedException e) {} updateNotification(); } @Override - public void detachFromDevice(String busId) { + public void detachFromDevice(Socket s, String busId) { UsbDevice dev = getDevice(busId); if (dev == null) { return; @@ -672,16 +749,47 @@ public void detachFromDevice(String busId) { cleanupDetachedDevice(dev.getDeviceId()); } - - class UrbContext { - public UsbIpSubmitUrb originalMsg; - public UsbIpSubmitUrbReply replyMsg; - public ByteBuffer buffer; + + @Override + public void cleanupSocket(Socket s) { + AttachedDeviceContext context = socketMap.remove(s); + if (context == null) { + return; + } + + cleanupDetachedDevice(context.device.getDeviceId()); + } + + @Override + public void abortUrbRequest(Socket s, UsbIpUnlinkUrb msg) { + AttachedDeviceContext context = socketMap.get(s); + if (context == null) { + return; + } + + UsbIpUnlinkUrbReply reply = new UsbIpUnlinkUrbReply(msg.seqNum, msg.devId, msg.direction, msg.ep); + + boolean found = false; + synchronized (context.activeMessages) { + for (UsbIpSubmitUrb urbMsg : context.activeMessages) { + if (msg.seqNumToUnlink == urbMsg.seqNum) { + context.activeMessages.remove(urbMsg); + found = true; + break; + } + } + } + + System.out.println("Removed URB? " + (found ? "yes" : "no")); + sendReply(s, reply, + found ? UsbIpSubmitUrb.USBIP_STATUS_URB_ABORTED : + -22); // EINVAL } class AttachedDeviceContext { public UsbDevice device; public UsbDeviceConnection devConn; public ThreadPoolExecutor requestPool; + public HashSet activeMessages; } } diff --git a/src/org/cgutman/usbip/usb/DescriptorReader.java b/src/org/cgutman/usbip/usb/DescriptorReader.java deleted file mode 100644 index e33b52a..0000000 --- a/src/org/cgutman/usbip/usb/DescriptorReader.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.cgutman.usbip.usb; - -import android.hardware.usb.UsbDeviceConnection; - -public class DescriptorReader { - - private static final int GET_DESCRIPTOR_REQUEST_TYPE = 0x80; - private static final int GET_DESCRIPTOR_REQUEST = 0x06; - - private static final int DEVICE_DESCRIPTOR_TYPE = 1; - - public static UsbDeviceDescriptor readDeviceDescriptor(UsbDeviceConnection devConn) { - byte[] descriptorBuffer = new byte[UsbDeviceDescriptor.DESCRIPTOR_SIZE]; - - - int res = XferUtils.doControlTransfer(devConn, GET_DESCRIPTOR_REQUEST_TYPE, - GET_DESCRIPTOR_REQUEST, - (DEVICE_DESCRIPTOR_TYPE << 8) | 0x00, // Devices only have 1 descriptor - 0, descriptorBuffer, descriptorBuffer.length, 0); - if (res != UsbDeviceDescriptor.DESCRIPTOR_SIZE) { - return null; - } - - return new UsbDeviceDescriptor(descriptorBuffer); - } -} diff --git a/src/org/cgutman/usbip/usb/UsbControlHelper.java b/src/org/cgutman/usbip/usb/UsbControlHelper.java new file mode 100644 index 0000000..3bcc28a --- /dev/null +++ b/src/org/cgutman/usbip/usb/UsbControlHelper.java @@ -0,0 +1,63 @@ +package org.cgutman.usbip.usb; + +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; + +public class UsbControlHelper { + + private static final int GET_DESCRIPTOR_REQUEST_TYPE = 0x80; + private static final int GET_DESCRIPTOR_REQUEST = 0x06; + + private static final int GET_STATUS_REQUEST_TYPE = 0x82; + private static final int GET_STATUS_REQUEST = 0x00; + + private static final int CLEAR_FEATURE_REQUEST_TYPE = 0x02; + private static final int CLEAR_FEATURE_REQUEST = 0x01; + + private static final int FEATURE_VALUE_HALT = 0x00; + + private static final int DEVICE_DESCRIPTOR_TYPE = 1; + + public static UsbDeviceDescriptor readDeviceDescriptor(UsbDeviceConnection devConn) { + byte[] descriptorBuffer = new byte[UsbDeviceDescriptor.DESCRIPTOR_SIZE]; + + + int res = XferUtils.doControlTransfer(devConn, GET_DESCRIPTOR_REQUEST_TYPE, + GET_DESCRIPTOR_REQUEST, + (DEVICE_DESCRIPTOR_TYPE << 8) | 0x00, // Devices only have 1 descriptor + 0, descriptorBuffer, descriptorBuffer.length, 0); + if (res != UsbDeviceDescriptor.DESCRIPTOR_SIZE) { + return null; + } + + return new UsbDeviceDescriptor(descriptorBuffer); + } + + public static boolean isEndpointHalted(UsbDeviceConnection devConn, UsbEndpoint endpoint) { + byte[] statusBuffer = new byte[2]; + + int res = XferUtils.doControlTransfer(devConn, GET_STATUS_REQUEST_TYPE, + GET_STATUS_REQUEST, + 0, + endpoint != null ? endpoint.getAddress() : 0, + statusBuffer, statusBuffer.length, 0); + if (res != statusBuffer.length) { + return false; + } + + return (statusBuffer[0] & 1) != 0; + } + + public static boolean clearHaltCondition(UsbDeviceConnection devConn, UsbEndpoint endpoint) { + int res = XferUtils.doControlTransfer(devConn, CLEAR_FEATURE_REQUEST_TYPE, + CLEAR_FEATURE_REQUEST, + FEATURE_VALUE_HALT, + endpoint.getAddress(), + null, 0, 0); + if (res < 0) { + return false; + } + + return true; + } +} diff --git a/src/org/cgutman/usbip/usb/XferUtils.java b/src/org/cgutman/usbip/usb/XferUtils.java index f9323d4..8e88b4e 100644 --- a/src/org/cgutman/usbip/usb/XferUtils.java +++ b/src/org/cgutman/usbip/usb/XferUtils.java @@ -1,12 +1,29 @@ package org.cgutman.usbip.usb; +import org.cgutman.usbip.errno.Errno; + import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; public class XferUtils { - public static int doBulkTransfer(UsbDeviceConnection devConn, UsbEndpoint endpoint, byte[] buff, int interval) { + public static int doInterruptTransfer(UsbDeviceConnection devConn, UsbEndpoint endpoint, byte[] buff, int timeout) { + // Interrupt transfers are implemented as one-shot bulk transfers + int res = devConn.bulkTransfer(endpoint, buff, + buff.length, timeout); + if (res < 0) { + res = -Errno.getErrno(); + if (res != -110) { + // Don't print for ETIMEDOUT + System.err.println("Interrupt Xfer failed: "+res); + } + } + + return res; + } + + public static int doBulkTransfer(UsbDeviceConnection devConn, UsbEndpoint endpoint, byte[] buff, int timeout) { int bytesTransferred = 0; while (bytesTransferred < buff.length) { byte[] remainingBuffer = new byte[buff.length - bytesTransferred]; @@ -17,11 +34,15 @@ public static int doBulkTransfer(UsbDeviceConnection devConn, UsbEndpoint endpoi } int res = devConn.bulkTransfer(endpoint, remainingBuffer, - remainingBuffer.length, interval); + remainingBuffer.length, timeout); if (res < 0) { // Failed transfer terminates the bulk transfer - System.err.println("Bulk Xfer failed: "+res); - return -1; + res = -Errno.getErrno(); + if (res != -110) { + // Don't print for ETIMEDOUT + System.err.println("Bulk Xfer failed: "+res); + } + return res; } if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { @@ -43,13 +64,25 @@ public static int doBulkTransfer(UsbDeviceConnection devConn, UsbEndpoint endpoi public static int doControlTransfer(UsbDeviceConnection devConn, int requestType, int request, int value, int index, byte[] buff, int length, int interval) { + + // Mask out possible sign expansions + requestType &= 0xFF; + request &= 0xFF; + value &= 0xFFFF; + index &= 0xFFFF; + length &= 0xFFFF; + System.out.printf("SETUP: %x %x %x %x %x\n", requestType, request, value, index, length); int res = devConn.controlTransfer(requestType, request, value, index, buff, length, interval); if (res < 0) { - System.err.println("Control Xfer failed: "+res); + res = -Errno.getErrno(); + if (res != -110) { + // Don't print for ETIMEDOUT + System.err.println("Control Xfer failed: "+res); + } } return res;