2 releases
0.0.3 | Nov 27, 2023 |
---|---|
0.0.2 | Nov 27, 2023 |
0.0.1 |
|
#1398 in Hardware support
125KB
2K
SLoC
Future Technology Devices International (FTDI) produces the FT60X series of chips (e.g. FT601), which act as Super Speed USB 3.0 to FIFO bridges. FTDI provides a proprietary driver for these chips, called D3XX, which exposes a low-level API for interacting with the devices through its DLL/shared library.
This crate provides a safe, idiomatic Rust wrapper around FTDI's D3XX library.
Disclaimer
This crate is unofficial and is not affiliated with FTDI in any way.
The crate is still in early development and is unstable/experimental. Feedback and contributions are welcome!
What This Crate Does
This crate provides near-complete functionality of the D3XX library:
- Device enumeration
- Reading device configurations
- Pipe I/O
- GPIO control
- Notifications
- Overlapped (Asynchronous) I/O
This crate does not wrap functionality for configuring the device. If it is deemed necessary, the unsafe FFI functions may be called directly. However, it is recommended to use the FT60X Chip Configuration Programmer instead for this purpose.
Requirements
This crate is available for Linux, Windows, and macOS. Although the crate may be build without it, the D3XX driver must be installed for the target platform in order to communicate with devices.
Building this crate requires Clang to be installed.
Background
USB peripherals contain a series of numbered endpoints, which are essentially physical data buffers. Each endpoint may contain one or two buffers, corresponding to the direction of data flow (IN or OUT). D3XX devices have 8 endpoints, 4 each for IN and OUT transfers. The software representation of an endpoint is known as a "pipe" and is unidirectional.
Endpoints are collected into "interfaces", which are logical groupings of endpoints that serve a common purpose. These interfaces are then collected into "configurations", which are collections of interfaces that represent a complete set of functionality for the device. A device may have multiple configurations, but only one may be active at a time. D3XX offers a means of communicating with devices, primarily through the software equivalent of endpoints known as "pipes."
D3XX Constraints
The D3XX API does not provide many guarantees about the behavior of the driver. For example, there are no guarantees about what happens when a device is unplugged during a transfer, or whether any functions are thread-safe. Because many aspects of the D3XX API are not explicitly defined or documented, this crate intentionally puts in place additional restrictions and assumptions to ensure it is safe to use. The two main assumptions with the greatest consequence on the design of this crate are:
- The driver is not thread-safe.
- Any error can occur at any time for any reason.
Because of the lack of a clear standard, it is not recommended to use this crate in safety-critical applications. Any use of this crate in such applications is at your own risk.
Error Handling
The documentation on most functions in this crate returning a Result<T, D3xxError>
does not include an
explanation about the error conditions. This is because in most cases the D3XX documentation
does not provide any information about what errors can occur and under what circumstances.
Because there is no specification for errors, it is not wise to attempt to handle specific
errors in a systematic manner, as the error conditions may change in future versions of the
D3XX API. Instead, it is recommended to use a catch-all approach in most cases.
Global Lock
One of the consequences of the assumption regarding thread-safety is that some operations must be performed while holding a lock on the driver. For example, listing devices must be done with the lock held since the operation consists of a write followed by a read of the driver's device table, which may by invalidated at any point by another thread.
The operations which acquire the lock do so transparently by calling
with_global_lock
. This function is also available for use
by the user if access to the bindings are needed. Care should be taken to avoid deadlocks when
using this function.
Further Reading
It is recommended to read the D3XX Programmers Guide for more information about the capabilities provided by the D3XX API. Some information missing from the D3XX Programmers Guide can be found in the D3XX .NET Programmers Guide instead.
Simple Example
use std::io::{Read, Write};
use d3xx::{list_devices, Pipe};
// Scan for connected devices.
let all_devices = list_devices().expect("failed to list devices");
/// Open the first device found.
let device = all_devices[0].open().expect("failed to open device");
// Read 1024 bytes from input pipe 1
let mut buf = vec![0; 1024];
device
.pipe(Pipe::In1)
.read(&mut buf)
.expect("failed to read from pipe");
// Write 1024 bytes to output pipe 2
device
.pipe(Pipe::Out2)
.write(&buf)
.expect("failed to write to pipe");
Dependencies
~38MB
~34K SLoC