¿Cómo encontrar todos los dispositivos serie (ttyS, ttyUSB, ..) en Linux sin abrirlos?

113

¿Cuál es la forma correcta de obtener una lista de todos los puertos / dispositivos serie disponibles en un sistema Linux?

En otras palabras, cuando itero sobre todos los dispositivos /dev/, ¿cómo puedo saber cuáles son los puertos serie de la forma clásica, es decir, los que suelen admitir velocidades en baudios y control de flujo RTS / CTS ?

La solución estaría codificada en C.

Lo pregunto porque estoy usando una biblioteca de terceros que hace esto claramente mal: parece que solo se repite /dev/ttyS*. El problema es que existen, por ejemplo, puertos seriales a través de USB (proporcionados por adaptadores USB-RS232), y estos se enumeran en / dev / ttyUSB *. Y leyendo el Serial-HOWTO en Linux.org , tengo la idea de que también habrá otros espacios de nombres, a medida que llegue el momento.

Entonces necesito encontrar la forma oficial de detectar dispositivos seriales. El problema es que ninguno parece estar documentado o no puedo encontrarlo.

Imagino que una forma sería abrir todos los archivos de /dev/tty*y llamar a uno específico ioctl()que solo está disponible en dispositivos seriales. Sin embargo, ¿sería esa una buena solución?

Actualizar

hrickards sugirió buscar en la fuente de "setserial". Su código hace exactamente lo que tenía en mente:

Primero, abre un dispositivo con:

fd = open (path, O_RDWR | O_NONBLOCK)

Entonces invoca:

ioctl (fd, TIOCGSERIAL, &serinfo)

Si esa llamada no devuelve ningún error, aparentemente es un dispositivo en serie.

Encontré un código similar en Serial Programming / termios , que sugirió agregar también la O_NOCTTYopción.

Sin embargo, hay un problema con este enfoque:

Cuando probé este código en BSD Unix (es decir, Mac OS X), también funcionó. Sin embargo , los dispositivos en serie que se proporcionan a través de Bluetooth hacen que el sistema (controlador) intente conectarse al dispositivo Bluetooth, lo que demora un tiempo antes de que vuelva con un error de tiempo de espera. Esto se debe a que simplemente abre el dispositivo. Y puedo imaginar que cosas similares también pueden suceder en Linux; idealmente, no debería necesitar abrir el dispositivo para averiguar su tipo. Me pregunto si también hay una manera de invocar ioctlfunciones sin abrir, o abrir un dispositivo de una manera que no provoque conexiones.

¿Qué tengo que hacer?

Thomas Tempelmann
fuente
1
Alguien anónimo había sugerido esta edición, que fue rechazada, así que la dejo aquí como comentario: si usa la marca TIOCGSERIAL en la llamada ioctl, en lugar de TIOCMGET, la llamada no devuelve el error con algunas rutas incorrectas que no consulte un puerto COM (serie). Con el indicador TIOCMGET, ioctl solo funciona con los puertos COM disponibles para acceder en las rutas posibles de TTY y TTYUSB.
Thomas Tempelmann

Respuestas:

78

El /syssistema de archivos debe contener mucha información para su búsqueda. Mi sistema (2.6.32-40-generic # 87-Ubuntu) sugiere:

/sys/class/tty

Lo que le brinda descripciones de todos los dispositivos TTY conocidos por el sistema. Un ejemplo recortado:

# 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/

Siguiendo uno de estos enlaces:

# ll /sys/class/tty/ttyUSB0/
insgesamt 0
drwxr-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 dev
lrwxrwxrwx 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

Aquí el devarchivo contiene esta información:

# cat /sys/class/tty/ttyUSB0/dev
188:0

Este es el nodo mayor / menor. Estos se pueden buscar en el /devdirectorio para obtener nombres fáciles de usar:

# ll -R /dev |grep "188, *0"
crw-rw----   1 root dialout 188,   0 2012-03-28 20:44 ttyUSB0

El /sys/class/ttydirectorio contiene todos los dispositivos TTY, pero es posible que desee excluir esos molestos terminales virtuales y pseudo terminales. Le sugiero que examine solo aquellos que tienen una device/driverentrada:

# ll /sys/class/tty/*/device/driver
lrwxrwxrwx 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/
AH
fuente
@entalpi Encontrarás /dev/zero. ¿De verdad crees que este es un dispositivo en serie?
AH
Buscar en / dev es inútil, ya que ya tiene el nombre en / sys / class / tty (ya que por defecto udev crea el nodo / dev / DEVNAME). Lo que le interesa es cualquier enlace "simbólico" en / dev que apunte a dicho dispositivo. Esto es mucho más difícil de encontrar.
xryl669
28

En los kernels recientes (no estoy seguro desde cuándo) puede enumerar el contenido de / dev / serial para obtener una lista de los puertos seriales de su sistema. En realidad, son enlaces simbólicos que apuntan al nodo / dev / correcto:

flu0@laptop:~$ ls /dev/serial/
total 0
drwxr-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 0
lrwxrwxrwx 1 root root 13 2011-07-20 17:12 usb-Prolific_Technology_Inc._USB-Serial_Controller-if00-port0 -> ../../ttyUSB0
flu0@laptop:~$ ls /dev/serial/by-path/
total 0
lrwxrwxrwx 1 root root 13 2011-07-20 17:12 pci-0000:00:0b.0-usb-0:3:1.0-port0 -> ../../ttyUSB0

Este es un adaptador USB-Serie, como puede ver. Tenga en cuenta que cuando no hay puertos serie en el sistema, el directorio / dev / serial / no existe. Espero que esto ayude :).

gripe0
fuente
3
Esta es una función de udev (específicamente su configuración en /lib/udev/rules.d/??-persistent-serial.rules), que se introdujo en 2.5.
ergosys
4
¡Buen consejo! Desafortunadamente, no creo que esto muestre puertos seriales integrados, solo puertos seriales USB (vistos por udev cuando están conectados). No veo nada para / dev / serial en Ubuntu 14 en una VM de VMware (con ttyS0 / COM1 proporcionado por la VM), y las reglas de udev (60-persistent-serial.rules) solo miran dispositivos udev - No creo que udev se entere de los puertos serie ttyS * "integrados", tendrán que probarse con ioctl o similar como en las otras respuestas.
Reed Hedges
ls / dev / serial / ls: no se puede acceder a '/ dev / serial /': no
existe
2
@jpka: Eso sucede si no hay ningún dispositivo en serie para encontrar. Hice lo anterior y funcionó. Luego desconecté mi dispositivo serie (FTDI) del USB y luego produjo el error que describiste.
Warpspace
13

Estoy haciendo algo como el siguiente código. Funciona para dispositivos USB y también para los estúpidos dispositivos serial8250 de los que todos tenemos 30, pero solo un par de ellos realmente funcionan.

Básicamente utilizo el concepto de respuestas anteriores. Primero enumere todos los dispositivos tty en / sys / class / tty /. Los dispositivos que no contienen un subdirectorio / device se filtran. / sys / class / tty / console es uno de estos dispositivos. Luego, los dispositivos que realmente contienen un dispositivo se aceptan como puerto serie válido según el destino del controlador-enlace simbólico fx.

$ ls -al /sys/class/tty/ttyUSB0//device/driver
lrwxrwxrwx 1 root root 0 sep  6 21:28 /sys/class/tty/ttyUSB0//device/driver -> ../../../bus/platform/drivers/usbserial

y para ttyS0

$ ls -al /sys/class/tty/ttyS0//device/driver
lrwxrwxrwx 1 root root 0 sep  6 21:28 /sys/class/tty/ttyS0//device/driver -> ../../../bus/platform/drivers/serial8250

Todos los controladores controlados por serial8250 deben ser sondas que utilicen el ioctl mencionado anteriormente.

        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

Solo el puerto que informa un tipo de dispositivo válido es válido.

La fuente completa para enumerar los puertos de serie se ve así. Las adiciones son bienvenidas.

#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;   
}
Søren Holm
fuente
El enlace solitario se considera una respuesta deficiente, ya que no tiene sentido por sí mismo y no se garantiza que el recurso de destino esté vivo en el futuro. Intente incluir al menos un resumen de la información a la que está enlazando.
j0k
Gracias a Soren por esto, incluso nosotros conocemos las API y alguna idea al respecto, pero lo hiciste muy bien Soren, gracias de nuevo.
ind79ra
12

Creo que encontré la respuesta en la documentación de las fuentes de mi kernel: /usr/src/linux-2.6.37-rc3/Documentation/filesystems/proc.txt

1.7 TTY info in /proc/tty
-------------------------

Information about  the  available  and actually used tty's can be found in the
directory /proc/tty.You'll  find  entries  for drivers and line disciplines in
this directory, as shown in Table 1-11.


Table 1-11: Files in /proc/tty
..............................................................................
 File          Content                                        
 drivers       list of drivers and their usage                
 ldiscs        registered line disciplines                    
 driver/serial usage statistic and status of single tty lines 
..............................................................................

To see  which  tty's  are  currently in use, you can simply look into the file
/proc/tty/drivers:

  > cat /proc/tty/drivers 
  pty_slave            /dev/pts      136   0-255 pty:slave 
  pty_master           /dev/ptm      128   0-255 pty:master 
  pty_slave            /dev/ttyp       3   0-255 pty:slave 
  pty_master           /dev/pty        2   0-255 pty:master 
  serial               /dev/cua        5   64-67 serial:callout 
  serial               /dev/ttyS       4   64-67 serial 
  /dev/tty0            /dev/tty0       4       0 system:vtmaster 
  /dev/ptmx            /dev/ptmx       5       2 system 
  /dev/console         /dev/console    5       1 system:console 
  /dev/tty             /dev/tty        5       0 system:/dev/tty 
  unknown              /dev/tty        4    1-63 console 

Aquí hay un enlace a este archivo: http://git.kernel.org/?p=linux/kernel/git/next/linux-next.git;a=blob_plain;f=Documentation/filesystems/proc.txt;hb = e8883f8057c0f7c9950fa9f20568f37bfa62f34a

mk2
fuente
Sí, eso parece estar funcionando. Sin embargo, esta solución requiere que lea un archivo de texto y lo analice. Me pregunto si hay una mejor opción, es decir, una API que me permita obtener estos contenidos en un formato binario estructurado.
Thomas Tempelmann
9

encontré

dmesg | grep tty

haciendo el trabajo.

RealHuman75
fuente
3

setserial con la opción -g parece hacer lo que desea y la fuente C está disponible en http://www.koders.com/c/fid39344DABD14604E70DF1B8FEA7D920A94AF78BF8.aspx .

hrickards
fuente
Miré el código y tiene la falla que explico en mi pregunta al final, ya que tiene que abrir el dispositivo, lo que ya puede llevar a un intento de conexión, lo que a su vez no es bueno. Pero entonces, tal vez los controladores de Linux sean más inteligentes que el controlador OSX actual cuando se trata de compatibilidad con bluetooth, ya que no abren una conexión de inmediato. ¿Quién sabe? Tal vez debería comenzar una nueva pregunta para aclarar eso específicamente. Si resulta que está bien, entonces puedo aceptar tu respuesta aquí también. Hmmm ...
Thomas Tempelmann
3

No tengo un dispositivo serial aquí para probarlo, pero si tiene python y dbus, puede probarlo usted mismo.

import dbus
bus = dbus.SystemBus()
hwmanager = bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager')
hwmanager_i = dbus.Interface(hwmanager, 'org.freedesktop.Hal.Manager')
print hwmanager_i.FindDeviceByCapability("serial")

Si falla, puede buscar adentro hwmanager_i.GetAllDevicesWithProperties()para ver si el nombre de capacidad "serial" que supuse tiene un nombre diferente.

HTH

baol
fuente
2

No tengo un dispositivo serie USB, pero debe haber una forma de encontrar los puertos reales usando las bibliotecas HAL directamente:

====================================================================
#! /usr/bin/env bash
#
# Uses HAL to find existing serial hardware
#

for sport in $(hal-find-by-capability --capability serial) ; do
  hal-get-property --udi "${sport}" --key serial.device
done

====================================================================

El código python-dbus publicado ni este script sh enumeran los dispositivos bluetooth / dev / rfcomm *, por lo que no es la mejor solución.

Tenga en cuenta que en otras plataformas Unix, los puertos serie no se denominan ttyS? e incluso en linux, algunas tarjetas seriales le permiten nombrar los dispositivos. Asumir un patrón en los nombres de los dispositivos seriales es incorrecto.

kelk1
fuente
Lástima que HAL se eliminó de Ubuntu (después de 12.04), tenía algunas herramientas agradables y fáciles de usar. ¿Alguien sabe si hay un reemplazo a lo anterior? Pero si estás en una versión / distribución que tiene HAL, esto se ve bien.
Reed Hedges
2

El uso de / proc / tty / drivers solo indica qué controladores tty están cargados. Si está buscando una lista de los puertos serie, consulte / dev / serial, tendrá dos subdirectorios: by-id y by-path.

EX:

# find . -type l
./by-path/usb-0:1.1:1.0-port0
./by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller-if00-port0

Gracias a esta publicación: /superuser/131044/how-do-i-know-which-dev-ttys-is-my-serial-port

blarf
fuente
Aparentemente, esto depende de la distribución. No puedo encontrar / dev / serial en mi caja (ejecutando Debian)
SimonC
0

Mi enfoque a través de la marcación grupal para obtener cada tty con el usuario 'dialout' ls -l /dev/tty* | grep 'dialout' para obtener solo su carpeta ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev

Escuche fácilmente la salida tty, por ejemplo, cuando arduino serial out: head --lines 1 < /dev/ttyUSB0

escuche cada tty para una sola línea: for i in $(ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev); do head --lines 1 < $i; done

Realmente me gusta el enfoque mediante la búsqueda de controladores: ll /sys/class/tty/*/device/driver

Puede elegir el nombre tty ahora: ls /sys/class/tty/*/device/driver | grep 'driver' | cut -d "/" -f 5

McPeppr
fuente
0

La biblioteca del administrador de comunicaciones en serie tiene muchas API y funciones destinadas a la tarea que desea. Si el dispositivo es un USB-UART, se puede utilizar su VID / PID. Si el dispositivo es BT-SPP, se pueden utilizar API específicas de la plataforma. Eche un vistazo a este proyecto para la programación de puertos serie: https://github.com/RishiGupta12/serial-communication-manager

samuel05051980
fuente
0

sí, lo sé, llego demasiado tarde (como siempre). Aquí está mi código (basado en la respuesta de mk2). Quizás esto ayude a alguien:

std::vector<std::string> find_serial_ports()
{
 std::vector<std::string> ports;
    std::filesystem::path kdr_path{"/proc/tty/drivers"};
    if (std::filesystem::exists(kdr_path))
    {
        std::ifstream ifile(kdr_path.generic_string());
        std::string line;
        std::vector<std::string> prefixes;
        while (std::getline(ifile, line))
        {
            std::vector<std::string> items;
            auto it = line.find_first_not_of(' ');
            while (it != std::string::npos)
            {

                auto it2 = line.substr(it).find_first_of(' ');
                if (it2 == std::string::npos)
                {
                    items.push_back(line.substr(it));
                    break;
                }
                it2 += it;
                items.push_back(line.substr(it, it2 - it));
                it = it2 + line.substr(it2).find_first_not_of(' ');
            }
            if (items.size() >= 5)
            {
                if (items[4] == "serial" && items[0].find("serial") != std::string::npos)
                {
                    prefixes.emplace_back(items[1]);
                }
            }
        }
        ifile.close();
        for (auto& p: std::filesystem::directory_iterator("/dev"))
        {
            for (const auto& pf : prefixes)
            {
                auto dev_path = p.path().generic_string();
                if (dev_path.size() >= pf.size() && std::equal(dev_path.begin(), dev_path.begin() + pf.size(), pf.begin()))
                {
                    ports.emplace_back(dev_path);
                }
            }
        }
    }
    return ports;
}
cdannebe
fuente
Parece que su código analiza a qué se refiere la respuesta stackoverflow.com/a/4701610/43615 . Si es así, ¿podría mencionarlo en su respuesta, por favor?
Thomas Tempelmann