Come trovare tutti i dispositivi seriali (ttyS, ttyUSB, ..) su Linux senza aprirli?


113

Qual è il modo corretto per ottenere un elenco di tutte le porte / dispositivi seriali disponibili su un sistema Linux?

In altre parole, quando itero su tutti i dispositivi in /dev/, come faccio a sapere quali sono le porte seriali nel modo classico, cioè quelle che di solito supportano velocità di trasmissione e controllo di flusso RTS / CTS ?

La soluzione sarebbe codificata in C.

Lo chiedo perché sto usando una libreria di terze parti che lo fa chiaramente in modo sbagliato: sembra solo iterare /dev/ttyS*. Il problema è che ci sono, ad esempio, porte seriali su USB (fornite da adattatori USB-RS232) e quelle sono elencate in / dev / ttyUSB *. E leggendo il Serial-HOWTO su Linux.org , ho l'idea che ci saranno anche altri spazi per i nomi, col tempo.

Quindi devo trovare il modo ufficiale per rilevare i dispositivi seriali. Il problema è che nessuno sembra essere documentato o non riesco a trovarlo.

Immagino che un modo sarebbe aprire tutti i file da /dev/tty*e chiamare uno specifico ioctl()su di essi che è disponibile solo su dispositivi seriali. Sarebbe una buona soluzione, però?

Aggiornare

hrickards ha suggerito di guardare il sorgente di "setserial". Il suo codice fa esattamente quello che avevo in mente:

Innanzitutto, apre un dispositivo con:

fd = open (path, O_RDWR | O_NONBLOCK)

Quindi invoca:

ioctl (fd, TIOCGSERIAL, &serinfo)

Se quella chiamata non restituisce errori, a quanto pare si tratta di un dispositivo seriale.

Ho trovato un codice simile in Serial Programming / termios , che ha suggerito di aggiungere anche l' O_NOCTTYopzione.

Tuttavia, c'è un problema con questo approccio:

Quando ho testato questo codice su BSD Unix (ovvero, Mac OS X), ha funzionato anche lui. Tuttavia , i dispositivi seriali forniti tramite Bluetooth fanno sì che il sistema (driver) tenti di connettersi al dispositivo Bluetooth, che richiede un po 'di tempo prima che si ritorni con un errore di timeout. Ciò è causato dalla semplice apertura del dispositivo. E posso immaginare che cose simili possano accadere anche su Linux - idealmente, non dovrei aver bisogno di aprire il dispositivo per capire il suo tipo. Mi chiedo se esiste anche un modo per richiamare ioctlfunzioni senza un open, o aprire un dispositivo in modo tale da non causare connessioni?

Cosa dovrei fare?


1
Qualcuno anonimo aveva suggerito questa modifica, che è stata rifiutata, quindi la lascio qui come commento invece: se usi il flag TIOCGSERIAL nella chiamata ioctl, invece di TIOCMGET, la chiamata non restituisce errori con alcuni percorsi sbagliati che non lo fanno fare riferimento a una porta COM (seriale). Con il flag TIOCMGET, ioctl funziona solo con le porte COM disponibili per l'accesso nei percorsi possibili sia TTY che TTYUSB.
Thomas Tempelmann

Risposte:


78

