Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

INFO: the problem with detecting serial ports on Linux #22

Open
knghtbrd opened this issue Dec 14, 2015 · 0 comments
Open

INFO: the problem with detecting serial ports on Linux #22

knghtbrd opened this issue Dec 14, 2015 · 0 comments

Comments

@knghtbrd
Copy link
Member

Non-Linux systems are relatively easy to determine what are and are not serial ports because they follow some semblance of rules for consistent device naming around serial and other devices. Linux doesn't.

And for all the crazy things Linux does with udev and sysfs and procfs to tell you all about the system, there's no simple way to say, "hi, what serial ports do you have?" Fortunately, on any Linux system modern enough for us to care about for this purpose, we do have some help. First, all serial devices are listed in /sys/class/tty. Here's mine on a Debian nettop system:

tjcarter@shiro:~$ ls /sys/class/tty/
console@  tty11@  tty17@  tty22@  tty28@  tty33@  tty39@  tty44@  tty5@   tty55@  tty60@  tty9@     ttyUSB1@
ptmx@     tty12@  tty18@  tty23@  tty29@  tty34@  tty4@   tty45@  tty50@  tty56@  tty61@  ttyS0@    ttyUSB2@
tty@      tty13@  tty19@  tty24@  tty3@   tty35@  tty40@  tty46@  tty51@  tty57@  tty62@  ttyS1@    ttyUSB3@
tty0@     tty14@  tty2@   tty25@  tty30@  tty36@  tty41@  tty47@  tty52@  tty58@  tty63@  ttyS2@
tty1@     tty15@  tty20@  tty26@  tty31@  tty37@  tty42@  tty48@  tty53@  tty59@  tty7@   ttyS3@
tty10@    tty16@  tty21@  tty27@  tty32@  tty38@  tty43@  tty49@  tty54@  tty6@   tty8@   ttyUSB0@

Obviously not all of those are tty entries. But if you happen to limit the list a little, you'll see the ones that are:

tjcarter@shiro:~$ ls /sys/class/tty/*/device/driver
/sys/class/tty/ttyS0/device/driver@  /sys/class/tty/ttyS3/device/driver@    /sys/class/tty/ttyUSB2/device/driver@
/sys/class/tty/ttyS1/device/driver@  /sys/class/tty/ttyUSB0/device/driver@  /sys/class/tty/ttyUSB3/device/driver@
/sys/class/tty/ttyS2/device/driver@  /sys/class/tty/ttyUSB1/device/driver@

That's more like it! The indicated entries in /sys/class/tty are real serial ports that exist on my system at the moment. There's also a little more information in /proc:

tjcarter@shiro:~$ cat /proc/tty/drivers 
/dev/tty             /dev/tty        5       0 system:/dev/tty
/dev/console         /dev/console    5       1 system:console
/dev/ptmx            /dev/ptmx       5       2 system
/dev/vc/0            /dev/vc/0       4       0 system:vtmaster
usbserial            /dev/ttyUSB   188 0-511 serial
serial               /dev/ttyS       4 64-95 serial
pty_slave            /dev/pts      136 0-1048575 pty:slave
pty_master           /dev/ptm      128 0-1048575 pty:master
unknown              /dev/tty        4 1-63 console
tjcarter@shiro:~$ sudo cat /proc/tty/driver/serial
serinfo:1.0 driver revision:
0: uart:unknown port:000003F8 irq:4
1: uart:unknown port:000002F8 irq:3
2: uart:unknown port:000003E8 irq:4
3: uart:unknown port:000002E8 irq:3
tjcarter@shiro:~$ sudo cat /proc/tty/driver/usbserial
usbserinfo:1.0 driver:2.0
0: module:ftdi_sio name:"FTDI USB Serial Device" vendor:0403 product:6001 num_ports:1 port:0 path:usb-0000:00:1d.7-6.1
1: module:ftdi_sio name:"FTDI USB Serial Device" vendor:0403 product:6001 num_ports:1 port:0 path:usb-0000:00:1d.7-6.2
2: module:keyspan name:"Keyspan 2 port adapter" vendor:06cd product:0110 num_ports:2 port:0 path:usb-0000:00:1d.7-6.3
3: module:keyspan name:"Keyspan 2 port adapter" vendor:06cd product:0110 num_ports:2 port:1 path:usb-0000:00:1d.7-6.3

You can determine how useful any of that is. I find it somewhat annoying to access this information using bash because I just don't have access to the tools to make use of it. I can use the sysfs interface though just fine from bash. I'd want to use python for sure if I were trying to use the older /proc interface.

That leads to the next problem: USB serial devices are wont to move around across reboots if you plug one into a USB port that is enumerated sooner on the next reboot. /dev/serial/by-id and /dev/serial/by-path contain entries that don't change upon reboots, supposedly. We'll use by-id since it's human-readable after a fashion. We'll implement something that basically works(ish) using bash:

#! /bin/bash
# vim: ts=4 sw=4 tw=78 ft=sh

for device in /dev/tty*; do
    # See if it's a real device
    devname=$(basename $device)
    if [ -d /sys/class/tty/$devname/device/driver ]; then
        # According to sysfs, it is

        # Now see if there's a /dev/serial entry for it
        for devserial in /dev/serial/by-id/*; do
            if [ "$(readlink -f $devserial)" = "$device" ]; then
                # Yes, use that instead
                device="$devserial"
            fi
        done

        devices="${devices:+$devices }$device"
    fi
done

# Show what we found
for dev in $devices; do
    echo "$dev"
done

Doing the same in python looks like this and is a little more robust:

#! /usr/bin/env python3
# vim: ts=4 sw=4 tw=78 ft=python

import os
import glob
from pprint import pprint

# The by-id paths are supposed to be stable across reboots
deviceIdx = {}
for symlink in glob.glob('/dev/serial/by-id/*'):
    deviceIdx[os.path.realpath(symlink)] = symlink

devices = []
ttySysDir = '/sys/class/tty'
for tty in os.listdir(ttySysDir):
    # VCs, ptys, etc don't have /sys/class/tty/<tty>/device/driver's
    if os.path.exists(os.path.join(ttySysDir, tty, 'device/driver')):
        # Appears to be a real serial device
        device = os.path.join('/dev', tty)
        if device in deviceIdx:
            # use the stable name
            devices.append(deviceIdx[device])
        elif os.path.exists(device):
            devices.append(device)

# Show what we found
pprint(devices)

The output of these commands:

tjcarter@shiro:~$ ./findserial.sh
/dev/ttyS0
/dev/ttyS1
/dev/ttyS2
/dev/ttyS3
/dev/serial/by-id/usb-FTDI_USB_Serial_Converter_FTGUK9F7-if00-port0
/dev/serial/by-id/usb-FTDI_USB_Serial_Converter_FT8ZQX5V-if00-port0
/dev/serial/by-id/usb-Keyspan__a_division_of_InnoSys_Inc._Keyspan_USB_Serial_Adapter-if00-port0
/dev/serial/by-id/usb-Keyspan__a_division_of_InnoSys_Inc._Keyspan_USB_Serial_Adapter-if00-port1
tjcarter@shiro:~$ ./findserial.py
['/dev/ttyS0',
'/dev/ttyS1',
'/dev/ttyS2',
'/dev/ttyS3',
'/dev/serial/by-id/usb-FTDI_USB_Serial_Converter_FTGUK9F7-if00-port0',
'/dev/serial/by-id/usb-FTDI_USB_Serial_Converter_FT8ZQX5V-if00-port0',
'/dev/serial/by-id/usb-Keyspan__a_division_of_InnoSys_Inc._Keyspan_USB_Serial_Adapter-if00-port0',
'/dev/serial/by-id/usb-Keyspan__a_division_of_InnoSys_Inc._Keyspan_USB_Serial_Adapter-if00-port1']

Of course, you can see there that my FTDI adapters have serial numbers. The Keyspan doesn't, so if I plugged another one in, we'd be back to the same enumeration problem again—cheap crap usually doesn't have unique serial numbers. Which is funny because the Keyspan adapters were anything but cheap at the time they were sold!

Anyway, here's what by-path entries look like:

tjcarter@shiro:~$ ls /dev/serial/by-path/
pci-0000:00:1d.7-usb-0:6.1:1.0-port0@  pci-0000:00:1d.7-usb-0:6.3:1.0-port0@
pci-0000:00:1d.7-usb-0:6.2:1.0-port0@  pci-0000:00:1d.7-usb-0:6.3:1.0-port1@

Kind of misleading because at first glance it looks like those are PCI devices. You have to read backwards: Ports in a tree of USB devices in a tree of PCI devices. That's gonna make users' heads hurt. It makes MY head hurt, and I know what I'm looking at!

I don't mind presenting the user with the by-path device node when we have one, provided that we give them some human-readable output. The problem is that we don't have human-readable output for the UART drivers, other than that they're UARTs. It'd be real easy to figure out on my little nettop that the four UART ports don't physically exist if we translate that for the user so they know to look for physical serial ports on the computer. There aren't any. (I really ought to see if I can disable those in the BIOS, but left them on while I was working here.)

Anyway, this sufficiently defines the problem I think. The solution is there, but it isn't ever going to be pretty due to crappy hardware and software design. The best we can do is try to tell the user what we find in as much detail as we can to help them identify which ports are which, and make sure it works before we make any assumptions that it will.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant