Glen Turner (vk5tu) wrote,

Multiport RS-232/USB dongles and Linux udev

Multiport RS-232 to USB dongles are widely used in industrial control and can be an economic alternative to terminal servers in some particular situations.

This shows a typical product at the serious end of the market, with 16 RS-232 ports, industrial case, DIN mounting, AC power input. Products with 4 or 8 ports in a non-industrial case are much more economic. In general products with 4 ports take power from the USB port, which might be an issue if connecting to an embedded systems computer which cannot output full USB current (Raspberry Pi, etc).

Internally they are all the same: each RS-232 port is backed by a RS-232/USB UART, such as the FT232 family from Future Technology Devices International. The USB side of those devices is wired to a USB hub. The upstream port of that hub is presented on the chassis for connection to a computer.

Here is a four-port device in a plastic case:

$ lsusb
Bus 002 Device 006: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC
Bus 002 Device 005: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC
Bus 002 Device 004: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC
Bus 002 Device 003: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC
Bus 002 Device 002: ID 1a40:0101 Terminus Technology Inc. Hub

Linux will present the RS-232 ports as devices:

$ echo /dev/ttyUSB*
/dev/ttyUSB0 /dev/ttyUSB1 /dev/ttyUSB2 /dev/ttyUSB3

udev

Usually we have udev rules for these devices. Firstly we can stop Modem Manager from probing the ports. This is annoying when connected to a host but downright dangerous in industrial applications. Create a file /etc/udev/rules.d/99-local.rules and add:

ACTION!="add|change", GOTO="99-local-end"
# FTDI FT232 RS-232/USB devices
SUBSYSTEM=="usb", ATTR{idVendor}=="0403", ATTR{idProduct}=="6001", ENV{ID_MM_DEVICE_IGNORE}="1"
GOTO="99-local-end"

Secondly, we can add symlinks to the device. This allows user space applications to always use the same name for the ports. To understand the usefulness of this consider that some other device might be assigned /dev/ttyUSB0 (eg, plug in a 3G/USB modem) and that will increment the numbering on all the other /dev/ttyUSB[0-9]+ ports.

Let's see which attributes which can be used in udev rules to differentiate the ports:

$ sudo udevadm info --attribute-walk /dev/ttyUSB0 > w0.txt
$ sudo udevadm info --attribute-walk /dev/ttyUSB1 > w1.txt
$ sudo udevadm info --attribute-walk /dev/ttyUSB2 > w2.txt
$ sudo udevadm info --attribute-walk /dev/ttyUSB3 > w3.txt

Looking through the files we annoyingly see that the end of the devpath attribute looks like the best contender to identify each FTDI RS-232/USB UART after the USB hub:

w0.txt: ATTRS{devpath}=="1.1"
w1.txt: ATTRS{devpath}=="1.2"
w2.txt: ATTRS{devpath}=="1.3"
w3.txt: ATTRS{devpath}=="1.4"

Nothing a small program to return the characters after the dot can't fix:

#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;
}

This program simply manipulates text:

$ multiusbserial-id 1.1
ID_MULTIUSBSERIAL_DEVNAME_MINOR=1
$ multiusbserial-id 1.2
ID_MULTIUSBSERIAL_DEVNAME_MINOR=2
$ multiusbserial-id 1.3
ID_MULTIUSBSERIAL_DEVNAME_MINOR=3
$ multiusbserial-id 1.4
ID_MULTIUSBSERIAL_DEVNAME_MINOR=4

I could have done this in almost any programming language, I just used C because it doesn't have to establish a huge runtime environment prior to doing something trivial.

The program is used in a udev rule in /etc/udev/rules.d/99-local.rules 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"
# Switch console
ENV{ID_MULTIUSBSERIAL_DEVNAME_MINOR}=="2", GROUP="wheel", MODE="0660", SYMLINK+="ttyswitch"
# Router console
ENV{ID_MULTIUSBSERIAL_DEVNAME_MINOR}=="3", GROUP="wheel", MODE="0660", SYMLINK+="ttyrouter"
# Unused
ENV{ID_MULTIUSBSERIAL_DEVNAME_MINOR}=="4", GROUP="wheel", MODE="0660"

LABEL="99-local-end"

Make it take effect with sudo systemctl restart systemd-udev-trigger.

The first port contains a traditional public switched telephone network modem. In UNIX those devices are the target of a symlink named "/dev/modem". In Linux those devices are in the "dialout" group, and users permitted to use the modem are placed into that group with the command sudo usermod -a -G dialout $SUDO_USER.

The second and third ports are connected to "Console" ports on networking devices. Because they are part of the systems infrastructure I want to restrict the use of those ports to systems administrators; that is, to the "wheel" group. Notice how the names start with "tty", as some terminal emulation packages will check for that.

Minicom

Minicom is a popular terminal emulator. You can type minicom configuration_name to use a canned configuration. That gives a simple way to connect to the router and switch RS-232 consoles: simply type minicom router or minicom switch. /etc/minirc.router contains:

pu port             /dev/ttyrouter
pu baudrate         9600
pu bits             8
pu parity           N
pu stopbits         1
pu minit
pu mreset
pu mhangup

/etc/minirc.switch contains:

pu port             /dev/ttyswitch
pu baudrate         9600
pu bits             8
pu parity           N
pu stopbits         1
pu minit
pu mreset
pu mhangup

The PSTN modem has a simple configuration. Note that the default device used by minicom is /dev/modem. /etc/minirc.dfl contains:

pu minit            ~^M~AT S7=45 S0=0 L1 V1 X4 &c1 E1 Q0^M
pu mreset           ^M~ATZ^M~

If you are primarily doing console server tasks then look at conserver. It does port sharing well and has the huge advantage of being able to be configured to log input to a file when a user isn't at the console. That's very useful for recording that device crash.

Embedded systems development

Let's say port 4 of that 4-port RS-232/USB dongle was attached to the console of an embedded system. We want the developers to have access to that console. Firstly, create a UNIX group for the engineering staff using sudo groupadd -r eng. Secondly add the engineering staff to that group using sudo usermod -a -G eng . If you are using a directory system then you may need to use special commands specific to that system rather than the typical utilities. Finally use a udev rule to set the group and mode on the tty port connected to the embedded system's RS-232 console. At the same time we can set a symbolic link so that specific tty numbers don't get hardcoded into development scripts.

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}"
ENV{ID_MULTIUSBSERIAL_DEVNAME_MINOR}=="4", GROUP="eng", MODE="0660", SYMLINK+="ttyeng0"
LABEL="99-local-end"

Developers use "/dev/ttyeng0" in their utilities and scripts. They don't need to be a superuser to use the embedded system's RS-232 console, such as when using a development utility to download code to the system.

Systems administrators can use one multi-port RS-232/USB dongle to connect all of the prototypes to the development machine. Simply use udev to name them "/dev/ttyeng0", "/dev/ttyeng1", and so on; or use a scheme which matches the names of the prototype systems.

When writing development utilities use lockdev to take a lock on the /dev/ttyUSB… device. Lockdev is in the Fedora package "lockdev-devel" and the Debian package "liblockdev1-dev". Programs should use lockdev(3), scripts should say lockdev -l /dev/, check for a return code of 0, use the device, and then say lockdev -u /dev/. Lockdev will work correctly when handed a symlink to the /dev/ttyUSB… device.

Unfortunately the lockdev command won't tell you which mongrel left their session running and went to lunch. lockdev stores the process ID of the locking process in files it creates in /var/lock/lockdev (Fedora) or /var/lock (Debian).

Tags: linux
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 0 comments