How to distinguish between identical USB-to-serial adapters?

is there any way I can write a udev rule that fixes the name of each adapter based on which physical port on the hub the adapter is plugged into?

Yes there is, as it turns out. Consider the last portion of the device hierarchy shown in the second example above:

looking at parent device '/devices/pci0000:00/0000:00:1d.7/usb1/1-4/1-4.5': KERNELS=="1-4.5"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{configuration}==""
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
ATTRS{bmAttributes}=="80"
ATTRS{bMaxPower}=="100mA"
ATTRS{urbnum}=="69"
ATTRS{idVendor}=="067b"
ATTRS{idProduct}=="2303"
ATTRS{bcdDevice}=="0300"
ATTRS{bDeviceClass}=="00"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bDeviceProtocol}=="00"
ATTRS{bNumConfigurations}=="1"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{speed}=="12"
ATTRS{busnum}=="1"
ATTRS{devnum}=="7" ATTRS{version}==" 1.10" ATTRS{maxchild}=="0" ATTRS{quirks}=="0x0"
ATTRS{authorized}=="1"
ATTRS{manufacturer}=="Prolific Technology Inc."
ATTRS{product}=="USB-Serial Controller"

The name given to this device by the kernel (KERNELS=="1-4.5") indicates that this device is plugged into the fifth port of a hub connected to port four on bus 1 (see this FAQ for more information on how to decode the sysfs usb device hierarchy). With some help from this guide to writing udev rules I came up with the following set of udev rules for my USB-to-serial-port converters:

KERNEL=="ttyUSB*", KERNELS=="1-8.1.5", NAME="ttyUSB0"
KERNEL=="ttyUSB*", KERNELS=="1-8.1.6", NAME="ttyUSB1"
KERNEL=="ttyUSB*", KERNELS=="1-8.1.1", NAME="ttyUSB2"
KERNEL=="ttyUSB*", KERNELS=="1-8.1.2", NAME="ttyUSB3"

These rules have one obvious disadvantage: they assume that all USB-to-serial converters will be plugged into the same hub ("1-8.1.*"). If a USB to serial converter was plugged into another USB port it could be assigned the name "ttyUSB0" which would conflict with the naming scheme described above. However, since I leave all of the converters plugged into the hub I can live with this constraint.


Although it would not help in this specific case, some adapters are assigned unique serial ids:

udevadm info -a -n /dev/ttyUSB1 | grep '{serial}'

An example adapter serial id:

  ATTRS{serial}=="A6008isP"`

and udev rules would then contain:

SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="A6008isP", SYMLINK+="arduino"

Source


Have you looked at the contents of /dev/serial/by-id/? In a similar situation each device was assigned a unique persistent ID there (I'll admit don't know what it actually represents).


Since the original question was asked 3 years ago, this might not adress the asker, but I will post it for future reference.

There is a way to reprogramm the Serial-Number by accessing the EEPROM of the FTDI-Chips, Silicon labs provides a tool, but it is Windows only:

Product page->Tools->Fixed Function Customization Utility

Direct link

An instruction can be found at remotehq:

http://remoteqth.com/wiki/index.php?page=How+to+set+usb+device+SerialNumber

There is also a Unix library on Sourceforge. It is only tested with CP2101 / CP2102 / CP2103 and I did not try it out personally.

http://sourceforge.net/projects/cp210x-program/


Using an answer rather than a comment as I need formatting.

These rules have one obvious disadvantage: they assume that all USB-to-serial converters will be plugged into the same hub ("1-8.1.*"). If a USB to serial converter was plugged into another USB port it could be assigned the name "ttyUSB0" which would conflict with the naming scheme described above. However, since I leave all of the converters plugged into the hub I can live with this constraint.

I had this issue and it's easily fixed by using a small C program to manipulate the text of %devpath or some other USB attribute of your choosing.

You then call that program like this:

ACTION!="add|change", GOTO="99-local-end

SUBSYSTEM=="usb", ATTR{idVendor}=="0403", ATTR{idProduct}=="6001", ENV{ID_MM_DEVICE_IGNORE}="1"
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", GOTO="99-local-tty-ftdi"
GOTO="99-local-end"

LABEL="99-local-tty-ftdi"
IMPORT{program}="/usr/local/lib/udev/multiusbserial-id %s{devpath}"
# Hayes-style Modem
ENV{ID_MULTIUSBSERIAL_DEVNAME_MINOR}=="1", GROUP="dialout", MODE="0660", SYMLINK+="modem"
# Console for network device
ENV{ID_MULTIUSBSERIAL_DEVNAME_MINOR}=="2", GROUP="wheel", MODE="0660", SYMLINK+="ttyswitch"
# Serial port for software development
ENV{ID_MULTIUSBSERIAL_DEVNAME_MINOR}=="3", GROUP="eng", MODE="0660", SYMLINK+="ttyrouter"
# Unused
ENV{ID_MULTIUSBSERIAL_DEVNAME_MINOR}=="4", GROUP="wheel", MODE="0660"

LABEL="99-local-end"

where multiusbserial-id is the compiled C program.

The program just needs to print text after a particular point, so it isn't complex

/* multiusbserial.c */
#include <stdio.h>
#include <stdlib.h>

#define PROGRAM_NAME "multiusbserial-id"
#define VARIABLE_PREFIX "ID_MULTIUSBSERIAL_"

int main(int argc, char *argv[])
{
  char *p;
  int found = 0;

  if (argc != 2) {
    fprintf(stderr, "Usage: " PROGRAM_NAME " ATTRS{devpath}\n");
    exit(1);
  }

  for (p = argv[1]; *p != '\0'; p++) {
    if (*p == '.') {
      p++;
      found = (*p != '\0');
      break;
    }
  }

  if (!found) {
    fprintf(stderr, PROGRAM_NAME ": unexpected format\n");
    exit(1);
  }

  printf(VARIABLE_PREFIX "DEVNAME_MINOR=%s\n", p);
  return 0;
}

I wrote a blog article with more details. It's one of a series in setting up an embedded systems team programming environment.