Il /sysfilesystem dovrebbe contenere molte informazioni per la tua ricerca. Il mio sistema (2.6.32-40-generic # 87-Ubuntu) suggerisce:

/sys/class/tty

Che fornisce le descrizioni di tutti i dispositivi TTY noti al sistema. Un esempio ridotto:

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

Seguendo uno di questi link:

# 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

Qui il devfile contiene queste informazioni:

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

Questo è il nodo maggiore / minore. Questi possono essere cercati nella /devdirectory per ottenere nomi facili da usare:

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

La /sys/class/ttydirectory contiene tutti i dispositivi TTY ma potresti voler escludere quei fastidiosi terminali virtuali e pseudo terminali. Ti suggerisco di esaminare solo quelli che hanno una device/drivervoce:

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

@entalpi troverai /dev/zero. Pensi davvero che questo sia un dispositivo seriale?
AH

La ricerca in / dev è inutile, poiché hai già il nome in / sys / class / tty (poiché per impostazione predefinita udev crea il nodo / dev / DEVNAME). Quello che ti interessa è qualsiasi collegamento "simbolico" in / dev che punta a tale dispositivo. Questo è molto più difficile da trovare.
xryl669

28

Nei kernel recenti (non so da quando) puoi elencare il contenuto di / dev / serial per ottenere un elenco delle porte seriali sul tuo sistema. In realtà sono collegamenti simbolici che puntano al nodo / dev / corretto:

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

Questo è un adattatore seriale USB, come puoi vedere. Notare che quando non ci sono porte seriali sul sistema, la directory / dev / serial / non esiste. Spero che questo ti aiuti :).


3
Questa è una funzione di udev (in particolare la sua configurazione in /lib/udev/rules.d/??-persistent-serial.rules), che è stata introdotta nella 2.5.
ergosys

4
Ottimo suggerimento! Sfortunatamente non penso che questo mostrerà porte seriali integrate, solo porte seriali USB (viste da udev quando collegate). Non vedo nulla per / dev / serial in Ubuntu 14 in una VM VMware (con ttyS0 / COM1 fornito dalla VM) e le regole udev (60-persistent-serial.rules) guardano solo ai dispositivi udev - Non credo che udev scopra le porte seriali "built-in" ttyS *, dovranno essere testate con ioctl o simili come nelle altre risposte.
Reed Hedges

ls / dev / serial / ls: impossibile accedere a "/ dev / serial /": nessun file o directory Slackware 14.2 corrente x64
jpka

2
@jpka: succede se non c'è un dispositivo seriale da trovare. Ho fatto come sopra e ha funzionato. Ho quindi scollegato il mio dispositivo seriale (FTDI) dall'USB e successivamente ha prodotto l'errore che hai descritto.
Warpspace

13

Sto facendo qualcosa di simile al seguente codice. Funziona per i dispositivi USB e anche per gli stupidi dispositivi serial8250 di cui tutti abbiamo 30 - ma solo un paio di essi funziona davvero.

Fondamentalmente utilizzo il concetto delle risposte precedenti. Innanzitutto enumera tutti i dispositivi tty in / sys / class / tty /. I dispositivi che non contengono una sottodirectory / device vengono filtrati. / sys / class / tty / console è un tale dispositivo. Quindi i dispositivi che contengono effettivamente un dispositivo vengono quindi accettati come porta seriale valida a seconda della destinazione del 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

e per 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

Tutti i driver pilotati da serial8250 devono essere sonde che utilizzano il già citato 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

Solo la segnalazione della porta di un tipo di dispositivo valido è valida.

La fonte completa per l'enumerazione delle porte seriali è simile a questa. Le aggiunte sono benvenute.

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

Il collegamento solitario è considerato una risposta scadente poiché è privo di significato di per sé e non è garantito che la risorsa di destinazione sia viva in futuro. Prova a includere almeno un riepilogo delle informazioni a cui ti stai collegando.
j0k

Grazie a Soren per questo, anche noi conosciamo le API e qualche idea su di esso ma sei stato davvero bravo Soren, grazie ancora.
ind79ra

12

Penso di aver trovato la risposta nella documentazione del sorgente del 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 

Ecco un collegamento a questo file: http://git.kernel.org/?p=linux/kernel/git/next/linux-next.git;a=blob_plain;f=Documentation/filesystems/proc.txt;hb = e8883f8057c0f7c9950fa9f20568f37bfa62f34a


Sì, sembra funzionare. Tuttavia, questa soluzione mi richiede di leggere un file di testo e di analizzarlo. Mi chiedo se esista una soluzione migliore, ovvero un'API che mi permette di ottenere questi contenuti in un formato binario strutturato.
Thomas Tempelmann


3

setserial con l'opzione -g sembra fare quello che vuoi e il sorgente C è disponibile su http://www.koders.com/c/fid39344DABD14604E70DF1B8FEA7D920A94AF78BF8.aspx .


Ho guardato il codice e ha il difetto che spiego nella mia domanda alla fine in quanto deve aprire il dispositivo, il che potrebbe già portare a un tentativo di connessione - che a sua volta non va bene. Ma poi, forse i driver Linux sono più intelligenti degli attuali driver OSX quando si tratta di supporto Bluetooth, poiché non apriranno subito una connessione? Chissà? Forse dovrei iniziare una nuova domanda per chiarirlo in modo specifico. Se risulta che va bene, posso accettare la tua risposta anche qui. Hmmm ...
Thomas Tempelmann

3

Non ho alcun dispositivo seriale qui per testarlo, ma se hai python e dbus puoi provarlo tu stesso.

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")

Se fallisce puoi cercare all'interno hwmanager_i.GetAllDevicesWithProperties() per vedere se il nome della capacità "seriale" che ho appena indovinato ha un nome diverso.

HTH


2

Non ho un dispositivo seriale USB, ma deve esserci un modo per trovare le porte reali utilizzando direttamente le librerie HAL:

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

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

Il codice python-dbus pubblicato né questo script sh elenca i dispositivi bluetooth / dev / rfcomm *, quindi non è la soluzione migliore.

Notare che su altre piattaforme unix, le porte seriali non si chiamano ttyS? e anche in Linux, alcune schede seriali consentono di nominare i dispositivi. Supponendo che un modello nei nomi dei dispositivi seriali sia sbagliato.


Peccato che HAL sia stato rimosso da Ubuntu (dopo la 12.04), aveva alcuni strumenti facili da usare. Qualcuno sa se c'è una sostituzione a quanto sopra? Ma se sei su una versione / distro che ha HAL, questo sembra carino.
Reed Hedges

2

L'uso di / proc / tty / drivers indica solo quali driver tty sono caricati. Se stai cercando un elenco delle porte seriali controlla / dev / serial, avrà due sottodirectory: by-id e 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

Grazie a questo post: /superuser/131044/how-do-i-know-which-dev-ttys-is-my-serial-port


Apparentemente questo dipende dalla distribuzione. Non riesco a trovare / dev / serial sulla mia macchina (con Debian in esecuzione)
SimonC

0

Il mio approccio tramite dialout di gruppo per ottenere ogni tty con utente "dialout" ls -l /dev/tty* | grep 'dialout' per ottenere solo la sua cartella ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev

ascolta facilmente l'output tty, ad esempio quando l'uscita seriale di arduino: head --lines 1 < /dev/ttyUSB0

ascolta ogni uscita per una sola riga: for i in $(ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev); do head --lines 1 < $i; done

Mi piace molto l'approccio alla ricerca di conducenti: ll /sys/class/tty/*/device/driver

Puoi scegliere il nome tty ora: ls /sys/class/tty/*/device/driver | grep 'driver' | cut -d "/" -f 5


0

La libreria del gestore della comunicazione seriale ha molte API e funzionalità mirate all'attività desiderata. Se il dispositivo è un USB-UART, è possibile utilizzare il suo VID / PID. Se il dispositivo è BT-SPP, è possibile utilizzare API specifiche della piattaforma. Dai un'occhiata a questo progetto per la programmazione della porta seriale: https://github.com/RishiGupta12/serial-communication-manager


0

si, lo so, sono in ritardo (come sempre). Ecco il mio pezzo di codice (basato sulla risposta di mk2). Forse questo aiuta qualcuno:

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

Sembra che il tuo codice analizzi a quale risposta si riferisce stackoverflow.com/a/4701610/43615 . Se è così, lo menzioneresti nella tua risposta, per favore?
Thomas Tempelmann
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.