diff --git a/java/src/ch/ntb/usb/Device.java b/java/src/ch/ntb/usb/Device.java index 96436db..ae9d7a6 100644 --- a/java/src/ch/ntb/usb/Device.java +++ b/java/src/ch/ntb/usb/Device.java @@ -258,8 +258,8 @@ public class Device { * endpoint address to write to * @param data * data to write to this endpoint - * @param length - * length of the data + * @param size + * size of the data * @param timeout * amount of time in ms the device will try to send the data * until a timeout exception is thrown @@ -269,7 +269,7 @@ public class Device { * @return the actual number of bytes written * @throws USBException */ - public int writeBulk(int out_ep_address, byte[] data, int length, + public int writeBulk(int out_ep_address, byte[] data, int size, int timeout, boolean reopenOnTimeout) throws USBException { if (usbDevHandle <= 0) { throw new USBException("invalid device handle"); @@ -277,11 +277,11 @@ public class Device { if (data == null) { throw new USBException("data must not be null"); } - if (length <= 0) { - throw new USBException("size must be > 0"); + if (size <= 0 || size > data.length) { + throw new ArrayIndexOutOfBoundsException("invalid size: " + size); } - int lenWritten = LibusbJava.usb_bulk_write(usbDevHandle, out_ep_address, - data, length, timeout); + int lenWritten = LibusbJava.usb_bulk_write(usbDevHandle, + out_ep_address, data, size, timeout); if (lenWritten < 0) { if (lenWritten == TIMEOUT_ERROR_CODE) { // try to reopen the device and send the data again @@ -289,8 +289,7 @@ public class Device { logger.info("try to reopen"); reset(); open(dev_configuration, dev_interface, dev_altinterface); - return writeBulk(out_ep_address, data, length, timeout, - false); + return writeBulk(out_ep_address, data, size, timeout, false); } throw new USBTimeoutException("LibusbWin.usb_bulk_write: " + LibusbJava.usb_strerror()); @@ -338,8 +337,8 @@ public class Device { if (data == null) { throw new USBException("data must not be null"); } - if (size <= 0) { - throw new USBException("size must be > 0"); + if (size <= 0 || size > data.length) { + throw new ArrayIndexOutOfBoundsException("invalid size: " + size); } int lenRead = LibusbJava.usb_bulk_read(usbDevHandle, in_ep_address, data, size, timeout); @@ -379,8 +378,8 @@ public class Device { * endpoint address to write to * @param data * data to write to this endpoint - * @param length - * length of the data + * @param size + * size of the data * @param timeout * amount of time in ms the device will try to send the data * until a timeout exception is thrown @@ -390,7 +389,7 @@ public class Device { * @return the actual number of bytes written * @throws USBException */ - public int writeInterrupt(int out_ep_address, byte[] data, int length, + public int writeInterrupt(int out_ep_address, byte[] data, int size, int timeout, boolean reopenOnTimeout) throws USBException { if (usbDevHandle <= 0) { throw new USBException("invalid device handle"); @@ -398,11 +397,11 @@ public class Device { if (data == null) { throw new USBException("data must not be null"); } - if (length <= 0) { - throw new USBException("size must be > 0"); + if (size <= 0 || size > data.length) { + throw new ArrayIndexOutOfBoundsException("invalid size: " + size); } int lenWritten = LibusbJava.usb_interrupt_write(usbDevHandle, - out_ep_address, data, length, timeout); + out_ep_address, data, size, timeout); if (lenWritten < 0) { if (lenWritten == TIMEOUT_ERROR_CODE) { // try to reopen the device and send the data again @@ -410,8 +409,8 @@ public class Device { logger.info("try to reopen"); reset(); open(dev_configuration, dev_interface, dev_altinterface); - return writeInterrupt(out_ep_address, data, length, - timeout, false); + return writeInterrupt(out_ep_address, data, size, timeout, + false); } throw new USBTimeoutException("LibusbWin.usb_bulk_write: " + LibusbJava.usb_strerror()); @@ -459,11 +458,11 @@ public class Device { if (data == null) { throw new USBException("data must not be null"); } - if (size <= 0) { - throw new USBException("size must be > 0"); + if (size <= 0 || size > data.length) { + throw new ArrayIndexOutOfBoundsException("invalid size: " + size); } - int lenRead = LibusbJava.usb_interrupt_read(usbDevHandle, in_ep_address, - data, size, timeout); + int lenRead = LibusbJava.usb_interrupt_read(usbDevHandle, + in_ep_address, data, size, timeout); if (lenRead < 0) { if (lenRead == TIMEOUT_ERROR_CODE) { // try to reopen the device and send the data again @@ -494,6 +493,81 @@ public class Device { return lenRead; } + /** + * Performs a control request to the default control pipe on a device.
+ * The parameters mirror the types of the same name in the USB + * specification. + * + * @param requestType + * USB device request type (USB specification 9.3, + * bmRequestType). Use constants from {@link ch.ntb.usb.USB} + * (REQ_TYPE_xxx). + * @param request + * specific request (USB specification 9.4, bRequest). Use + * constants from {@link ch.ntb.usb.USB} (REQ_xxx). + * @param value + * field that varies according to request (USB specification 9.4, + * wValue) + * @param index + * field that varies according to request (USB specification 9.4, + * wIndex) + * @param data + * the send/receive buffer + * @param size + * the buffer size + * @param timeout + * amount of time in ms the device will try to send/receive data + * until a timeout exception is thrown + * @param reopenOnTimeout + * if set to true, the device will try to open the connection and + * send/receive the data again before a timeout exception is + * thrown + * @return the number of bytes written/read + * @throws USBException + */ + public int controlMsg(int requestType, int request, int value, int index, + byte[] data, int size, int timeout, boolean reopenOnTimeout) + throws USBException { + if (usbDevHandle <= 0) { + throw new USBException("invalid device handle"); + } + if (data == null) { + throw new USBException("data must not be null"); + } + if (size <= 0 || size > data.length) { + throw new ArrayIndexOutOfBoundsException("invalid size: " + size); + } + int len = LibusbJava.usb_control_msg(usbDevHandle, requestType, + request, value, index, data, size, timeout); + if (len < 0) { + if (len == TIMEOUT_ERROR_CODE) { + // try to reopen the device and send the data again + if (reopenOnTimeout) { + logger.info("try to reopen"); + reset(); + open(dev_configuration, dev_interface, dev_altinterface); + return controlMsg(requestType, request, value, index, data, + size, timeout, false); + } + throw new USBTimeoutException("LibusbWin.controlMsg: " + + LibusbJava.usb_strerror()); + } + throw new USBException("LibusbWin.controlMsg: " + + LibusbJava.usb_strerror()); + } + + logger.info("length read/written: " + len); + if (logger.isLoggable(Level.FINEST)) { + StringBuffer sb = new StringBuffer("controlMsg: " + len + + " Bytes received(written: "); + for (int i = 0; i < len; i++) { + sb.append("0x" + String.format("%1$02X", data[i]) + " "); + } + logger.info(sb.toString()); + } + return len; + } + /** * Claim an interface to send and receive USB data.
* diff --git a/java/src/ch/ntb/usb/USB.java b/java/src/ch/ntb/usb/USB.java index b8ecd8d..6305af6 100644 --- a/java/src/ch/ntb/usb/USB.java +++ b/java/src/ch/ntb/usb/USB.java @@ -20,14 +20,146 @@ import ch.ntb.usb.logger.LogUtil; */ public class USB { + // Standard requests (USB spec 9.4) /** - * The maximum packet size of a bulk transfer when operation in highspeed + * This request returns status for the specified recipient (USB spec 9.4.5). + * + * @see ch.ntb.usb.Device#controlMsg(int, int, int, int, byte[], int, int, + * boolean) + */ + public static final int REQ_GET_STATUS = 0x00; + /** + * This request is used to clear or disable a specific feature (USB spec + * 9.4.1). + * + * @see ch.ntb.usb.Device#controlMsg(int, int, int, int, byte[], int, int, + * boolean) + */ + public static final int REQ_CLEAR_FEATURE = 0x01; + // 0x02 is reserved + /** + * This request is used to set or enable a specific feature (USB spec + * 9.4.9). + * + * @see ch.ntb.usb.Device#controlMsg(int, int, int, int, byte[], int, int, + * boolean) + */ + public static final int REQ_SET_FEATURE = 0x03; + // 0x04 is reserved + /** + * This request sets the device address for all future device accesses (USB + * spec 9.4.6). + * + * @see ch.ntb.usb.Device#controlMsg(int, int, int, int, byte[], int, int, + * boolean) + */ + public static final int REQ_SET_ADDRESS = 0x05; + /** + * This request returns the specified descriptor if the descriptor exists + * (USB spec 9.4.3). + * + * @see ch.ntb.usb.Device#controlMsg(int, int, int, int, byte[], int, int, + * boolean) + */ + public static final int REQ_GET_DESCRIPTOR = 0x06; + /** + * This request is optional and may be used to update existing descriptors + * or new descriptors may be added (USB spec 9.4.8). + * + * @see ch.ntb.usb.Device#controlMsg(int, int, int, int, byte[], int, int, + * boolean) + */ + public static final int REQ_SET_DESCRIPTOR = 0x07; + /** + * This request returns the current device configuration value (USB spec + * 9.4.2). + * + * @see ch.ntb.usb.Device#controlMsg(int, int, int, int, byte[], int, int, + * boolean) + */ + public static final int REQ_GET_CONFIGURATION = 0x08; + /** + * This request sets the device configuration (USB spec 9.4.7). + * + * @see ch.ntb.usb.Device#controlMsg(int, int, int, int, byte[], int, int, + * boolean) + */ + public static final int REQ_SET_CONFIGURATION = 0x09; + /** + * This request returns the selected alternate setting for the specified + * interface (USB spec 9.4.4). + * + * @see ch.ntb.usb.Device#controlMsg(int, int, int, int, byte[], int, int, + * boolean) + */ + public static final int REQ_GET_INTERFACE = 0x0A; + /** + * This request allows the host to select an alternate setting for the + * specified interface (USB spec 9.4.10). + * + * @see ch.ntb.usb.Device#controlMsg(int, int, int, int, byte[], int, int, + * boolean) + */ + public static final int REQ_SET_INTERFACE = 0x0B; + /** + * This request is used to set and then report an endpoint’s synchronization + * frame (USB spec 9.4.11). + * + * @see ch.ntb.usb.Device#controlMsg(int, int, int, int, byte[], int, int, + * boolean) + */ + public static final int REQ_SYNCH_FRAME = 0x0C; + + // data transfer direction (USB spec 9.3) + /** + * Identifies the direction of data transfer in the second phase of the + * control transfer.
+ * The state of the Direction bit is ignored if the wLength field is zero, + * signifying there is no Data stage.
+ * Specifies bit 7 of bmRequestType. + * + * @see ch.ntb.usb.Device#controlMsg(int, int, int, int, byte[], int, int, + * boolean) + */ + public static final int REQ_TYPE_DIR_HOST_TO_DEVICE = (0x00 << 7), + REQ_TYPE_DIR_DEVICE_TO_HOST = (0x01 << 7); + + // request types (USB spec 9.3) + /** + * Specifies the type of the request.
+ * Specifies bits 6..5 of bmRequestType. + * + * @see ch.ntb.usb.Device#controlMsg(int, int, int, int, byte[], int, int, + * boolean) + */ + public static final int REQ_TYPE_TYPE_STANDARD = (0x00 << 5), + REQ_TYPE_TYPE_CLASS = (0x01 << 5), + REQ_TYPE_TYPE_VENDOR = (0x02 << 5), + REQ_TYPE_TYPE_RESERVED = (0x03 << 5); + + // request recipient (USB spec 9.3) + /** + * Specifies the intended recipient of the request.
+ * Requests may be directed to the device, an interface on the device, or a + * specific endpoint on a device. When an interface or endpoint is + * specified, the wIndex field identifies the interface or endpoint.
+ * Specifies bits 4..0 of bmRequestType. + * + * @see ch.ntb.usb.Device#controlMsg(int, int, int, int, byte[], int, int, + * boolean) + */ + public static final int REQ_TYPE_RECIP_DEVICE = 0x00, + REQ_TYPE_RECIP_INTERFACE = 0x01, REQ_TYPE_RECIP_ENDPOINT = 0x02, + REQ_TYPE_RECIP_OTHER = 0x03; + + /** + * The maximum packet size of a bulk transfer when operating in highspeed * (480 MB/s) mode. */ public static int HIGHSPEED_MAX_BULK_PACKET_SIZE = 512; /** - * The maximum packet size of a bulk transfer when operation in fullspeed + * The maximum packet size of a bulk transfer when operating in fullspeed * (12 MB/s) mode. */ public static int FULLSPEED_MAX_BULK_PACKET_SIZE = 64; diff --git a/java/test/ch/ntb/usb/test/DeviceTest.java b/java/test/ch/ntb/usb/test/DeviceTest.java index 406c142..4a925a9 100644 --- a/java/test/ch/ntb/usb/test/DeviceTest.java +++ b/java/test/ch/ntb/usb/test/DeviceTest.java @@ -12,6 +12,7 @@ import static org.junit.Assert.fail; import java.io.FileInputStream; import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.util.Properties; import junit.framework.Assert; @@ -32,9 +33,12 @@ import ch.ntb.usb.testApp.AbstractDeviceInfo.TransferMode; public class DeviceTest { private static final String testdevicePropertiesFile = "testdevice.properties"; - private static final String deviceInfoKey = "testdeviceInfo"; + private static final String Manufacturer = "inf.ntb.ch"; + private static final String Product = "JUnit Test Board"; + private static final String SerialVersion = "00.10.00"; + private static AbstractDeviceInfo devinfo; private static byte[] testData; @@ -202,6 +206,157 @@ public class DeviceTest { } } + @Test + public void controlMsg() throws Exception { + dev.open(devinfo.getConfiguration(), devinfo.getInterface(), devinfo + .getAltinterface()); + // GET STATUS (device) + byte[] data = getTestData(2); + int length = dev.controlMsg(USB.REQ_TYPE_DIR_DEVICE_TO_HOST + | USB.REQ_TYPE_TYPE_STANDARD | USB.REQ_TYPE_RECIP_DEVICE, + USB.REQ_GET_STATUS, 0, 0, data, data.length, devinfo + .getTimeout(), false); + assertEquals((byte) 0x01, data[0]); + assertEquals((byte) 0x00, data[1]); + // GET STATUS (interface) + data = getTestData(2); + length = dev.controlMsg(USB.REQ_TYPE_DIR_DEVICE_TO_HOST + | USB.REQ_TYPE_TYPE_STANDARD | USB.REQ_TYPE_RECIP_INTERFACE, + USB.REQ_GET_STATUS, 0, 0, data, data.length, devinfo + .getTimeout(), false); + assertEquals((byte) 0x00, data[0]); + assertEquals((byte) 0x00, data[1]); + // GET STATUS (endpoint) + data = getTestData(2); + length = dev.controlMsg(USB.REQ_TYPE_DIR_DEVICE_TO_HOST + | USB.REQ_TYPE_TYPE_STANDARD | USB.REQ_TYPE_RECIP_ENDPOINT, + USB.REQ_GET_STATUS, 0, 0, data, data.length, devinfo + .getTimeout(), false); + assertEquals((byte) 0x00, data[0]); + assertEquals((byte) 0x00, data[1]); + // GET CONFIGURATION + data = getTestData(1); + length = dev.controlMsg(USB.REQ_TYPE_DIR_DEVICE_TO_HOST + | USB.REQ_TYPE_TYPE_STANDARD | USB.REQ_TYPE_RECIP_DEVICE, + USB.REQ_GET_CONFIGURATION, 0, 0, data, data.length, devinfo + .getTimeout(), false); + assertEquals((byte) devinfo.getConfiguration(), data[0]); + // // GET INTERFACE + // data = byte[1]; + // length = dev.controlMsg(USB.REQ_TYPE_DIR_DEVICE_TO_HOST + // | USB.REQ_TYPE_TYPE_STANDARD | USB.REQ_TYPE_RECIP_INTERFACE, + // USB.REQ_GET_INTERFACE, 0, devinfo.getInterface(), data, data.length, + // devinfo + // .getTimeout(), false); + // logData(data, length); + // GET DESCRIPTOR (device descriptor) + data = getTestData(128); + length = dev.controlMsg(USB.REQ_TYPE_DIR_DEVICE_TO_HOST + | USB.REQ_TYPE_TYPE_STANDARD | USB.REQ_TYPE_RECIP_DEVICE, + USB.REQ_GET_DESCRIPTOR, 1 << 8, 0, data, data.length, devinfo + .getTimeout(), false); + validateDeviceDescriptor(data, length); + // GET DESCRIPTOR (string descriptor (1)) + data = getTestData(128); + length = dev.controlMsg(USB.REQ_TYPE_DIR_DEVICE_TO_HOST + | USB.REQ_TYPE_TYPE_STANDARD | USB.REQ_TYPE_RECIP_DEVICE, + USB.REQ_GET_DESCRIPTOR, (3 << 8) + 1, 0, data, data.length, + devinfo.getTimeout(), false); + String s = getString(data, length); + assertEquals(s, Manufacturer); + // GET DESCRIPTOR (string descriptor (2)) + data = getTestData(128); + length = dev.controlMsg(USB.REQ_TYPE_DIR_DEVICE_TO_HOST + | USB.REQ_TYPE_TYPE_STANDARD | USB.REQ_TYPE_RECIP_DEVICE, + USB.REQ_GET_DESCRIPTOR, (3 << 8) + 2, 0, data, data.length, + devinfo.getTimeout(), false); + s = getString(data, length); + assertEquals(s, Product); + // GET DESCRIPTOR (string descriptor (3)) + data = getTestData(128); + length = dev.controlMsg(USB.REQ_TYPE_DIR_DEVICE_TO_HOST + | USB.REQ_TYPE_TYPE_STANDARD | USB.REQ_TYPE_RECIP_DEVICE, + USB.REQ_GET_DESCRIPTOR, (3 << 8) + 3, 0, data, data.length, + devinfo.getTimeout(), false); + s = getString(data, length); + assertEquals(s, SerialVersion); + // close the device + dev.close(); + } + + private void validateDeviceDescriptor(byte[] data, int length) { + // length read + assertEquals(18, length); + // descriptor length + assertEquals((byte) 18, data[0]); + // descriptor type + assertEquals((byte) 1, data[1]); + // USB specification number LSB + assertEquals((byte) 0, data[2]); + // USB specification number MSB + assertEquals((byte) 0x02, data[3]); + // device class (vendor specific) + assertEquals((byte) 0xff, data[4]); + // device subclass (vendor specific) + assertEquals((byte) 0xff, data[5]); + // device protocol (vendor specific) + assertEquals((byte) 0xff, data[6]); + // maximum packet size for endpoint zero + assertEquals((byte) 64, data[7]); + // Vendor ID (NTB) LSB + assertEquals((byte) 0x35, data[8]); + // Vendor ID (NTB) MSB + assertEquals((byte) 0x82, data[9]); + // Product ID (JUnit test board) LSB + assertEquals((byte) 0x22, data[10]); + // Product ID (JUnit test board) MSB + assertEquals((byte) 0x02, data[11]); + + // Device release number LSB + assertEquals((byte) 0x00, data[12]); + // Device release number MSB + assertEquals((byte) 0x10, data[13]); + // Index of manufacturer string descriptor + assertEquals((byte) 0x01, data[14]); + // Index of product string descriptor + assertEquals((byte) 0x02, data[15]); + // Index of serial number string descriptor + assertEquals((byte) 0x03, data[16]); + // Number of possible configurations + assertEquals((byte) 0x01, data[17]); + } + + private byte[] getTestData(int length) { + byte[] b = new byte[length]; + for (int i = 0; i < b.length; i++) { + b[i] = (byte) (Math.random() * 256); + } + return b; + } + + private void logData(byte[] data, int length) { + if (length > 0) { + System.out.println("length: " + length); + for (int i = 0; i < length; i++) { + System.out.print("0x" + Integer.toHexString(data[i] & 0xff) + + "\t"); + } + System.out.println(); + } + } + + private String getString(byte[] data, int length) + throws UnsupportedEncodingException { + // data length + assertTrue(length > 2); + // string length + assertTrue(data[0] > 2); + // string descriptor ident + assertEquals((byte) 3, data[1]); + // create string from data + return new String(data, 2, length - 2, "UTF-16LE"); + } + @Test public void invalidConfig() throws Exception { try { diff --git a/java/version.properties b/java/version.properties index db02053..7c2fcf9 100644 --- a/java/version.properties +++ b/java/version.properties @@ -1,4 +1,4 @@ #Thu Aug 24 14:28:28 CEST 2006 version.major=0 -version.minor=3 +version.minor=4 version.release=0