Skip to content

Linux Implementation

Manuel Bl edited this page Apr 30, 2023 · 4 revisions

The Linux implementation is probably the simplest implementation as the USB API on Linux is straight-forward to use. The main challenge on Linux is the poor documentation. There is minimal documentation in The Linux-USB Host Side API. It partially covers USB Request Blocks (URBs). But operations like USBDEVFS_DISCARDURB aren't documented at all.

Also see Linux reference code.

Device enumeration and monitoring

For the device enumeration and monitoring, the libudev functions are used. The library is straight-forward to use, both because it provides the necessary features and because the API is built for use in C and is a good match for Java's Foreign Function & Memory API.

libudev is officially deprecated. The replacement is libsystemd. But it seems that libsystemd isn't as widely available as libudev. A migration to libsystemd is planned and will be straight-forward as the two libraries have a very similar API.

The biggest issue with libudev and libsystemd is the lack of documentation. There are a few man pages. But they are very limited.

Registry background thread

The background thread on Linux is very simple as well. libudev provides a file descriptor for the monitor. So the background thread can simply use poll() to wait for the next monitor event.

Asynchronous task completion thread

Asychronous transfers are implemented by createing a USB Request Block (URB) and submitting it. The background thread (LinuxAsyncTask) then uses poll() to wait for completed transfers. The result of each transfer is retrieved by reaping the URB and calling the client's completion handler.

The tricky part is that poll() needs to be waken up whenever a new asynchronous transfer is added or removed as the list of file descriptors to wait for changes. The list of file descriptors is updated in a different thread than the one calling poll(). So care must be taken to make it multi-threading safe.

Descriptive information

Linux does not provide any APIs to query any descriptive information about interfaces and endpoints. There are two options. The first option would be to read the pseudo-files in /sys/bus/usb/devices/n-n.n. It is quite cumbersome and would require many file operations.

The second and chosen option is to read all data from the USB device's file path (returned by libudev). It's a concatenation of the device descriptor and configuration descriptor. The descriptors are then parsed by this library (see net.codecrete.usb.common.ConfigurationParser). The configuration parser is needed anyway on Windows to learn about the interface associations and functions.

Device communication

Device communication is straight-forward as well as the API is very similar to the one used by this library. A device needs to be opened. Then ioctl() can be used to claim interfaces and communicate with endpoints.

Transfer operations create an asynchronous Transfer request, submit it and wait for it to complete. Waitings is implemented with Object.wait() and Object.notify(), with a timeout if needed. If a timeout occurs, the transfer is aborted.

Clone this wiki locally