133 votes

Comment trouver tous les périphériques série (ttyS, ttyUSB, ..) sous Linux sans les ouvrir ?

Quelle est la bonne façon d'obtenir une liste de tous les ports/périphériques série disponibles sur un système Linux ?

En d'autres termes, quand j'itère sur tous les dispositifs dans /dev/ comment puis-je savoir quels sont les ports série classiques, c'est-à-dire ceux qui supportent habituellement des vitesses de transmission et des vitesses d'échantillonnage ? RTS/CTS le contrôle des flux ?

La solution serait codée en C.

Je pose la question parce que j'utilise une bibliothèque tierce qui fait clairement fausse route : elle semble seulement itérer sur /dev/ttyS* . Le problème est qu'il existe, par exemple, des ports série sur USB (fournis par des adaptateurs USB-RS232), et ceux-ci sont répertoriés sous /dev/ttyUSB*. Et en lisant le Serial-HOWTO à Linux.org J'ai l'impression qu'il y aura d'autres espaces de noms, le moment venu.

Je dois donc trouver le moyen officiel de détecter les périphériques série. Le problème est qu'aucune ne semble être documentée, ou je ne peux pas la trouver.

J'imagine qu'une solution serait d'ouvrir tous les fichiers à partir de /dev/tty* et appeler un ioctl() sur eux qui n'est disponible que sur les périphériques série. Est-ce que ce serait une bonne solution ?

Mise à jour

hrickards a suggéré de regarder la source de "setserial". Son code fait exactement ce que j'avais en tête :

D'abord, il ouvre un appareil avec :

fd = open (path, O_RDWR | O_NONBLOCK)

Ensuite, il invoque :

ioctl (fd, TIOCGSERIAL, &serinfo)

Si cet appel ne renvoie aucune erreur, alors c'est un périphérique série, apparemment.

J'ai trouvé un code similaire dans _Programmation en série/termios_ qui a suggéré d'ajouter également le O_NOCTTY option.

Il y a cependant un problème avec cette approche :

Lorsque j'ai testé ce code sur BSD Unix (c'est-à-dire Mac OS X), il a également fonctionné. Cependant Dans le cas des périphériques série fournis par Bluetooth, le système (pilote) essaie de se connecter au périphérique Bluetooth, ce qui prend un certain temps avant de renvoyer une erreur de dépassement de délai. Ceci est causé par la simple ouverture du périphérique. Et je peux imaginer que des choses similaires peuvent se produire sous Linux également - idéalement, je ne devrais pas avoir besoin d'ouvrir le périphérique pour connaître son type. Je me demande s'il existe également un moyen d'invoquer ioctl fonctionne sans ouverture, ou ouvre un dispositif de manière à ce qu'il ne provoque pas de connexions ?

Que dois-je faire ?

1 votes

Quelqu'un d'anonyme avait suggéré cette modification, qui a été rejetée, alors je la laisse ici en commentaire à la place : Si vous utilisez le drapeau TIOCGSERIAL dans l'appel ioctl, au lieu de TIOCMGET, alors l'appel ne renvoie pas d'erreur avec certains chemins erronés qui ne font pas référence à un port COM (série). Avec le drapeau TIOCMGET, ioctl ne fonctionne qu'avec les ports COM disponibles pour l'accès dans les chemins possibles TTY et TTYUSB.

100voto

A.H. Points 23369

Le site /sys devrait contenir beaucoup d'informations pour votre quête. Mon système (2.6.32-40-generic #87-Ubuntu) suggère :

/sys/class/tty

Qui vous donne la description de tous les appareils TTY connus du système. Un exemple réduit :

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

En suivant l'un de ces liens :

# 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

Ici, le dev contient ces informations :

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

C'est le nœud majeur/minor. Ceux-ci peuvent être recherchés dans le /dev pour obtenir des noms conviviaux :

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

Le site /sys/class/tty dir contient tous les périphériques TTY, mais vous pourriez vouloir exclure ces ennuyeux terminaux virtuels et pseudo-terminaux. Je vous suggère d'examiner uniquement ceux qui ont un device/driver l'entrée :

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

0 votes

@entalpi Vous trouverez /dev/zero . Vous pensez vraiment que c'est un appareil en série ?

1 votes

Chercher dans /dev est inutile, puisque vous avez déjà le nom dans /sys/class/tty (puisque par défaut udev crée le noeud /dev/DEVNAME). Ce qui vous intéresse, c'est tout lien "symbolique" dans /dev qui pointe vers ce périphérique. C'est beaucoup plus difficile à trouver.

37voto

flu0 Points 71

Dans les noyaux récents (pas sûr depuis quand), vous pouvez lister le contenu de /dev/serial pour obtenir une liste des ports série sur votre système. Il s'agit en fait de liens symboliques pointant vers le nœud /dev/ correct :

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

C'est un adaptateur USB-Série, comme vous pouvez le voir. Notez que lorsqu'il n'y a pas de port série sur le système, le répertoire /dev/serial/ n'existe pas. J'espère que cela vous aidera :).

5 votes

Il s'agit d'une fonction d'udev (spécifiquement sa configuration dans /lib/udev/rules.d/??-persistent-serial.rules), qui a été introduite en 2.5.

4 votes

Bon conseil ! Malheureusement, je ne pense pas que cela montrera les ports série intégrés, seulement les ports série USB (vus par udev lorsqu'ils sont connectés). Je ne vois rien pour /dev/serial dans Ubuntu 14 dans une VM VMware (avec ttyS0/COM1 fourni par la VM), et les règles udev (60-persistent-serial.rules) ne regardent que les périphériques udev -- je ne pense pas qu'udev découvre les ports série ttyS* "intégrés", ils devront être testés avec ioctl ou similaire comme dans les autres réponses.

0 votes

Ls /dev/serial/ ls : ne peut accéder à '/dev/serial/' : Aucun fichier ou répertoire de ce type Slackware 14.2 current x64

18voto

Søren Holm Points 41

Je fais quelque chose comme le code suivant. Cela fonctionne pour les périphériques USB et aussi pour les stupides périphériques serial8250 que nous avons tous 30, mais seulement deux d'entre eux fonctionnent vraiment.

En gros, j'utilise le concept des réponses précédentes. D'abord, j'énumère tous les périphériques tty dans /sys/class/tty/. Les périphériques qui ne contiennent pas de sous-répertoire /device sont éliminés par filtrage. /sys/class/tty/console est un tel périphérique. Les périphériques contenant réellement un périphérique sont alors acceptés comme des ports série valides en fonction de la cible du lien symbolique du pilote 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

et pour 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

Tous les pilotes pilotés par serial8250 doivent être sondés en utilisant l'ioctl mentionné précédemment.

        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

Seul le port rapportant un type de dispositif valide est valide.

La source complète pour l'énumération des ports de série ressemble à ceci. Les ajouts sont les bienvenus.

#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;   
}

0 votes

Le lien unique est considéré comme une mauvaise réponse puisqu'elle n'a pas de sens en elle-même et que la ressource cible n'est pas garantie d'être vivante dans le futur. Veuillez essayer d'inclure au moins un résumé de l'information vers laquelle vous établissez un lien.

0 votes

Merci à Soren pour cela, même si nous connaissons les API et avons quelques idées à ce sujet, vous avez vraiment fait du bon travail Soren, merci encore.

15voto

mk2 Points 41

Je pense avoir trouvé la réponse dans la documentation source de mon noyau : /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 

Voici un lien vers ce fichier : http://git.kernel.org/?p=linux/kernel/git/next/linux-next.git;a=blob_plain;f=Documentation/filesystems/proc.txt;hb=e8883f8057c0f7c9950fa9f20568f37bfa62f34a

0 votes

Oui, ça semble fonctionner. Cependant, cette solution me demande de lire un fichier texte et de l'analyser. Je me demande s'il existe une meilleure solution, c'est-à-dire une API qui me permette d'obtenir ces contenus dans un format binaire structuré.

6voto

hrickards Points 491

Setserial avec l'option -g semble faire ce que vous voulez et la source C est disponible à l'adresse suivante http://www.koders.com/c/fid39344DABD14604E70DF1B8FEA7D920A94AF78BF8.aspx .

0 votes

J'ai regardé le code et il a le défaut que j'explique dans ma question à la fin car il doit ouvrir le périphérique, ce qui peut déjà conduire à une tentative de connexion - ce qui n'est pas bon. Mais alors, peut-être que les pilotes Linux sont plus intelligents que les pilotes OSX actuels en ce qui concerne le support Bluetooth, car ils n'ouvrent pas une connexion immédiatement ? Qui sait ? Peut-être que je devrais commencer une nouvelle question pour clarifier cela spécifiquement. S'il s'avère que c'est bien, alors je peux accepter votre réponse ici aussi. Hmmm...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X