Skip to content

Commit 9c2dcfd

Browse files
committed
Bulk/interrupt transfer use async IO internally (Linux)
1 parent a44fd56 commit 9c2dcfd

File tree

12 files changed

+161
-129
lines changed

12 files changed

+161
-129
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
java-does-usb/sample.bin
2+
13
# Maven
24
target/
35
*.ser

java-does-usb/jextract/linux/gen_linux.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ $JEXTRACT --source --output ../../src/main/java \
1717
--include-constant ETIMEDOUT \
1818
--include-constant EPIPE \
1919
--include-constant EBADF \
20+
--include-constant EAGAIN \
2021
/usr/include/errno.h
2122

2223
# string.h

java-does-usb/src/main/java/net/codecrete/usb/common/USBDeviceImpl.java

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,13 +307,88 @@ public byte[] transferIn(int endpointNumber) {
307307
@Override
308308
public abstract byte[] transferIn(int endpointNumber, int timeout);
309309

310+
protected void waitForTransfer(Transfer transfer, int timeout, USBDirection direction, int endpointNumber) {
311+
if (timeout <= 0) {
312+
waitNoTimeout(transfer);
313+
314+
} else {
315+
boolean hasTimedOut = waitWithTimeout(transfer, timeout);
316+
317+
// test for timeout
318+
if (hasTimedOut && transfer.resultCode == 0) {
319+
abortTransfers(direction, endpointNumber);
320+
waitNoTimeout(transfer);
321+
throw new USBTimeoutException(getOperationDescription(direction, endpointNumber) + "aborted due to " +
322+
"timeout");
323+
}
324+
}
325+
326+
// test for error
327+
if (transfer.resultCode != 0) {
328+
var operation = getOperationDescription(direction, endpointNumber);
329+
throwOSException(transfer.resultCode, operation + " failed");
330+
}
331+
}
332+
333+
private static void waitNoTimeout(Transfer transfer) {
334+
// wait for transfer
335+
while (transfer.resultSize == -1) {
336+
try {
337+
transfer.wait();
338+
} catch (InterruptedException e) {
339+
// ignore and retry
340+
}
341+
}
342+
}
343+
344+
private static boolean waitWithTimeout(Transfer transfer, int timeout) {
345+
// wait for transfer to complete, or abort when timeout occurs
346+
long expiration = System.currentTimeMillis() + timeout;
347+
long remainingTimeout = timeout;
348+
while (remainingTimeout > 0 && transfer.resultSize == -1) {
349+
try {
350+
transfer.wait(remainingTimeout);
351+
remainingTimeout = expiration - System.currentTimeMillis();
352+
353+
} catch (InterruptedException e) {
354+
// ignore and retry
355+
}
356+
}
357+
358+
return remainingTimeout <= 0;
359+
}
360+
361+
protected static String getOperationDescription(USBDirection direction, int endpointNumber) {
362+
if (endpointNumber == 0) {
363+
return "Control transfer";
364+
} else {
365+
return String.format("Transfer %s on endpoint %d", direction.name(), endpointNumber);
366+
}
367+
368+
}
369+
310370
/**
311371
* Create a transfer object suitable for this device.
312372
*
313373
* @return transfer object
314374
*/
315375
protected abstract Transfer createTransfer();
316376

377+
/**
378+
* Completion handler used for synchronous, blocking transfers.
379+
* <p>
380+
* Calls {@link Object#notify()} so the caller can use
381+
* {@link Object#wait()} to wait for completion.
382+
* </p>
383+
*
384+
* @param transfer the transfer that has completed
385+
*/
386+
protected static void onSyncTransferCompleted(Transfer transfer) {
387+
synchronized (transfer) {
388+
transfer.notify();
389+
}
390+
}
391+
317392
/**
318393
* Throws an exception for the specified operating-specific error code.
319394
*

java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxAsyncTask.java

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ static synchronized LinuxAsyncTask instance() {
7070

7171
/**
7272
* Background task for handling asynchronous IO completions.
73+
* <p>
74+
* It polls on all registered file descriptors. If a file descriptor is
75+
* ready, the URB is "reaped".
76+
* </p>
77+
* <p>
78+
* Using an additional {@code eventfd} file descriptor, this background task
79+
* can be woken up to refresh the list of polled file descriptors.
80+
* </p>
7381
*/
7482
private void asyncCompletionTask() {
7583

@@ -91,7 +99,7 @@ private void asyncCompletionTask() {
9199
var n = fds.length;
92100
for (int i = 0; i < n; i++) {
93101
pollfd.fd$set(asyncPolls, i, fds[i]);
94-
pollfd.events$set(asyncPolls, i, (short) (poll.POLLIN() | poll.POLLOUT()));
102+
pollfd.events$set(asyncPolls, i, (short) poll.POLLOUT());
95103
pollfd.revents$set(asyncPolls, i, (short) 0);
96104
}
97105

@@ -111,7 +119,10 @@ private void asyncCompletionTask() {
111119
continue;
112120

113121
if ((revent & poll.POLLERR()) != 0) {
114-
// most likely the device has been disconnected; ignore
122+
// most likely the device has been disconnected;
123+
// remove from polled FD list to prevent further problems
124+
int fd = pollfd.fd$get(asyncPolls, i);
125+
removeFdFromAsyncIOCompletion(fd);
115126
continue;
116127
}
117128

@@ -127,16 +138,24 @@ private void asyncCompletionTask() {
127138
throwLastError(errnoState, "internal error (eventfd_read)");
128139
}
129140
}
130-
131141
}
132142
}
133143
}
134144

