#usb-device #ftdi #usb #api-bindings #ft60x

d3xx

Rust bindings for the FTDI D3XX library

2 releases

0.0.3 Nov 27, 2023
0.0.2 Nov 27, 2023
0.0.1 Nov 25, 2023

#1692 in Hardware support

MIT license

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:

  1. The driver is not thread-safe.
  2. 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
~33K SLoC