-
Notifications
You must be signed in to change notification settings - Fork 11
Architecture
The overall class structure is shown in the diagram below. The Usb
class creates a singleton instance of UsbDeviceRegistry
. The device registry implementations are responsible for enumerating the USB devices, monitoring connected and disconnected devices and creating new UsbDevice
instances.
For each supported operating system, a subclass of UsbDeviceRegistry
exists. They contain operating system specific code and can only be created on the appropriate operating system as they bind to native functions. The UsbDeviceRegistry
class contains the code common to the three registry implementations.
The device registry instance creates instances of UsbDevice
. They will create instances of operating system specific class of the same operating system. UsbDeviceImpl
contains the code common to the three USB device implementations. UsbDevice
is the public interface exposed to the library users.
The single UsbDeviceRegistry
instance manages a global list of all connected USB devices. It initially enumerates all connected devices. It then monitors devices being connected and disconnected and updates the list accordingly.
All the work is done in a background thread. After the initial enumeration, the background thread is mainly waiting for the next event, triggered by connecting or disconnecting a USB device. The waiting mechanism is operating system specific. The background thread also calls the registered notification handlers.
During the initial enumeration, the triggering code has to wait until the background process has started and completed the enumeration. To synchronize these activities, a Condition
is used.
Most of the registry code is in operating system specific subclasses as it mainly uses native APIs, and they considerably differ between operating systems.
The implementations of UsbDevice
are mostly operating system specific as well. Most code can be found in the specific subclasses. The intermediate class UsbDeviceImpl
contains the common code, mainly related to descriptive information about the devices and its interfaces and endpoints.
At the core, all transfers from and to endpoints are executed asynchronously. The EndpointInputStream
and EndpointOutputStream
returned by UsbDevice.openInputStream()
and UsbDevice.openOutputStream()
use multiple concurrent asynchronous transfers to achieve a high throughput. Blocking methods like UsbDevice.transferIn()
and UsbDevice.transferOut()
are blocking wrappers around the asynchronous transfers. Currently, no API is offered to use the asynchronous functions directly.
For the implementation of asynchronous transfers, a background task is used to handle the IO completion. Each operating system has a separate implementation without shared code and without a common superclass.
When an asynchronous operation completes, a completion handler provided as part of the submitted request will be called. It is called from the background thread handling the transfer completion. It is up to the client to ensure that the completion handler only does minimal work as it would otherwise delay other asynchronous operations. Furthermore, it will need to handle it in multi-threading safe way. In EndpointInputStream
and EndpointOutputStream
, the completion handler just adds the completed transfer to a BlockingArrayQueue
instance.