145+
/**
146+
* Reap URB and handle the completed transfer.
147+
*
148+
* @param fd file descriptor
149+
* @param urbPointerHolder native memory to receive the URB pointer
150+
* @param errnoState native memory to receive the errno
151+
*/
135152
private void reapURB(int fd, MemorySegment urbPointerHolder, MemorySegment errnoState) {
136153
int res;
137-
res = IO.ioctl(fd, REAPURB, urbPointerHolder, errnoState);
154+
res = IO.ioctl(fd, REAPURBNDELAY, urbPointerHolder, errnoState);
138155
if (res < 0) {
139156
var err = Linux.getErrno(errnoState);
157+
if (err == errno.EAGAIN())
158+
return; // retry
140159
if (err == errno.EBADF())
141160
return; // ignore, device might have been closed
142161
throwException(err, "internal error (reap URB)");
@@ -188,30 +207,29 @@ synchronized void addForAsyncIOCompletion(LinuxUSBDevice device) {
188207
* @param device USB device
189208
*/
190209
synchronized void removeFromAsyncIOCompletion(LinuxUSBDevice device) {
210+
removeFdFromAsyncIOCompletion(device.fileDescriptor());
211+
notifyAsyncIOTask();
212+
}
213+
214+
private synchronized void removeFdFromAsyncIOCompletion(int fd) {
191215
// copy file descriptor (except the device's) into new array
192216
int n = asyncFds.length;
193-
if (n == 0) {
194-
System.err.println("internal error (file descriptor not found) - ignoring");
217+
if (n == 0)
195218
return;
196-
}
197219

198-
int fd = device.fileDescriptor();
199220
int[] fds = new int[n - 1];
200221
int tgt = 0;
201222
for (int asyncFd : asyncFds) {
202223
if (asyncFd != fd) {
203-
if (tgt == n) {
204-
System.err.println("internal error (file descriptor not found) - ignoring");
224+
if (tgt == n)
205225
return;
206-
}
207226
fds[tgt] = asyncFd;
208227
tgt += 1;
209228
}
210229
}
211230

212231
// make new array to active one
213232
asyncFds = fds;
214-
notifyAsyncIOTask();
215233
}
216234

217235
synchronized void submitBulkTransfer(LinuxUSBDevice device, int endpointAddress, LinuxTransfer transfer) {
@@ -252,7 +270,7 @@ private synchronized LinuxTransfer getTransferResult(long urbAddr) {
252270
if (transfer == null)
253271
throwException("internal error (unknown URB)");
254272

255-
transfer.resultCode = usbdevfs_urb.status$get(transfer.urb);
273+
transfer.resultCode = -usbdevfs_urb.status$get(transfer.urb);
256274
transfer.resultSize = usbdevfs_urb.actual_length$get(transfer.urb);
257275

258276
availableURBs.add(transfer.urb);

java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxEndpointInputStream.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package net.codecrete.usb.linux;
99

10+
import net.codecrete.usb.USBDirection;
1011
import net.codecrete.usb.common.EndpointInputStream;
1112
import net.codecrete.usb.common.Transfer;
1213

@@ -18,6 +19,6 @@ public class LinuxEndpointInputStream extends EndpointInputStream {
1819

1920
@Override
2021
protected void submitTransferIn(Transfer transfer) {
21-
((LinuxUSBDevice) device).submitTransferIn(endpointNumber, (LinuxTransfer) transfer);
22+
((LinuxUSBDevice) device).submitBulkTransfer(USBDirection.IN, endpointNumber, (LinuxTransfer) transfer);
2223
}
2324
}

java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxEndpointOutputStream.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package net.codecrete.usb.linux;
99

10+
import net.codecrete.usb.USBDirection;
1011
import net.codecrete.usb.common.EndpointOutputStream;
1112
import net.codecrete.usb.common.Transfer;
1213

@@ -18,6 +19,6 @@ public class LinuxEndpointOutputStream extends EndpointOutputStream {
1819

1920
@Override
2021
protected void submitTransferOut(Transfer transfer) {
21-
((LinuxUSBDevice) device).submitTransferOut(endpointNumber, (LinuxTransfer) transfer);
22+
((LinuxUSBDevice) device).submitBulkTransfer(USBDirection.OUT, endpointNumber, (LinuxTransfer) transfer);
2223
}
2324
}

java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUSBDevice.java

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77

88
package net.codecrete.usb.linux;
99

10-
import net.codecrete.usb.*;
10+
import net.codecrete.usb.USBControlTransfer;
11+
import net.codecrete.usb.USBDirection;
12+
import net.codecrete.usb.USBException;
13+
import net.codecrete.usb.USBTransferType;
1114
import net.codecrete.usb.common.Transfer;
1215
import net.codecrete.usb.common.USBDeviceImpl;
1316
import net.codecrete.usb.common.USBInterfaceImpl;
14-
import net.codecrete.usb.linux.gen.errno.errno;
1517
import net.codecrete.usb.linux.gen.fcntl.fcntl;
1618
import net.codecrete.usb.linux.gen.unistd.unistd;
17-
import net.codecrete.usb.linux.gen.usbdevice_fs.usbdevfs_bulktransfer;
1819
import net.codecrete.usb.linux.gen.usbdevice_fs.usbdevfs_ctrltransfer;
1920
import net.codecrete.usb.linux.gen.usbdevice_fs.usbdevfs_setinterface;
2021
import net.codecrete.usb.usbstandard.DeviceDescriptor;
@@ -209,12 +210,12 @@ public void controlTransferOut(USBControlTransfer setup, byte[] data) {
209210
}
210211
}
211212

212-
private MemorySegment createBulkTransfer(Arena arena, byte endpointAddress, MemorySegment data, int timeout) {
213-
var transfer = arena.allocate(usbdevfs_bulktransfer.$LAYOUT());
214-
usbdevfs_bulktransfer.ep$set(transfer, 255 & endpointAddress);
215-
usbdevfs_bulktransfer.len$set(transfer, (int) data.byteSize());
216-
usbdevfs_bulktransfer.data$set(transfer, data);
217-
usbdevfs_bulktransfer.timeout$set(transfer, timeout);
213+
private LinuxTransfer createSyncTransfer(MemorySegment data) {
214+
var transfer = new LinuxTransfer();
215+
transfer.data = data;
216+
transfer.dataSize = (int) data.byteSize();
217+
transfer.resultSize = -1;
218+
transfer.completion = USBDeviceImpl::onSyncTransferCompleted;
218219
return transfer;
219220
}
220221

@@ -225,15 +226,11 @@ public void transferOut(int endpointNumber, byte[] data, int timeout) {
225226
try (var arena = Arena.openConfined()) {
226227
var buffer = arena.allocate(data.length);
227228
buffer.copyFrom(MemorySegment.ofArray(data));
228-
var transfer = createBulkTransfer(arena, endpoint.endpointAddress(), buffer, timeout);
229+
var transfer = createSyncTransfer(buffer);
229230

230-
var errnoState = arena.allocate(Linux.ERRNO_STATE.layout());
231-
int res = IO.ioctl(fd, USBDevFS.BULK, transfer, errnoState);
232-
if (res < 0) {
233-
int err = Linux.getErrno(errnoState);
234-
if (err == errno.ETIMEDOUT())
235-
throw new USBTimeoutException("Transfer out aborted due to timeout");
236-
throwLastError(errnoState, "USB OUT transfer on endpoint %d failed", endpointNumber);
231+
synchronized (transfer) {
232+
asyncTask.submitBulkTransfer(this, endpoint.endpointAddress(), transfer);
233+
waitForTransfer(transfer, timeout, USBDirection.OUT, endpointNumber);
237234
}
238235
}
239236
}
@@ -244,19 +241,14 @@ public byte[] transferIn(int endpointNumber, int timeout) {
244241

245242
try (var arena = Arena.openConfined()) {
246243
var buffer = arena.allocate(endpoint.packetSize());
244+
var transfer = createSyncTransfer(buffer);
247245

248-
var transfer = createBulkTransfer(arena, endpoint.endpointAddress(), buffer, timeout);
249-
250-
var errnoState = arena.allocate(Linux.ERRNO_STATE.layout());
251-
int res = IO.ioctl(fd, USBDevFS.BULK, transfer, errnoState);
252-
if (res < 0) {
253-
int err = Linux.getErrno(errnoState);
254-
if (err == errno.ETIMEDOUT())
255-
throw new USBTimeoutException("Transfer in aborted due to timeout");
256-
throwLastError(errnoState, "USB IN transfer on endpoint %d failed", endpointNumber);
246+
synchronized (transfer) {
247+
asyncTask.submitBulkTransfer(this, endpoint.endpointAddress(), transfer);
248+
waitForTransfer(transfer, timeout, USBDirection.IN, endpointNumber);
257249
}
258250

259-
return buffer.asSlice(0, res).toArray(JAVA_BYTE);
251+
return buffer.asSlice(0, transfer.resultSize).toArray(JAVA_BYTE);
260252
}
261253
}
262254

@@ -306,13 +298,8 @@ public OutputStream openOutputStream(int endpointNumber) {
306298
return new LinuxEndpointOutputStream(this, endpointNumber);
307299
}
308300

309-
synchronized void submitTransferIn(int endpointNumber, LinuxTransfer transfer) {
310-
var endpoint = getEndpoint(USBDirection.IN, endpointNumber, USBTransferType.BULK, USBTransferType.INTERRUPT);
311-
asyncTask.submitBulkTransfer(this, endpoint.endpointAddress(), transfer);
312-
}
313-
314-
synchronized void submitTransferOut(int endpointNumber, LinuxTransfer transfer) {
315-
var endpoint = getEndpoint(USBDirection.OUT, endpointNumber, USBTransferType.BULK, USBTransferType.INTERRUPT);
301+
synchronized void submitBulkTransfer(USBDirection direction, int endpointNumber, LinuxTransfer transfer) {
302+
var endpoint = getEndpoint(direction, endpointNumber, USBTransferType.BULK, null);
316303
asyncTask.submitBulkTransfer(this, endpoint.endpointAddress(), transfer);
317304
}
318305
}

java-does-usb/src/main/java/net/codecrete/usb/linux/USBDevFS.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class USBDevFS {
2121
public static final long SUBMITURB = 0x8038550AL;
2222
public static final long DISCARDURB = 0x550BL;
2323
public static final long REAPURB = 0x4008550CL;
24+
public static final long REAPURBNDELAY = 0x4008550DL;
2425
public static final long RESETEP = 0x80045503L;
2526

2627
}

java-does-usb/src/main/java/net/codecrete/usb/linux/gen/errno/errno.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ public class errno {
2121
public static int EBADF() {
2222
return (int)9L;
2323
}
24+
/**
25+
* {@snippet :
26+
* #define EAGAIN 11
27+
* }
28+
*/
29+
public static int EAGAIN() {
30+
return (int)11L;
31+
}
2432
/**
2533
* {@snippet :
2634
* #define EPIPE 32

0 commit comments

Comments
 (0)