Assigning unique device names for CP2102N serial port devices on FreeBSD
26 Jun 2023 - tsp
Last update 26 Jun 2023
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: