All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.codecrete.usb.common.EndpointInputStream Maven / Gradle / Ivy

//
// Java Does USB
// Copyright (c) 2023 Manuel Bleichenbacher
// Licensed under MIT License
// https://opensource.org/licenses/MIT
//

package net.codecrete.usb.common;

import net.codecrete.usb.UsbDirection;
import net.codecrete.usb.UsbException;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.io.InputStream;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.concurrent.ArrayBlockingQueue;

import static java.lang.foreign.ValueLayout.JAVA_BYTE;

/**
 * Input stream for bulk endpoints – optimized for high throughput.
 *
 * 

* Multiple asynchronous transfers are submitted to achieve a good * degree of concurrency between the USB communication handled by the operating * system and the consuming application code. *

*

* For thread synchronization (between the background thread handling IO completions * and the consuming application thread) a blocking queue is used. When an transfer * completes, the background thread adds it to the queue. The consuming code * waits for the next item in the queue. *

*/ public abstract class EndpointInputStream extends InputStream { protected UsbDeviceImpl device; protected final int endpointNumber; // Arena to allocate buffers and completion handlers protected final Arena arena; // Transfer size (multiple of packet size) protected final int transferSize; // Queue of completed transfers private final ArrayBlockingQueue completedTransferQueue; // Number of outstanding transfers (includes transfers pending with the // operating system and transfers in the completed queue) private int numOutstandingTransfers; // Transfer and associated buffer being currently read from private Transfer currentTransfer; // Read offset within current transfer buffer private int readOffset; /** * Creates a new instance * * @param device USB device * @param endpointNumber endpoint number * @param bufferSize approximate buffer size (in bytes) */ protected EndpointInputStream(UsbDeviceImpl device, int endpointNumber, int bufferSize) { this.device = device; this.endpointNumber = endpointNumber; arena = Arena.ofShared(); var packetSize = device.getEndpoint(UsbDirection.IN, endpointNumber).getPacketSize(); // use between 4 and 32 packets per transfer (256B to 2KB for FS, 2KB to 16KB for HS) var numPacketsPerTransfer = (int) Math.round(Math.sqrt((double) bufferSize / packetSize)); numPacketsPerTransfer = Math.min(Math.max(numPacketsPerTransfer, 4), 32); transferSize = numPacketsPerTransfer * packetSize; // use at least 2 outstanding transfers (3 in total) var maxOutstandingTransfers = Math.max((bufferSize + transferSize / 2) / transferSize, 3); configureEndpoint(); completedTransferQueue = new ArrayBlockingQueue<>(maxOutstandingTransfers); // create all transfers, and submit them except one try { for (var i = 0; i < maxOutstandingTransfers; i++) { final var transfer = device.createTransfer(); transfer.setData(arena.allocate(transferSize, 8)); transfer.setDataSize(transferSize); transfer.setCompletion(this::onCompletion); if (i == 0) { currentTransfer = transfer; } else { submitTransfer(transfer); } } } catch (Exception t) { collectOutstandingTransfers(); throw t; } } private boolean isClosed() { return device == null; } @SuppressWarnings("RedundantThrows") @Override public void close() throws IOException { if (isClosed()) return; // abort all transfers on endpoint try { device.abortTransfers(UsbDirection.IN, endpointNumber); } catch (UsbException e) { // If aborting the transfer is not possible, the device has // likely been closed or unplugged. So all outstanding // transfers will terminate anyway. } device = null; collectOutstandingTransfers(); } @Override public int read() throws IOException { if (isClosed()) return -1; if (available() == 0) receiveMoreData(); var b = currentTransfer.data().get(JAVA_BYTE, readOffset) & 0xff; readOffset += 1; return b; } @Override public int read(byte @NotNull [] b, int off, int len) throws IOException { if (isClosed()) return -1; var numRead = 0; do { if (available() == 0) receiveMoreData(); // copy data to receiving buffer var n = Math.min(len - numRead, currentTransfer.resultSize() - readOffset); MemorySegment.copy(currentTransfer.data(), readOffset, MemorySegment.ofArray(b), (long) off + numRead, n); readOffset += n; numRead += n; } while (numRead < len && hasMoreTransfers()); return numRead; } @SuppressWarnings("RedundantThrows") @Override public int available() throws IOException { return currentTransfer.resultSize() - readOffset; } private boolean hasMoreTransfers() { return !completedTransferQueue.isEmpty(); } private void receiveMoreData() throws IOException { try { // loop until non-ZLP has been received do { // the current transfer has no more data to process and // can be submitted to read more data submitTransfer(currentTransfer); currentTransfer = waitForCompletedTransfer(); readOffset = 0; // check for error if (currentTransfer.resultCode() != 0) device.throwOSException(currentTransfer.resultCode(), "error occurred while reading from endpoint %d", endpointNumber); } while (currentTransfer.resultSize() <= 0); } catch (Exception t) { close(); throw t; } } private Transfer waitForCompletedTransfer() { while (true) { try { var transfer = completedTransferQueue.take(); numOutstandingTransfers -= 1; return transfer; } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } private void submitTransfer(Transfer transfer) { submitTransferIn(transfer); numOutstandingTransfers += 1; } private void onCompletion(Transfer transfer) { completedTransferQueue.add(transfer); } private void collectOutstandingTransfers() { // wait until completion handlers have been called while (numOutstandingTransfers > 0) waitForCompletedTransfer(); completedTransferQueue.clear(); currentTransfer = null; arena.close(); } protected abstract void submitTransferIn(Transfer transfer); protected void configureEndpoint() { } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy