Hoe vind je alle seriële apparaten (ttyS, ttyUSB, ..) op Linux zonder ze te openen?

Wat is de juiste manier om een ​​lijst te krijgen van alle beschikbare seriële poorten/apparaten op een Linux-systeem?

Met andere woorden, als ik alle apparaten in /dev/doorloop, hoe weet ik dan op de klassieke manier welke seriële poorten zijn, dat wil zeggen diegene die gewoonlijk baudrates ondersteunen en RTS/CTSstroomregeling?

De oplossing zou worden gecodeerd in C.

Ik vraag het omdat ik een bibliotheek van derden gebruik die dit duidelijk verkeerd doet: het lijkt alleen te herhalen over /dev/ttyS*. Het probleem is dat er bijvoorbeeld seriële poorten zijn via USB (geleverd door USB-RS232-adapters), en die worden vermeld onder /dev/ttyUSB*. En als ik de Serial-HOWTO op Linux.orglees, krijg ik het idee dat er na verloop van tijd ook andere naamruimten zullen zijn.

Dus ik moet de officiële manier vinden om seriële apparaten te detecteren. Het probleem is dat niets gedocumenteerd lijkt te zijn, of ik kan het niet vinden.

Ik stel me voor dat een manier zou zijn om alle bestanden van /dev/tty*te openen en een specifieke ioctl()erop aan te roepen die alleen beschikbaar is op seriële apparaten. Zou dat echter een goede oplossing zijn?

Bijwerken

hrickardsstelde voor om naar de bron te kijken voor “setserial”.
De code doet precies wat ik in gedachten had:

Eerst opent het een apparaat met:

fd = open (path, O_RDWR | O_NONBLOCK)

Vervolgens roept het aan:

ioctl (fd, TIOCGSERIAL, &serinfo)

Als die oproep geen fout retourneert, is het blijkbaar een serieel apparaat.

Ik heb vergelijkbare code gevonden in Serial Programming/termios, die voorstelde om ook de optie O_NOCTTYtoe te voegen.

Er is echter één probleem met deze aanpak:

Toen ik deze code testte op BSD Unix (dat wil zeggen, Mac OS X), werkte deze ook. Echterseriële apparaten die worden geleverd via Bluetooth zorgen ervoor dat het systeem (stuurprogramma) probeert verbinding te maken met het Bluetooth-apparaat, wat even duurt voordat het terugkeert met een time-out fout. Dit wordt veroorzaakt door het apparaat gewoon te openen. En ik kan me voorstellen dat soortgelijke dingen ook op Linux kunnen gebeuren – idealiter zou ik het apparaat niet hoeven te openen om het type te achterhalen. Ik vraag me af of er ook een manier is om ioctl-functies aan te roepen zonder een open, of een apparaat te openen op een manier dat er geen verbindingen worden gemaakt?

Wat moet ik doen?


Antwoord 1, autoriteit 100%

Het /sysbestandssysteem zou voldoende informatie moeten bevatten voor je zoektocht. Mijn systeem (2.6.32-40-generiek #87-Ubuntu) suggereert:

/sys/class/tty

Hiermee krijgt u beschrijvingen van alle TTY-apparaten die bij het systeem bekend zijn. Een ingekort voorbeeld:

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

Een van deze links volgen:

# 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

Hier bevat het bestand devdeze informatie:

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

Dit is het hoofd-/kleinknooppunt. Deze kunnen worden doorzocht in de /devdirectory om gebruiksvriendelijke namen te krijgen:

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

De /sys/class/ttymap bevat alle TTY-apparaten, maar misschien wilt u die vervelende virtuele terminals en pseudo-terminals uitsluiten. Ik raad u aan alleen die te onderzoeken die een device/driver-invoer hebben:

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

Antwoord 2, autoriteit 36%

In recente kernels (niet zeker sinds wanneer) kun je de inhoud van /dev/serial weergeven om een ​​lijst te krijgen van de seriële poorten op je systeem. Het zijn eigenlijk symbolische links die naar de juiste /dev/ node verwijzen:

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

Dit is een USB-seriële adapter, zoals u kunt zien. Merk op dat wanneer er geen seriële poorten op het systeem zijn, de /dev/serial/ directory niet bestaat. Ik hoop dat dit helpt :).


Antwoord 3, autoriteit 17%

Ik doe zoiets als de volgende code. Het werkt voor USB-apparaten en ook voor de stomme serial8250-apparaten waarvan we er allemaal 30 hebben – maar slechts een paar werken echt.

In principe gebruik ik het concept uit eerdere antwoorden. Noem eerst alle tty-apparaten in /sys/class/tty/. Apparaten die geen /device-submap bevatten, worden weggefilterd. /sys/class/tty/console is zo’n apparaat. Vervolgens worden de apparaten die daadwerkelijk een apparaat bevatten, geaccepteerd als geldige seriële poort, afhankelijk van het doel van de 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

en voor 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

Alle stuurprogramma’s die door serial8250 worden aangestuurd, moeten probes zijn die de eerder genoemde ioctl gebruiken.

       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

Alleen poorten die een geldig apparaattype rapporteren, zijn geldig.

De volledige bron voor het opsommen van de seriële poorten ziet er als volgt uit. Aanvullingen zijn welkom.

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

Antwoord 4, autoriteit 16%

Ik heb gevonden

dmesg | grep tty

het werk doen.


Antwoord 5, autoriteit 15%

Ik denk dat ik het antwoord heb gevonden in mijn kernelbrondocumentatie:
/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 

Hier is een link naar dit bestand:
http://git.kernel.org/?P=LINUX/kernel/git/NEXT/LINUX-NEXT.GIT;A=Blob_PlainImiMSF=Documentation/Filesystems/Proc.txt;HB=E8883F8057C0F7C9950FA9F20568F37BFA62F34A


6, Autoriteit 2%

De bibliotheek voor seriële communicatiebeheer heeft veel API’s en functies die zijn gericht op de taak die u wilt. Als het apparaat een USB-UART is, kan zijn VID/PID worden gebruikt. Als het apparaat BT-SPP is, kunnen platformspecifieke API’s worden gebruikt. Bekijk dit project voor het programmeren van seriële poorten: https://github.com/RishiGupta12/serial -communicatiemanager


Antwoord 7

ja, ik weet het, ik ben te laat (zoals altijd). Hier is mijn stukje code (gebaseerd op het antwoord van mk2). Misschien helpt dit iemand:

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

Other episodes