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

net.codecrete.usb.linux.LinuxAsyncTask Maven / Gradle / Ivy

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

package net.codecrete.usb.linux;

import net.codecrete.usb.UsbTransferType;
import net.codecrete.usb.linux.gen.errno.errno;
import net.codecrete.usb.linux.gen.usbdevice_fs.usbdevfs_urb;

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static java.lang.foreign.ValueLayout.ADDRESS;
import static net.codecrete.usb.common.ForeignMemory.dereference;
import static net.codecrete.usb.linux.EPoll.epoll_create1;
import static net.codecrete.usb.linux.EPoll.epoll_wait;
import static net.codecrete.usb.linux.Linux.allocateErrorState;
import static net.codecrete.usb.linux.LinuxUsbException.throwException;
import static net.codecrete.usb.linux.LinuxUsbException.throwLastError;
import static net.codecrete.usb.linux.UsbDevFS.DISCARDURB;
import static net.codecrete.usb.linux.UsbDevFS.REAPURBNDELAY;
import static net.codecrete.usb.linux.UsbDevFS.SUBMITURB;
import static net.codecrete.usb.linux.gen.epoll.epoll.EPOLLOUT;
import static net.codecrete.usb.linux.gen.epoll.epoll.EPOLLWAKEUP;
import static net.codecrete.usb.linux.gen.errno.errno.EINTR;
import static net.codecrete.usb.linux.gen.errno.errno.ENODEV;
import static net.codecrete.usb.linux.gen.fcntl.fcntl.FD_CLOEXEC;
import static net.codecrete.usb.linux.gen.usbdevice_fs.usbdevice_fs.USBDEVFS_URB_TYPE_BULK;
import static net.codecrete.usb.linux.gen.usbdevice_fs.usbdevice_fs.USBDEVFS_URB_TYPE_CONTROL;
import static net.codecrete.usb.linux.gen.usbdevice_fs.usbdevice_fs.USBDEVFS_URB_TYPE_INTERRUPT;
import static net.codecrete.usb.linux.gen.usbdevice_fs.usbdevice_fs.USBDEVFS_URB_TYPE_ISO;

