Assigning unique device names for CP2102N serial port devices on FreeBSD

26 Jun 2023 - tsp
Last update 26 Jun 2023
Reading time 5 mins

The problem

So what’s the problem that is getting solved? Usually when one attaches an USB to serial (or RS485) adapter to ones computer it gets assigned a teletype interface (tty). On FreeBSD those are usually called /dev/cuaU for traditional callout ports, /dev/ttyU for callin ports. When accessing serial ports it does not matter which of those interfaces are used, the distinction is required for modem style (teletype) devices. The assignment of the device numbers usually occurs in sequence. Whenever one changes the order of USB devices on an hub or host controller or they are attached or detached with different timing the device numbering changes. This is not a huge problem when trying to use those devices on demand manually since one can see which device got attached or not. This is a problem when accessing devices programmatically even over system reboots - or when accessing them even over system changes. When using teletype devices also for communication (for example for UMTS/LTE interfaces) one can even force a system offline when devices get renumbered and loose all connectivity to remote devices.

The solution

The solution is simple - one wants to have the same device filename for the same device every time. Unfortunately there is no way to access the devices by their physical port or assign a fixed name in the form of /dev/ttyU or /dev/cuaU to devices. Those will change over time and hardware configuration changes. On the other hand devices can usually be distinguished by their serial number. Each USB device should have a unique serial number (note that some devices like some ZTE USB modems as well as CH340 USB to serial converters do not have a unique serial number so this is still not possible as described here). The idea is to create a symbolic link to the cuaU device based on the serial number of the device using a simple devd configuration.

A very simple method for CP2102N based devices looks like the following in /etc/devd.conf

attach 1000 {
        match "vendor"          "0x10c4";
        match "product"         "0xea60";
        action                  "ln -sf /dev/cua$ttyname /dev/cp2102n_$sernum && ln -sf /dev/cua$ttyname.lock /dev/cp2102n_$sernum.lock && ln -sf /dev/cua$ttyname.init /dev/cp2102n_$sernum.init";
};
        
notify 1000 {
        match "vendor"          "0x10c4";
        match "product"         "0xea60";
        match "type"            "DETACH";
        match "subsystem"       "DEVICE";
        action                  "rm /dev/cp2102n_$sernum && rm /dev/cp2102n_$sernum.init && rm /dev/cp2102n_$sernum.lock";
};

The idea of those scripts is very simple. Whenever a device with vendor ID 0x10c4 and product 0xea60 - which is the default for CP2102N devices - is attached to the system - after most other rules have been executed (this is realized by the large rule number) the attach rule simply generates symbolic links to the cuaU, the lock and init device. Those symbolic links simply include the serial number of the device. It’s assumed that all devices have been configured either with Simplcity Studio to a custom but unique (at least for the setup) serial or have been configured to use the internal unique serial for CP2102N devices. Note that it’s not possible to create directories using devd.

The notify rule is used to delete the symbolic links whenever a device of the same family is detached again.

This approach works also for other vendor and device IDs of course. As a result the script above generates links to device files like the following:

/dev/cp2102n_1aa2d3624a83ed118437ae5f9d1cc348
/dev/cp2102n_1aa2d3624a83ed118437ae5f9d1cc348.lock
/dev/cp2102n_1aa2d3624a83ed118437ae5f9d1cc348.init

A small problem with ZTE modems

I tried to use the same approach with ZTE modems. The main problem here was that they exposed multiple functions as tty devices - each of them having the same $ttyname. Unfortunately I was not able to identify an environment variable for each of the functions to be used in the attach rule. Since I had not much time to do further investigation so I decided to solve the problem with a dirty hack. This works since I knew that the ZTE USB modem I used on my embedded systems expose three interfaces - and I’ve attached only one device with the same vendor and device ID per system.

The quickest hacked approach that came to my mind was to write two shell scripts that create symbolic links for all four devices exposed under a given $ttyname base name. This script is called makemodemlinks.sh

#!/bin/sh

CANDIDATES=`ls /dev/cua${1}*`

for cand in ${CANDIDATES}; do
	ismodem=0

	case ${cand} in
		"/dev/cuaU"?"."?"."*) ismodem=1;;
		"/dev/cuaU"?"."?) ismodem=2;;
		*) ismodem=0;;
	esac

	if [ ${ismodem} -eq 1 ]; then
		idpart=`echo ${cand} | tail -c 8`
		aliaspath="/dev/usbModem${idpart}"
		if [ ! -e ${aliaspath} ]; then
			ln -sf ${cand} ${aliaspath}
			chgrp dialer ${aliaspath}
			chgrp -h dialer ${aliaspath}
		fi
	fi
	if [ ${ismodem} -eq 2 ]; then
		idpart=`echo ${cand} | tail -c 3`
		aliaspath="/dev/usbModem${idpart}"
		if [ ! -e ${aliaspath} ]; then
			ln -sf ${cand} ${aliaspath}
			chgrp dialer ${aliaspath}
			chgrp -h dialer ${aliaspath}
		fi
	fi
done

The $ttyname will be passed as first argument. The script then locates all device files with the pattern /dev/cuaUX.Y as well as /dev/cuaUX.Y.lock and /dev/cuaUX.Y.init. All files are then linked under the basename /dev/usbModem. The major number X is stripped so only one device can be attached using this script. In addition a deletion script rmmodemlinks.sh is used:

#!/bin/sh

CANDIDATES=`ls /dev/usbModem.*`

for cand in ${CANDIDATES}; do
	rm ${cand}
done

I simply call those scripts from devd.conf whenever the ZTE modem is attached or detached:

attach 1000 {
	match "vendor"		"0x19d2";
	match "product"		"0x0031";
	action 			"/root/makemodemlinks.sh $ttyname";
};

notify 1000 {
	match "vendor"		"0x19d2";
	match "product"		"0x0031";
	match "type"		"DETACH";
	match "subsystem"	"DEVICE";
	action			"/root/rmmodemlinks.sh $ttyname";
};

There is for sure a way better way to handle USB modems in this case - the major problem having been the ZTE modems not supplying a unique serial number. But for the acute problem the scripts had been sufficient.

This article is tagged:


Data protection policy

Dipl.-Ing. Thomas Spielauer, Wien (webcomplains389t48957@tspi.at)

This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/

Valid HTML 4.01 Strict Powered by FreeBSD IPv6 support