web-dev-qa-db-fra.com

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

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

En d'autres termes, lorsque j'itère tous les périphériques dans /dev/, comment savoir quels sont les ports série de la manière classique, c'est-à-dire ceux qui prennent généralement en charge les débits en bauds et RTS/CTS contrôle de flux?

La solution serait codée en C.

Je pose la question parce que j'utilise une bibliothèque tierce qui fait clairement ce qui ne va pas: il semble que seule itération sur /dev/ttyS*. Le problème est qu’il existe, par exemple, des ports série sur USB (fournis par les adaptateurs USB-RS232), et que ceux-ci sont répertoriés sous/dev/ttyUSB *. Et en lisant le Serial-HOWTO sur Linux.org , je me rends compte qu’il y aura également 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'aucun ne semble être documenté, ou je ne le trouve pas.

J'imagine qu'une solution consisterait à ouvrir tous les fichiers à partir de /dev/tty* et à appeler une ioctl() spécifique disponible uniquement sur les périphériques série. Serait-ce une bonne solution, cependant?

Mettre à jour

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

Tout d'abord, il ouvre un appareil avec:

fd = open (path, O_RDWR | O_NONBLOCK)

Ensuite, il appelle:

ioctl (fd, TIOCGSERIAL, &serinfo)

Si cet appel ne renvoie aucune erreur, il s'agit apparemment d'un périphérique série.

J'ai trouvé un code similaire dans Programmation série/termios, qui suggérait également d'ajouter l'option O_NOCTTY.

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

Lorsque j'ai testé ce code sur BSD Unix (c'est-à-dire Mac OS X), cela a également fonctionné. Cependant, les périphériques série fournis via Bluetooth obligent le système (pilote) à tenter de se connecter au périphérique Bluetooth, ce qui prend un certain temps avant qu'il ne revienne avec une erreur de délai d'attente. Cela est dû à la simple ouverture de l'appareil. Et je peux imaginer que des choses similaires peuvent aussi arriver sous Linux - idéalement, je ne devrais pas avoir besoin d'ouvrir le périphérique pour comprendre son type. Je me demande s’il existe également un moyen d’appeler des fonctions ioctl sans ouvrir ou d’ouvrir un périphérique de manière à ne pas créer de connexions?

Que devrais-je faire?

95
Thomas Tempelmann

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

/sys/class/tty

Ce qui vous donne une description de tous les appareils ATS connus du système. Un exemple allégé:

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

Après 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 fichier dev contient ces informations:

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

C'est le noeud majeur/mineur. Vous pouvez effectuer une recherche dans le répertoire /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 répertoire /sys/class/tty contient tous les périphériques ATS, mais vous souhaiterez peut-être exclure ces terminaux virtuels et pseudo-terminaux embêtants. Je vous suggère d’examiner uniquement ceux qui ont une entrée device/driver:

# 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/
66
A.H.

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 de votre système. Ce sont en fait des liens symboliques pointant vers le bon/dev/node:

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

Comme vous pouvez le constater, il s'agit d'un adaptateur USB-série. Notez que lorsqu'il n'y a pas de ports série sur le système, le répertoire/dev/serial/n'existe pas. J'espère que cela t'aides :).

23
flu0

Je fais quelque chose comme le code suivant. Cela fonctionne aussi bien pour les périphériques USB que pour les stupides problèmes de série 8250 que nous avons tous sur 30, mais seuls quelques-uns fonctionnent vraiment.

Fondamentalement, j'utilise le concept des réponses précédentes. Commencez par énumérer tous les périphériques tty dans/sys/class/tty /. Les périphériques ne contenant pas de sous-répertoire/device sont filtrés./sys/class/tty/console est un tel périphérique. Ensuite, les périphériques contenant réellement un périphérique sont alors acceptés comme port série valide en fonction de la cible du driver-symlink 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 des sondes 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 signalant un type de périphérique valide est valide.

La source complète pour énumérer les ports 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;   
}
12
Søren Holm

Je pense avoir trouvé la réponse dans la documentation relative à la source du noyau: /Usr/src/linux-2.6.37-rc3/Documentation/systèmes de fichiers/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

11
mk2

J'ai trouvé

dmesg | grep tty

faire le travail.

5
RealHuman75

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

3
hrickards

Je n'ai pas de périphérique série USB, mais il doit exister un moyen de trouver les ports réels à l'aide des bibliothèques HAL directement:

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

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

Le code python-dbus publié, ni ce script sh ne répertorie les périphériques bluetooth/dev/rfcomm *, ce n’est donc pas la meilleure solution.

Notez que sur les autres plates-formes Unix, les ports série ne sont pas nommés ttyS? et même sous Linux, certaines cartes série vous permettent de nommer les périphériques. En supposant qu'un motif dans les noms de périphériques série est incorrect. 

2
kelk1

L'utilisation de/proc/tty/drivers indique uniquement quels pilotes tty sont chargés. Si vous recherchez une liste des ports série, consultez/dev/serial, il y aura deux sous-répertoires: by-id et 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

Merci à ce post: https://superuser.com/questions/131044/how-do-i-know-which-dev-ttys-is-my-serial-port

2
blarf

Je n'ai pas de périphérique série pour le tester, mais si vous avez python et dbus, vous pouvez l'essayer vous-même.

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 cela échoue, vous pouvez chercher dans hwmanager_i.GetAllDevicesWithProperties() pour voir si le nom de capacité "série" que je viens de deviner a un nom différent.

HTH

2
baol

Mon approche via groupe dialout pour obtenir chaque tty avec le 'dialout' de l'utilisateur ls -l /dev/tty* | grep 'dialout' ne récupérer que son dossier ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev

écouter facilement la sortie tty, par exemple quand arduino serial out: head --lines 1 < /dev/ttyUSB0

écoutez chaque tty pour une seule ligne: for i in $(ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev); do head --lines 1 < $i; done

J'aime beaucoup l'approche via la recherche de pilotes: ll /sys/class/tty/*/device/driver

Vous pouvez choisir le nom de tty maintenant: ls /sys/class/tty/*/device/driver | grep 'driver' | cut -d "/" -f 5

0
McPeppr

La bibliothèque du gestionnaire de communication série possède de nombreuses API et fonctionnalités ciblées pour la tâche souhaitée. Si le périphérique est un USB-UART, son VID/PID peut être utilisé. Si le périphérique est BT-SPP, des API spécifiques à une plate-forme peuvent être utilisées. Jetez un coup d’œil à ce projet pour la programmation par port série: https://github.com/RishiGupta12/serial-communication-manager

0
samuel05051980