/**
 * Background task for handling asynchronous transfers.
 * 

* Each USB device must register its file handle with this task. *

*

* The task keeps track of the submitted transfers by indexing them * by URB address (USB request block). *

*

* URBs are allocated but never freed. To limit the memory usage, * URBs are reused. So the maximum number of outstanding transfers * determines the number of allocated URBs. *

*/ @SuppressWarnings("java:S6548") class LinuxAsyncTask { /** * Singleton instance of background task. */ static final LinuxAsyncTask INSTANCE = new LinuxAsyncTask(); private static final int NUM_EVENTS = 5; private final Arena urbArena = Arena.ofAuto(); /// available URBs private final List availableURBs = new ArrayList<>(); /// map of URB addresses to transfer (for outstanding transfers) private final Map transfersByURB = new LinkedHashMap<>(); /// file descriptor of epoll private int epollFd = -1; /** * Background task for handling asynchronous IO completions. *

* It polls on all registered file descriptors. If a file descriptor is * ready, the URB is "reaped". *

*/ @SuppressWarnings({"java:S2189", "java:S135", "java:S3776"}) private void asyncCompletionTask() { try (var arena = Arena.ofConfined()) { var errorState = allocateErrorState(arena); var urbPointerHolder = arena.allocate(ADDRESS); var events = arena.allocate(EPoll.EVENT$LAYOUT, NUM_EVENTS); while (true) { // wait for file descriptor to be ready var res = epoll_wait(epollFd, events, NUM_EVENTS, -1, errorState); if (res < 0) { var err = Linux.getErrno(errorState); if (err == EINTR()) continue; // continue on interrupt throwException(err, "internal error (epoll_wait)"); } // for all ready file descriptors, reap URBs for (int i = 0; i < res; i++) { var fd = (int) EPoll.EVENT_ARRAY_DATA_FD$VH.get(events, 0L, i); reapURBs(fd, urbPointerHolder, errorState); } } } } /** * Reap all pending URBs and handle the completed transfers. * * @param fd file descriptor * @param urbPointerHolder native memory to receive the URB pointer * @param errorState native memory to receive the errno */ private synchronized void reapURBs(int fd, MemorySegment urbPointerHolder, MemorySegment errorState) { while (true) { var res = IO.ioctl(fd, REAPURBNDELAY, urbPointerHolder, errorState); if (res < 0) { var err = Linux.getErrno(errorState); if (err == errno.EAGAIN()) return; // no more pending URBs if (err == errno.ENODEV()) { // device might have been unplugged EPoll.removeFileDescriptor(epollFd, fd); return; } throwException(err, "internal error (reap URB)"); } // call completion handler var urb = dereference(urbPointerHolder); var transfer = getTransferWithResult(urb); transfer.completion().completed(transfer); } } /** * Register a device for asynchronous IO completion handling * * @param device USB device */ synchronized void addForAsyncIOCompletion(LinuxUsbDevice device) { // start background process if needed if (epollFd < 0) startAsyncIOTask(); EPoll.addFileDescriptor(epollFd, EPOLLOUT() | EPOLLWAKEUP(), device.fileDescriptor()); } /** * Unregisters a device from asynchronous IO completion handling. * * @param device USB device */ synchronized void removeFromAsyncIOCompletion(LinuxUsbDevice device) { int fd = device.fileDescriptor(); // remove file descriptor from epoll EPoll.removeFileDescriptor(epollFd, fd); // reap outstanding URBs try (var arena = Arena.ofConfined()) { var errorState = allocateErrorState(arena); var urbPointerHolder = arena.allocate(ADDRESS); reapURBs(fd, urbPointerHolder, errorState); } // reclaim stale URBs transfersByURB.entrySet().removeIf(e -> { var urb = e.getKey(); var isMatch = usbdevfs_urb.usercontext(urb).address() == fd; if (isMatch) { var transfer = e.getValue(); transfer.urb = null; transfer.setResultCode(ENODEV()); transfer.setResultSize(0); transfer.completion().completed(transfer); availableURBs.add(urb); } return isMatch; }); } synchronized void submitTransfer(LinuxUsbDevice device, int endpointAddress, UsbTransferType transferType, LinuxTransfer transfer) { linkToUrb(transfer); var urb = transfer.urb; usbdevfs_urb.type(urb, (byte) urbTransferType(transferType)); usbdevfs_urb.endpoint(urb, (byte) endpointAddress); usbdevfs_urb.buffer(urb, transfer.data()); usbdevfs_urb.buffer_length(urb, transfer.dataSize()); usbdevfs_urb.usercontext(urb, MemorySegment.ofAddress(device.fileDescriptor())); try (var arena = Arena.ofConfined()) { var errorState = allocateErrorState(arena); if (IO.ioctl(device.fileDescriptor(), SUBMITURB, urb, errorState) < 0) { var action = endpointAddress >= 128 ? "reading from" : "writing to"; var endpoint = endpointAddress == 0 ? "control endpoint" : String.format("endpoint %d", endpointAddress); throwLastError(errorState, "error occurred while %s %s", action, endpoint); } } } private static int urbTransferType(UsbTransferType transferType) { return switch (transferType) { case BULK -> USBDEVFS_URB_TYPE_BULK(); case INTERRUPT -> USBDEVFS_URB_TYPE_INTERRUPT(); case CONTROL -> USBDEVFS_URB_TYPE_CONTROL(); case ISOCHRONOUS -> USBDEVFS_URB_TYPE_ISO(); }; } /** * Links the specified transfer instance to a URB. *

* The transfer is assigned an URB instance, and a list * of associations from URB to transfer is maintained. *

* @param transfer the transfer to assign a URB. */ private void linkToUrb(LinuxTransfer transfer) { MemorySegment urb; var size = availableURBs.size(); if (size > 0) { urb = availableURBs.remove(size - 1); } else { urb = usbdevfs_urb.allocate(urbArena); } transfer.urb = urb; transfersByURB.put(urb, transfer); } /** * Gets the transfer associated with the specified URB and adds the result. *

* The URB is returned into the list of URBs available for further transfers. *

* * @param urb URB instance * @return transfer associated with the URB */ @SuppressWarnings("java:S2259") private synchronized LinuxTransfer getTransferWithResult(MemorySegment urb) { var transfer = transfersByURB.remove(urb); if (transfer == null) throwException("internal error (unknown URB)"); transfer.setResultCode(-usbdevfs_urb.status(transfer.urb)); transfer.setResultSize(usbdevfs_urb.actual_length(transfer.urb)); availableURBs.add(transfer.urb); transfer.urb = null; return transfer; } @SuppressWarnings("java:S1066") synchronized void abortTransfers(LinuxUsbDevice device, byte endpointAddress) { var fd = device.fileDescriptor(); try (var arena = Arena.ofConfined()) { var errorState = allocateErrorState(arena); // iterate all URBs and discard the ones for the specified endpoint transfersByURB.keySet().stream() .filter(urb -> usbdevfs_urb.usercontext(urb).address() == fd && usbdevfs_urb.endpoint(urb) == endpointAddress) .forEach(urb -> { if (IO.ioctl(fd, DISCARDURB, urb, errorState) < 0) { // ignore EINVAL; it occurs if the URB has completed at the same time if (Linux.getErrno(errorState) != errno.EINVAL()) throwLastError(errorState, "error occurred while aborting transfer"); } } ); } } private void startAsyncIOTask() { try (var arena = Arena.ofConfined()) { var errorState = allocateErrorState(arena); epollFd = epoll_create1(FD_CLOEXEC(), errorState); if (epollFd < 0) throwLastError(errorState, "internal error (epoll_create)"); } // start background thread for handling IO completion var thread = new Thread(this::asyncCompletionTask, "USB async IO"); thread.setDaemon(true); thread.start(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy