2 August 2020

# FTDI and USB device ID

by Mike Krinkin

In a few of the previous articles I was writing a driver for Nintendo Wiichuk. It was an I2C input device and the driver was tested on BeagleBone Black Wireless. However there weren’t a lot of opportunities for me to use a joystick on the board, so the experience is somewhat incomplete.

The driver for Nintendo Wiichuk is not really architecture specific, so I figured that if I was able to connect the joystick to my laptop it would be cool to try it in a game on my laptop. The problem is that I don’t have any exposed I2C connectors, so I want to try to create an USB to I2C adapter. And this article is the first step.

# Hardware

First of all we need hardware capabale of converting USB commands into I2C data transfers. Frankly speaking I didn’t do a good job searching for a USB to I2C bridge in the first place and that what started this effort.

The device I managed to find is an FTDI MPSSE cable that is theoretically capable of serving as an USB to I2C bridge. The FTDI chips are often used for USB to UART conversion in various hobby projects, however with a different configuration and a bunch of additional excercises they should be able to communicate with I2C devices as well.

NOTE: ultimately the decision to use an FTDI based cable was not economically great one, as with the delivery and taxes it costed me almost 70 pounds. Half of that cost was shipping for a tiny package.

# USB VID and PID

According to the datasheet the device uses USB Vendor ID 0x0403 and USB Product ID 0x6014. USB Vendor and Product ID are something of a unique device id. This identifier is used to figure out what driver should manage a particular device or even what kernel module to load once the system detected a new device connected to the system.

If you connect an FTDI MPSSE cable to your Linux system you may notice that the system automatically loads ftdi_sio module. It works because all USB devices in addition to the device-specific functions they provide also conform to a generic interface. Among other things the generic interface allows to detect when a new device was connected to the USB bus and obtain some basic information about the device (like VID and PID).

That means that you don’t need a special driver for the device to detect that a device was connected to the USB bus or figure out what device was connected. Once the system knows that information it can figure out what driver can manage the device looking at the VID:PID that drivers support.

As you probably could have guessed from the name, USB Vendor ID identifies a manufacturer and the USB Product ID is used to identify a particular kind of device that that vendor produces.

USB Vendor IDs are assigned by the central authoritiy: USB-IF. Getting USB Vendor ID costs quite a price for an individual developer, but it’s probably not that much for an organization capable of manufacturing devices.

In my experiments I’m going to just pick 0x0005:0x0001 as a VID:PID pair. As far as I can tell this particular USB Vendor ID is not used yet and that’s enough for experiments for now. However in order to do that we would need to override the original FTDI VID:PID. Fortunately enough the FTDI cable I have allows to do that.

Once we override the device id the ftdi_sio driver will not be loaded automatically when we connect the device. That’s exactly what we want, since we will be working on a different driver for our FTDI device.

# FTDI drivers

FTDI distributes a library as well as C headers that can communicate with the FTDI based devices from the user space. We will use this library to override VID:PID for the cable. You can download the driver from the ftdichip.com and they even have a video that shows how you can install the driver into your system. However I will cover it here as well:

cd ~/ws
mkdir libftd2xx
cd libftd2xx
wget https://www.ftdichip.com/Drivers/D2XX/Linux/libftd2xx-x86_64-1.4.8.gz
tar -xvf libftd2xx-x86_64-1.4.8.gz
sudo cp release/build/libftd2xx.a /usr/local/lib
sudo cp release/build/libftd2xx.so.1.4.8 /usl/local/lib
sudo ln -s /usr/loca/lib/libftd2xx.so.1.4.8 /usr/local/lib/libftd2xx.so
sudo chmod 0644 /usr/loca/lib/libftd2xx.so.1.4.8
ldconfig


NOTE: /usr/local/lib works for libraries in Ubuntu, but in Red Hat based repositories /usr/local/lib is not a directoy used by default for libraries and as a result building binaries there might require additional effort to pick up libraries from /usr/local/lib.

Besides the libraries themselves we also need header files with declarations of function and relevenat data structructures that we’d need to use the library:

cd ~/ws
mkdir -p bootlin/ftdi/usr
cp ~/ws/libftd2xx/release/ftd2xx.h bootlin/ftdi/usr
cp ~/ws/libftd2xx/release/WinTypes.h bootlin/ftdi/usr
chmod -x bootlin/ftdi/usr/ftd2xx.h
chmod -x bootlin/ftdi/usr/WinTypes.h


NOTE: ~/ws/bootlin is the place where I stored the I2C driver for the Nintendo Wiichuk device, so I decided to use it for the USB to I2C driver related things. ~/ws/bootlin/ftdi/usr would be the directory for all the userspace tools.

NOTE: I’m not sure why but ftd2xx.h and WinTypes.h files had execute bit set and they should not be executable, so I removed the execute bit for them along the way.

# Overriding VID and PID

Now when we have the FTDI libraries installed we’ll start by writing a tool to enumerate the connected FTDI devices. We will use this tool to find serial numbers of the connected devices. The FTDI library allows to communicate with the device based on its serial number, so this tool will come in handy later to find the serial number of our device.

The full list of available functions can be found in the D2XX Programmer’s Guide that can be obtained from ftdichip.com.

Apparently there are multiple ways to use this API to obtain device serial number, I’m going to use FT_ListDevices function. The first step would be to figure out how many FTDI based devices are connected to the system:

#include "ftd2xx.h"

static int devices(int *devices)
{
FT_STATUS status;
DWORD n;

status = FT_ListDevices(&n, NULL, FT_LIST_NUMBER_ONLY);
if (!FT_SUCCESS(status))
return -1;
*devices = n;
return 0;
}


NOTE: the FTDI library uses a somewhat questionable interface. They overuse void pointers instead of proper types. The meaning of void pointers depends on the combination of flags passed to the function. So it’s a mess.

This function will tell us how many FTDI devices are connected to the system. Later we can get information about a particular device based on the index of the device like this:

struct serial {
char serial[16];
};

static int device_serial(int index, struct serial *serial)
{
FT_STATUS status;

status = FT_ListDevices(
(void *)((unsigned long)index),
serial->serial,
FT_LIST_BY_INDEX | FT_OPEN_BY_SERIAL_NUMBER);
if (!FT_SUCCESS(status))
return -1;
return 0;
}


The rest of the logic boils down to just enumerating serial numbers of all the devices connected to the system.

NOTE: before running this tool make sure to remove the ftdi_sio and usbserial modules using rmmod command, so that they don’t interfere with the tools.

Running this tool I figured that the serial number for the FTDI cable I have was FT3ELPXS.

Actually we didn’t need to create this tool, there is already a generic tool available that allows to get the serial number of connected USB devices:

sudo lsusb -v


In the output of the lsusb command you could find something like this for the connected FTDI devices:

Bus 001 Device 019: ID 0005:0001
Device Descriptor:
bLength                18
bDescriptorType         1
bcdUSB               2.00
bDeviceClass            0 (Defined at Interface level)
bDeviceSubClass         0
bDeviceProtocol         0
bMaxPacketSize0        64
idVendor           0x0005
idProduct          0x0001
bcdDevice            9.00
iManufacturer           1 FTDI
iProduct                2 C232HM-DDHSL-0
iSerial                 3 FT3ELPXS
bNumConfigurations      1


One way or another we now know the serial number of the FTDI cable. The next step would be to override the VID:PID. The high level logic of changing the VID:PID is simple:

1. Open the device (using FT_OpenEx function)
2. Write VID:PID to the device EEPROM (using FT_EE_Program function)
3. Close the device (using FT_Close function).

static int open_device(const char *serial, FT_HANDLE *handle)
{
FT_STATUS status;

status = FT_OpenEx((void *)serial, FT_OPEN_BY_SERIAL_NUMBER, handle);
if (!FT_SUCCESS(status))
return -1;
return 0;
}

static int close_device(FT_HANDLE handle)
{
FT_STATUS status;

status = FT_Close(handle);
if (!FT_SUCCESS(status))
return -1;
return 0;
}


In the code above FT_HANDLE represents a handle of the open device (similar to FILE pointer for files in C for example). The library will find the right device by the serial number specified via the null-terminated string.

The device’s EEPROM allows to store a fixed structure that includes various things like manufacturer, serial number and VID:PID. However it also has some free space if we want to use it for something that we want to store on the device, though we don’t need it right now.

The interface to read and write the fixed part of the EEPROM uses data structure called FT_PROGRAM_DATA. What we want to do is to read the current FT_PROGRAM_DATA, update VID and PID and then write it back. To write the data back we can use FT_EE_Program mentioned above, to read the current content of EEPROM FT_EE_Read function can be used:

static int set_device_id(FT_HANDLE handle, unsigned vid, unsigned pid)
{
FT_STATUS status;
FT_PROGRAM_DATA data;
char m[32];
char mid[16];
char desc[64];
char serial[16];

data.Signature1 = 0x0;
data.Signature2 = 0xffffffff;
data.Version = 0x5;
data.Manufacturer = m;
data.ManufacturerId = mid;
data.Description = desc;
data.SerialNumber = serial;

if (!FT_SUCCESS(status))
return -1;

data.VendorId = vid;
data.ProductId = pid;

status = FT_EE_Program(handle, &data);
if (!FT_SUCCESS(status))
return -1;
return 0;
}


Combining the functions together we can override the VID:PID for the device. After that you should unplug and plug the device to see the effects. If everything is done correctly you should see a few effects:

1. lsubs should show the new VID:PID
2. ftdi_sio and usbserial will not be loaded automatically when you plug in the device.

# USB Linux Driver

Now we have a device that the kernel does not recognize since it has VID:PID, that the kernel does not know. So we can teach the kernel to recongize the device by creating a USB driver.

The main structure for a USB driver in Linux is struct usb_driver. There are four fields in that structure that we need to specify:

• name
• probe function
• disconnect function
• id_table.

name should be unique among the USB drivers, but other than that it does not have any affect on the operation of the driver. probe and disconnect functions are called when the device is connected (or detect if we loaded the module after the device was connected) and disconnected.

We aren’t going to implement any hadrware management logic in the driver just yet. So the most important part of the usb_driver structure for us is id_table field.

id_table is an array of struct usb_device_id. usb_device_id structure is served as a filter of a sort that allows to tell what kind of devices the driver manages. One specific way we can initialize this structure is to specify VID:PID of the device the driver manages.

Let’s take a look at the code. The driver will not manage the hardware in any way and will just print a message in the log when a device is connected or disconnected:

// SPDX-License-Identifier: GPL-2.0
#include <linux/init.h>
#include <linux/module.h>
#include <linux/usb.h>

#define VENDOR_ID  0x0005
#define PRODUCT_ID 0x0001

static const struct usb_device_id ftdi_id_table[] = {
{ USB_DEVICE(VENDOR_ID, PRODUCT_ID) },
{ }
};
MODULE_DEVICE_TABLE(usb, ftdi_id_table);

static int ftdi_usb_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
(void) interface;
(void) id;
pr_alert("Our FTDI-based device has been connected\n");
return 0;
}

static void ftdi_usb_disconnect(struct usb_interface *interface)
{
(void) interface;
pr_alert("Our FTDI-based device has been disconnected\n");
}

static struct usb_driver ftdi_usb_driver = {
.name = "ftdi_usb",
.probe = ftdi_usb_probe,
.disconnect = ftdi_usb_disconnect,
.id_table = ftdi_id_table,
};

module_usb_driver(ftdi_usb_driver);
MODULE_DESCRIPTION("FTDI USB-to-serial based something");
MODULE_AUTHOR("Krinkin Mike <krinkin.m.u@gmail.com>");


With this module installed in the system you can plug and unplug the device. If everything is fine you should be able to see some log messages in the dmesg command output:

[213829.249938] ftdi: loading out-of-tree module taints kernel.
[213829.250108] ftdi: module verification failed: signature and/or required key missing - tainting kernel
[213829.437515] Our FTDI-based device has been connected
[213829.437649] usbcore: registered new interface driver ftdi_usb
[213835.762120] usb 1-1.4: USB disconnect, device number 15
[213835.762254] Our FTDI-based device has been disconnected
[213841.600851] usb 1-1.4: new high-speed USB device number 16 using xhci_hcd
[213841.729589] usb 1-1.4: New USB device found, idVendor=0005, idProduct=0001
[213841.729595] usb 1-1.4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[213841.729598] usb 1-1.4: Product: C232HM-DDHSL-0
[213841.729601] usb 1-1.4: Manufacturer: FTDI
[213841.729604] usb 1-1.4: SerialNumber: FT3ELPXS
[213841.732887] Our FTDI-based device has been connected
[213846.514281] usb 1-1.4: USB disconnect, device number 16
[213846.514409] Our FTDI-based device has been disconnected
[214085.963952] usb 1-1: USB disconnect, device number 13
[214085.963958] usb 1-1.1: USB disconnect, device number 14
[214085.993207] usb 2-1: USB disconnect, device number 4