Search code examples
javaandroidusbjna

USBFS IOCTL call witn JNA doesn't work in 64 bits architectures


In Android, I'm using a class (UsbIso.java) to transfer data in an isochronous way from a USB device attached. Since Android doesn't natively support isochronous transfers, I had to use the USBFS native Linux mechanism through the JNA library to make the proper ioctl calls.

In Android devices with 32-bit architecture (armeabi, armeabi-v7a) everything works properly. In Android devices with 64-bit architecture (arm64-v8a) the ioctl call to reap URB (USBDEVFS_REAPURB, inside the reapRequest method, see related code below) returns error 14, bad address. I guess that this is caused either by the USBDEVFS_REAPURB parameter or by the PointerByReference parameter, which points to a non-valid virtual address, but I have no clue about how to solve it.

The related code in the UsbIso.java class that causes this error is this:

public Request reapRequest (boolean wait) throws IOException {
        PointerByReference urbPointer = new PointerByReference();
        int func = wait ? USBDEVFS_REAPURB : USBDEVFS_REAPURBNDELAY;
        int rc;
        try {
            rc = libc.ioctl(fileDescriptor, func, urbPointer);  // <-- Error 14, bad address
        } catch (LastErrorException e) {
            if (e.getErrorCode() == EAGAIN && !wait) {
                return null; 
            }
        }
...

}

Solution

  • You are using source code optimized for 32-bit:

    // Note: The layout and size of the USBFS structures matches that of Linux Kernel 3.2 and 3.14
    // for ARM 32 bit. For other environments (X86, 64 bit, future Linux kernels), it might be
    // necessary to adjust some values.
    

    While JNA would normally adjust a structure mapping for 32- vs. 64- bit, this code thinks JNA is too slow and manually maps those offsets:

    // This class is modeled after struct usbdevfs_urb in <linuxKernel>/include/linux/usbdevice_fs.h
    // At first I implemented the URB structure directly using com.sun.jna.Structure, but that was extremely slow.
    // Therefore byte offsets are now used to access the fields of the structure.
    

    If you look at the structure mapping for usbdevfs_urb there are 3 pointer fields that need adjusting from a 4-byte offset to an 8-byte offset. For example, the 5th field buffer changes from 4 bytes to 8 bytes, so this code breaks:

       public void setBuffer (Pointer buffer) {
          urbBuf.putInt(12, (int)Pointer.nativeValue(buffer)); }
       public void setBufferLength (int bufferLength) {
          urbBuf.putInt(16, bufferLength); }
    

    In particular, the putInt(12, (int) ...) should probably be putLong(12, ...) and the 16 in the next call should be 20 (and so on adding 4 to the remaining offsets.)

    The last two fields are also 8 byte vs. 4 byte, so the setUserContext() and getUserContext() need to deal with long rather than int and the urbBaseSize needs incrementing from 44 to 52 (+4 for the buffer, +4 for the userContext.

    I see a few other int variables representing memory addresses that would need to become longs. There may be other changes required that I've missed.