How to find all serial devices (ttyS, ttyUSB, ..) on Linux without opening them?
The /sys
filesystem should contain plenty information for your quest. My system (2.6.32-40-generic #87-Ubuntu) suggests:
/sys/class/tty
Which gives you descriptions of all TTY devices known to the system. A trimmed down example:
# ll /sys/class/tty/ttyUSB*lrwxrwxrwx 1 root root 0 2012-03-28 20:43 /sys/class/tty/ttyUSB0 -> ../../devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4/2-1.4:1.0/ttyUSB0/tty/ttyUSB0/lrwxrwxrwx 1 root root 0 2012-03-28 20:44 /sys/class/tty/ttyUSB1 -> ../../devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.3/2-1.3:1.0/ttyUSB1/tty/ttyUSB1/
Following one of these links:
# ll /sys/class/tty/ttyUSB0/insgesamt 0drwxr-xr-x 3 root root 0 2012-03-28 20:43 ./drwxr-xr-x 3 root root 0 2012-03-28 20:43 ../-r--r--r-- 1 root root 4096 2012-03-28 20:49 devlrwxrwxrwx 1 root root 0 2012-03-28 20:43 device -> ../../../ttyUSB0/drwxr-xr-x 2 root root 0 2012-03-28 20:49 power/lrwxrwxrwx 1 root root 0 2012-03-28 20:43 subsystem -> ../../../../../../../../../../class/tty/-rw-r--r-- 1 root root 4096 2012-03-28 20:43 uevent
Here the dev
file contains this information:
# cat /sys/class/tty/ttyUSB0/dev188:0
This is the major/minor node. These can be searched in the /dev
directory to get user-friendly names:
# ll -R /dev |grep "188, *0"crw-rw---- 1 root dialout 188, 0 2012-03-28 20:44 ttyUSB0
The /sys/class/tty
dir contains all TTY devices but you might want to exclude those pesky virtual terminals and pseudo terminals. I suggest you examine only those which have a device/driver
entry:
# ll /sys/class/tty/*/device/driverlrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS0/device/driver -> ../../../bus/pnp/drivers/serial/lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS1/device/driver -> ../../../bus/pnp/drivers/serial/lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS2/device/driver -> ../../../bus/platform/drivers/serial8250/lrwxrwxrwx 1 root root 0 2012-03-28 19:07 /sys/class/tty/ttyS3/device/driver -> ../../../bus/platform/drivers/serial8250/lrwxrwxrwx 1 root root 0 2012-03-28 20:43 /sys/class/tty/ttyUSB0/device/driver -> ../../../../../../../../bus/usb-serial/drivers/ftdi_sio/lrwxrwxrwx 1 root root 0 2012-03-28 21:15 /sys/class/tty/ttyUSB1/device/driver -> ../../../../../../../../bus/usb-serial/drivers/ftdi_sio/
In recent kernels (not sure since when) you can list the contents of /dev/serial to get a list of the serial ports on your system. They are actually symlinks pointing to the correct /dev/ node:
flu0@laptop:~$ ls /dev/serial/total 0drwxr-xr-x 2 root root 60 2011-07-20 17:12 by-id/drwxr-xr-x 2 root root 60 2011-07-20 17:12 by-path/flu0@laptop:~$ ls /dev/serial/by-id/total 0lrwxrwxrwx 1 root root 13 2011-07-20 17:12 usb-Prolific_Technology_Inc._USB-Serial_Controller-if00-port0 -> ../../ttyUSB0flu0@laptop:~$ ls /dev/serial/by-path/total 0lrwxrwxrwx 1 root root 13 2011-07-20 17:12 pci-0000:00:0b.0-usb-0:3:1.0-port0 -> ../../ttyUSB0
This is a USB-Serial adapter, as you can see. Note that when there are no serial ports on the system, the /dev/serial/ directory does not exists. Hope this helps :).
I'm doing something like the following code. It works for USB-devices and also the stupid serial8250-devuices that we all have 30 of - but only a couple of them realy works.
Basically I use concept from previous answers. First enumerate all tty-devices in /sys/class/tty/. Devices that does not contain a /device subdir is filtered away. /sys/class/tty/console is such a device. Then the devices actually containing a devices in then accepted as valid serial-port depending on the target of the driver-symlink fx.
$ ls -al /sys/class/tty/ttyUSB0//device/driverlrwxrwxrwx 1 root root 0 sep 6 21:28 /sys/class/tty/ttyUSB0//device/driver -> ../../../bus/platform/drivers/usbserial
and for ttyS0
$ ls -al /sys/class/tty/ttyS0//device/driverlrwxrwxrwx 1 root root 0 sep 6 21:28 /sys/class/tty/ttyS0//device/driver -> ../../../bus/platform/drivers/serial8250
All drivers driven by serial8250 must be probes using the previously mentioned ioctl.
if (ioctl(fd, TIOCGSERIAL, &serinfo)==0) { // If device type is no PORT_UNKNOWN we accept the port if (serinfo.type != PORT_UNKNOWN) the_port_is_valid
Only port reporting a valid device-type is valid.
The complete source for enumerating the serialports looks like this. Additions are welcome.
#include <stdlib.h>#include <dirent.h>#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <unistd.h>#include <string.h>#include <fcntl.h>#include <termios.h>#include <sys/ioctl.h>#include <linux/serial.h>#include <iostream>#include <list>using namespace std;static string get_driver(const string& tty) { struct stat st; string devicedir = tty; // Append '/device' to the tty-path devicedir += "/device"; // Stat the devicedir and handle it if it is a symlink if (lstat(devicedir.c_str(), &st)==0 && S_ISLNK(st.st_mode)) { char buffer[1024]; memset(buffer, 0, sizeof(buffer)); // Append '/driver' and return basename of the target devicedir += "/driver"; if (readlink(devicedir.c_str(), buffer, sizeof(buffer)) > 0) return basename(buffer); } return "";}static void register_comport( list<string>& comList, list<string>& comList8250, const string& dir) { // Get the driver the device is using string driver = get_driver(dir); // Skip devices without a driver if (driver.size() > 0) { string devfile = string("/dev/") + basename(dir.c_str()); // Put serial8250-devices in a seperate list if (driver == "serial8250") { comList8250.push_back(devfile); } else comList.push_back(devfile); }}static void probe_serial8250_comports(list<string>& comList, list<string> comList8250) { struct serial_struct serinfo; list<string>::iterator it = comList8250.begin(); // Iterate over all serial8250-devices while (it != comList8250.end()) { // Try to open the device int fd = open((*it).c_str(), O_RDWR | O_NONBLOCK | O_NOCTTY); if (fd >= 0) { // Get serial_info if (ioctl(fd, TIOCGSERIAL, &serinfo)==0) { // If device type is no PORT_UNKNOWN we accept the port if (serinfo.type != PORT_UNKNOWN) comList.push_back(*it); } close(fd); } it ++; }}list<string> getComList() { int n; struct dirent **namelist; list<string> comList; list<string> comList8250; const char* sysdir = "/sys/class/tty/"; // Scan through /sys/class/tty - it contains all tty-devices in the system n = scandir(sysdir, &namelist, NULL, NULL); if (n < 0) perror("scandir"); else { while (n--) { if (strcmp(namelist[n]->d_name,"..") && strcmp(namelist[n]->d_name,".")) { // Construct full absolute file path string devicedir = sysdir; devicedir += namelist[n]->d_name; // Register the device register_comport(comList, comList8250, devicedir); } free(namelist[n]); } free(namelist); } // Only non-serial8250 has been added to comList without any further testing // serial8250-devices must be probe to check for validity probe_serial8250_comports(comList, comList8250); // Return the lsit of detected comports return comList;}int main() { list<string> l = getComList(); list<string>::iterator it = l.begin(); while (it != l.end()) { cout << *it << endl; it++; } return 0; }