How to run agetty on USB serial

Now RedHat have documented a process for spinning up agetty on serial ports with custom parameters: How to configure serial getty with systemd - Red Hat Customer Portal

Basically,

# cp /usr/lib/systemd/system/serial-getty@.service /etc/systemd/system/serial-getty@ttyS0.service

Then edit the file and modify the agetty line:

[Service]
ExecStart=-/sbin/agetty --keep-baud 115200,38400,9600 %I $TERM    <-- Change this parameter
Type=idle

Finally

# systemctl enable serial-getty@ttyS0.service
# systemctl start serial-getty@ttyS0.service

This works fine if the serial port has a fixed name.

But with USB serial ports (/dev/ttyUSB*) the name can change depending on what is plugged into the machine at boot time. Today it might be ttyUSB0, but tomorrow it could be ttyUSB2.

With CentOS6 I used the by-id path to ensure it was always discovered.

  agetty -h -L -w /dev/serial/by-id/usb-utek_USB__-__Serial_Cable_FTVW64JR-if01-port0 9600 vt102

The “template” file from RedHat won’t work this way, though, because it has

After=dev-%i.device

as part of the unit definition. Looking at the output of systemctl -a | grep dev-tty these entries are all based on the ttyUSB name.

So how can I create a service defintion for agetty based on the by-id pathname?

I can’t think of a clear way of doing this in systemd units itself, but what you can do is try to force udev to keep a constant name. So for example, I’ll find my current serial adapter, get some ID information, and make a udev rule for it.

[label@sani ~]$ lsusb
Bus 002 Device 004: ID 0557:2410 ATEN International Co., Ltd 4-Port USB 3.0 Hub
Bus 002 Device 002: ID 174c:55aa ASMedia Technology Inc. ASM1051E SATA 6Gb/s bridge, ASM1053E SATA 6Gb/s bridge, ASM1153 SATA 3Gb/s bridge, ASM1153E SATA 6Gb/s bridge
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 009: ID 067b:2303 Prolific Technology, Inc. PL2303 Serial Port / Mobile Action MA-8910P
Bus 001 Device 007: ID 0557:2200 ATEN International Co., Ltd ATEN CS1942DP_1944DP
Bus 001 Device 006: ID 0557:8021 ATEN International Co., Ltd Hub
Bus 001 Device 005: ID 046d:08e5 Logitech, Inc. HD Pro Webcam C920
Bus 001 Device 004: ID 14ed:1012 Shure Inc. Shure MV7
Bus 001 Device 003: ID 0557:5411 ATEN International Co., Ltd 4-Port USB 2.0 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
[label@sani ~]$ ls /dev/ttyUSB*
/dev/ttyUSB0
[label@sani ~]$ udevadm info -a -n /dev/ttyUSB0 | egrep 'idVendor|idProduct|serial'
    SUBSYSTEMS=="usb-serial"
    ATTRS{idProduct}=="2303"
    ATTRS{idVendor}=="067b"
    ATTRS{idProduct}=="0002"
    ATTRS{idVendor}=="1d6b"
    ATTRS{serial}=="0000:00:14.0"

# So now I can see that my vendor and product match from my lsbusb output, I'll make a udev rule with that information
[label@sani ~]$ sudo rvim /etc/udev/rules.d/99-usb-serial.rules
SUBSYSTEM=="tty", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", ATTRS{serial}=="0000:00:14.0", SYMLINK+="ttyUSB0"

[label@sani ~]$ sudo udevadm control --reload-rules
[label@sani ~]$ sudo udevadm trigger

And see if that helps keeping your device name the same at all times. You could always name it ttyUSBSerial also, so that it’s consistent that way too.

Huh, that’s an interesting work around to a systemd limitation (which is an odd limitation 'cos I thought the whole point of systemd was to handle dynamic stuff! Never mind…)

Now I don’t get the same output as you 'cos -a follows the chain of parents and produces a lot more information, but I think it’s leading me down a possible path. In my case I have two 2-port FTDI adapters, so a combination of vendorID, modelID, serial and interface might work…

… and now I have no idea why this isn’t working!

% cat /etc/udev/rules.d/90-usbserial.rules 
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", ATTRS{serial}=="FTVW64JR", ATTRS{bInterfaceNumber}=="00", SYMLINK+="ttyTubeHost"
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", ATTRS{serial}=="FTVW64JR", ATTRS{bInterfaceNumber}=="01", SYMLINK+="ttyBBC"
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", ATTRS{serial}=="FTWKU6FP", ATTRS{bInterfaceNumber}=="01", SYMLINK+="ttyRouter"

This is the output for what I’d expect to be remapped as ttyBBC.

% udevadm info -a /dev/serial/by-id/usb-utek_USB__-__Serial_Cable_FTVW64JR-if01-port0

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

  looking at device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.5/1-1.5.1/1-1.5.1:1.1/ttyUSB1/tty/ttyUSB1':
    KERNEL=="ttyUSB1"
    SUBSYSTEM=="tty"
    DRIVER==""

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.5/1-1.5.1/1-1.5.1:1.1/ttyUSB1':
    KERNELS=="ttyUSB1"
    SUBSYSTEMS=="usb-serial"
    DRIVERS=="ftdi_sio"
    ATTRS{latency_timer}=="16"
    ATTRS{port_number}=="0"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.5/1-1.5.1/1-1.5.1:1.1':
    KERNELS=="1-1.5.1:1.1"
    SUBSYSTEMS=="usb"
    DRIVERS=="ftdi_sio"
    ATTRS{authorized}=="1"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bInterfaceClass}=="ff"
    ATTRS{bInterfaceNumber}=="01"
    ATTRS{bInterfaceProtocol}=="ff"
    ATTRS{bInterfaceSubClass}=="ff"
    ATTRS{bNumEndpoints}=="02"
    ATTRS{interface}=="USB <-> Serial Cable"
    ATTRS{supports_autosuspend}=="1"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.5/1-1.5.1':
    KERNELS=="1-1.5.1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{authorized}=="1"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bDeviceClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bMaxPacketSize0}=="8"
    ATTRS{bMaxPower}=="100mA"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bNumInterfaces}==" 2"
    ATTRS{bcdDevice}=="0500"
    ATTRS{bmAttributes}=="80"
    ATTRS{busnum}=="1"
    ATTRS{configuration}==""
    ATTRS{devnum}=="4"
    ATTRS{devpath}=="1.5.1"
    ATTRS{idProduct}=="6010"
    ATTRS{idVendor}=="0403"
    ATTRS{ltm_capable}=="no"
    ATTRS{manufacturer}=="utek"
    ATTRS{maxchild}=="0"
    ATTRS{product}=="USB <-> Serial Cable"
    ATTRS{quirks}=="0x0"
    ATTRS{removable}=="unknown"
    ATTRS{rx_lanes}=="1"
    ATTRS{serial}=="FTVW64JR"
    ATTRS{speed}=="12"
    ATTRS{tx_lanes}=="1"
    ATTRS{urbnum}=="1433013"
    ATTRS{version}==" 2.00"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.5':
    KERNELS=="1-1.5"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{authorized}=="1"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bDeviceClass}=="09"
    ATTRS{bDeviceProtocol}=="01"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{bMaxPower}=="100mA"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bcdDevice}=="0702"
    ATTRS{bmAttributes}=="e0"
    ATTRS{busnum}=="1"
    ATTRS{configuration}==""
    ATTRS{devnum}=="3"
    ATTRS{devpath}=="1.5"
    ATTRS{idProduct}=="0608"
    ATTRS{idVendor}=="05e3"
    ATTRS{ltm_capable}=="no"
    ATTRS{maxchild}=="4"
    ATTRS{product}=="USB2.0 Hub"
    ATTRS{quirks}=="0x0"
    ATTRS{removable}=="removable"
    ATTRS{rx_lanes}=="1"
    ATTRS{speed}=="480"
    ATTRS{tx_lanes}=="1"
    ATTRS{urbnum}=="128"
    ATTRS{version}==" 2.00"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1':
    KERNELS=="1-1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{authorized}=="1"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bDeviceClass}=="09"
    ATTRS{bDeviceProtocol}=="01"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{bMaxPower}=="0mA"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bcdDevice}=="0000"
    ATTRS{bmAttributes}=="e0"
    ATTRS{busnum}=="1"
    ATTRS{configuration}==""
    ATTRS{devnum}=="2"
    ATTRS{devpath}=="1"
    ATTRS{idProduct}=="0020"
    ATTRS{idVendor}=="8087"
    ATTRS{ltm_capable}=="no"
    ATTRS{maxchild}=="6"
    ATTRS{quirks}=="0x0"
    ATTRS{removable}=="fixed"
    ATTRS{rx_lanes}=="1"
    ATTRS{speed}=="480"
    ATTRS{tx_lanes}=="1"
    ATTRS{urbnum}=="31"
    ATTRS{version}==" 2.00"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb1':
    KERNELS=="usb1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{authorized}=="1"
    ATTRS{authorized_default}=="1"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bDeviceClass}=="09"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{bMaxPower}=="0mA"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bcdDevice}=="0418"
    ATTRS{bmAttributes}=="e0"
    ATTRS{busnum}=="1"
    ATTRS{configuration}==""
    ATTRS{devnum}=="1"
    ATTRS{devpath}=="0"
    ATTRS{idProduct}=="0002"
    ATTRS{idVendor}=="1d6b"
    ATTRS{interface_authorized_default}=="1"
    ATTRS{ltm_capable}=="no"
    ATTRS{manufacturer}=="Linux 4.18.0-348.2.1.el8_5.x86_64 ehci_hcd"
    ATTRS{maxchild}=="6"
    ATTRS{product}=="EHCI Host Controller"
    ATTRS{quirks}=="0x0"
    ATTRS{removable}=="unknown"
    ATTRS{rx_lanes}=="1"
    ATTRS{serial}=="0000:00:1a.0"
    ATTRS{speed}=="480"
    ATTRS{tx_lanes}=="1"
    ATTRS{urbnum}=="32"
    ATTRS{version}==" 2.00"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0':
    KERNELS=="0000:00:1a.0"
    SUBSYSTEMS=="pci"
    DRIVERS=="ehci-pci"
    ATTRS{ari_enabled}=="0"
    ATTRS{broken_parity_status}=="0"
    ATTRS{class}=="0x0c0320"
    ATTRS{companion}==""
    ATTRS{consistent_dma_mask_bits}=="32"
    ATTRS{d3cold_allowed}=="1"
    ATTRS{device}=="0x3b3c"
    ATTRS{dma_mask_bits}=="32"
    ATTRS{driver_override}=="(null)"
    ATTRS{enable}=="1"
    ATTRS{irq}=="16"
    ATTRS{local_cpulist}=="0-3"
    ATTRS{local_cpus}=="0f"
    ATTRS{msi_bus}=="1"
    ATTRS{numa_node}=="-1"
    ATTRS{revision}=="0x06"
    ATTRS{subsystem_device}=="0x3b3c"
    ATTRS{subsystem_vendor}=="0x1849"
    ATTRS{uframe_periodic_max}=="100"
    ATTRS{vendor}=="0x8086"

  looking at parent device '/devices/pci0000:00':
    KERNELS=="pci0000:00"
    SUBSYSTEMS==""
    DRIVERS==""

When I parse your output, I get this:

[label@sani ~]$ egrep 'idVendor|idProduct|serial|InterfaceNumber' /tmp/out
    SUBSYSTEMS=="usb-serial"
    ATTRS{bInterfaceNumber}=="01"
    ATTRS{idProduct}=="6010"
    ATTRS{idVendor}=="0403"
    ATTRS{serial}=="FTVW64JR"
    ATTRS{idProduct}=="0608"
    ATTRS{idVendor}=="05e3"
    ATTRS{idProduct}=="0020"
    ATTRS{idVendor}=="8087"
    ATTRS{idProduct}=="0002"
    ATTRS{idVendor}=="1d6b"
    ATTRS{serial}=="0000:00:1a.0"

I think you should be using the serial that looks like a PCI ID rather than the ID that looks like the SN of the device itself.

To try and see what’s going on I created a simpler rule that just chmod’s the object.

This works:

SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", ATTRS{serial}=="FTWKU6FP", MODE="0661"
...
crw-rw---- 1 root dialout 188,  0 Nov 27 09:20 /dev/ttyUSB0
crw-rw---- 1 root dialout 188,  1 Nov 27 09:20 /dev/ttyUSB1
crw-rw---x 1 root root    188,  2 Nov 27 09:20 /dev/ttyUSB2
crw-rw---x 1 root root    188,  3 Nov 27 09:20 /dev/ttyUSB3

We can see it correctly distinguishes between the units. But, of course, it applies to both ports.

However as soon as we add the interface number to it then it fails…

SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", ATTRS{serial}=="FTWKU6FP", ATTRS{bInterfaceNumber}=="01", MODE="0661"
...
crw-rw---- 1 root dialout 188,  0 Nov 27 09:25 /dev/ttyUSB0
crw-rw---- 1 root dialout 188,  1 Nov 27 09:25 /dev/ttyUSB1
crw-rw---- 1 root dialout 188,  2 Nov 27 09:25 /dev/ttyUSB2
crw-rw---- 1 root dialout 188,  3 Nov 27 09:25 /dev/ttyUSB3

Hmm…

So I think creating a custom unit may be simplest. It’s not as clean, but it seems to work…

[Unit]
Description=USB Getty
After=systemd-user-sessions.service plymouth-quit-wait.service getty-pre.target
After=rc-local.service
ConditionPathExists=/dev/serial/by-id/usb-utek_USB__-__Serial_Cable_FTVW64JR-if01-port0

[Install]
WantedBy=multi-user.target

[Service]
ExecStart=-/usr/sbin/agetty -h -L -w /serial/by-id/usb-utek_USB__-__Serial_Cable_FTVW64JR-if01-port0 9600 vt102
Type=idle
Restart=always
RestartSec=0
TTYReset=yes
TTYVHangup=yes
TTYVTDisallocate=yes
KillMode=process
IgnoreSIGPIPE=no
SendSIGHUP=yes

UnsetEnvironment=LANG LANGUAGE LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION

Hopefully the ConditionPathExists line will be the magic I need!

It doesn’t seem to correctly report on logouts, but otherwise seems to work!

% last -3
sweh     ttyUSB1                       Sat Nov 27 11:25    gone - no logout
sweh     ttyUSB1                       Sat Nov 27 11:18 - 11:25  (00:07)
sweh     ttyUSB1                       Sat Nov 27 11:17 - 11:18  (00:00)