Jak znaleźć wszystkie urządzenia szeregowe (ttyS, ttyUSB, ..) w systemie Linux bez ich otwierania?


113

Jaki jest właściwy sposób uzyskania listy wszystkich dostępnych portów szeregowych / urządzeń w systemie Linux?

Innymi słowy, kiedy iteruję po wszystkich urządzeniach, w /dev/jaki sposób mogę stwierdzić, które z nich są portami szeregowymi w klasyczny sposób, to znaczy te, które zwykle obsługują prędkości transmisji i kontrolę przepływu RTS / CTS ?

Rozwiązanie byłoby zakodowane w C.

Pytam, ponieważ używam biblioteki innej firmy, która robi to wyraźnie źle: wydaje się, że tylko iteruje /dev/ttyS*. Problem polega na tym, że istnieją na przykład porty szeregowe przez USB (dostarczane przez adaptery USB-RS232), a te są wymienione w / dev / ttyUSB *. Czytając Serial-HOWTO na Linux.org , doszedłem do wniosku, że z czasem pojawią się również inne przestrzenie nazw.

Muszę więc znaleźć oficjalny sposób wykrywania urządzeń szeregowych. Problem w tym, że żadna z nich nie jest udokumentowana lub nie mogę jej znaleźć.

Wyobrażam sobie, że jednym ze sposobów byłoby otwarcie wszystkich plików z /dev/tty*i wywołanie określonego ioctl()z nich, który jest dostępny tylko na urządzeniach szeregowych. Czy byłoby to jednak dobre rozwiązanie?

Aktualizacja

hrickards zasugerował przyjrzenie się źródłu "setserial". Jego kod robi dokładnie to, co miałem na myśli:

Najpierw otwiera urządzenie z:

fd = open (path, O_RDWR | O_NONBLOCK)

Następnie wywołuje:

ioctl (fd, TIOCGSERIAL, &serinfo)

Jeśli to wywołanie nie zwróci żadnego błędu, to najwyraźniej jest to urządzenie szeregowe.

Znalazłem podobny kod w Serial Programming / termios , co sugerowało dodanie tej O_NOCTTYopcji.

Jest jednak jeden problem z tym podejściem:

Kiedy testowałem ten kod na BSD Unix (czyli Mac OS X), również zadziałał. Jednak urządzenia szeregowe dostarczane przez Bluetooth powodują, że system (sterownik) próbuje połączyć się z urządzeniem Bluetooth, co zajmuje trochę czasu, zanim zwróci błąd przekroczenia limitu czasu. Jest to spowodowane po prostu otwarciem urządzenia. Mogę sobie wyobrazić, że podobne rzeczy mogą się zdarzyć również w Linuksie - w idealnym przypadku nie powinienem otwierać urządzenia, aby określić jego typ. Zastanawiam się, czy istnieje również sposób wywoływania ioctlfunkcji bez otwierania, czy też otwierania urządzenia w taki sposób, że nie powoduje to nawiązywania połączeń?

Co powinienem zrobić?


1
Ktoś anonimowy zasugerował tę edycję, która została odrzucona, więc zostawiam ją tutaj jako komentarz: Jeśli używasz flagi TIOCGSERIAL w wywołaniu ioctl zamiast TIOCMGET, to wywołanie nie zwraca błędu z niektórymi niewłaściwymi ścieżkami, które nie odnoszą się do portu COM (szeregowego). Z flagą TIOCMGET, ioctl działa tylko z portami COM dostępnymi w możliwych ścieżkach TTY i TTYUSB.
Thomas Tempelmann

Odpowiedzi:


78

System /sysplików powinien zawierać dużo informacji dla twojego zadania. Mój system (2.6.32-40-generic # 87-Ubuntu) sugeruje:

/sys/class/tty

Który zawiera opisy wszystkich urządzeń TTY znanych w systemie. Skrócony przykład:

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

Po jednym z tych linków:

# 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

Tutaj devplik zawiera te informacje:

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

To jest węzeł główny / pomocniczy. Można je przeszukiwać w /devkatalogu, aby uzyskać nazwy przyjazne dla użytkownika:

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

Katalog /sys/class/ttyzawiera wszystkie urządzenia TTY, ale możesz chcieć wykluczyć te nieznośne wirtualne terminale i pseudoterminale. Proponuję zbadać tylko te, które mają device/driverwpis:

# 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 Znajdziesz /dev/zero. Czy naprawdę myślisz, że to urządzenie szeregowe?
AH

Wyszukiwanie w / dev jest bezużyteczne, ponieważ masz już nazwę w / sys / class / tty (ponieważ domyślnie udev tworzy węzeł / dev / DEVNAME). To, co cię interesuje, to każdy link „symboliczny” w / dev, który wskazuje na takie urządzenie. To znacznie trudniejsze do znalezienia.
xryl669

28

W najnowszych jądrach (nie jestem pewien od kiedy) możesz wyświetlić zawartość / dev / serial, aby uzyskać listę portów szeregowych w twoim systemie. W rzeczywistości są to dowiązania symboliczne wskazujące na właściwy / 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

Jak widać, to adapter USB-Serial. Zauważ, że jeśli w systemie nie ma portów szeregowych, katalog / dev / serial / nie istnieje. Mam nadzieję że to pomoże :).


3
Jest to funkcja udev (w szczególności jego konfiguracja w /lib/udev/rules.d/??-persistent-serial.rules), która została wprowadzona w 2.5.
ergosys

4
Świetna wskazówka! Niestety nie sądzę, aby to pokazało wbudowane porty szeregowe, tylko porty szeregowe USB (widoczne przez udev po podłączeniu). Nie widzę nic dla / dev / serial w Ubuntu 14 na VMware VM (z ttyS0 / COM1 dostarczonym przez VM), a reguły udev (60-persistent-serial.rules) dotyczą tylko urządzeń udev - Nie sądzę, aby udev dowiedział się o „wbudowanych” portach szeregowych ttyS *, będą one musiały zostać przetestowane za pomocą ioctl lub podobnego, jak w innych odpowiedziach.
Reed Hedges

ls / dev / serial / ls: nie można uzyskać dostępu do '/ dev / serial /': Brak takiego pliku lub katalogu Slackware 14.2 bieżący x64
jpka

2
@jpka: Dzieje się tak, jeśli nie ma urządzenia szeregowego do znalezienia. Zrobiłem jak wyżej i zadziałało. Następnie odłączyłem moje urządzenie szeregowe (FTDI) od USB, a następnie wyświetliło się błąd, który opisałeś.
Warpspace

13

Robię coś podobnego do następującego kodu. Działa z urządzeniami USB, a także z głupimi urządzeniami Serial8250, których wszyscy mamy 30 - ale tylko kilka z nich naprawdę działa.

Zasadniczo używam koncepcji z poprzednich odpowiedzi. Najpierw wylicz wszystkie urządzenia tty w / sys / class / tty /. Urządzenia, które nie zawierają podkatalogu / device, są odfiltrowywane. Takim urządzeniem jest / sys / class / tty / console. Następnie urządzenia faktycznie zawierające urządzenia są następnie akceptowane jako prawidłowy port szeregowy w zależności od celu sterownika-dowiązania symbolicznego 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

i dla 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

Wszystkie sterowniki sterowane przez serial8250 muszą być sondami używającymi wspomnianego wcześniej 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

Prawidłowy jest tylko port zgłaszający prawidłowy typ urządzenia.

Pełne źródło do wyliczenia portów szeregowych wygląda następująco. Uzupełnienia są mile widziane.

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

Samotne łącze jest uważane za kiepską odpowiedź, ponieważ samo w sobie jest bez znaczenia, a docelowy zasób nie gwarantuje, że będzie żył w przyszłości. Spróbuj dołączyć przynajmniej podsumowanie informacji, do których tworzysz łącze.
j0k

Dzięki Soren za to, nawet my znamy interfejsy API i trochę pomysłów na to, ale zrobiłeś naprawdę dobry Soren, jeszcze raz dziękuję.
ind79ra

12

Myślę, że znalazłem odpowiedź w dokumentacji źródła mojego jądra: /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 

Oto link do tego pliku: http://git.kernel.org/?p=linux/kernel/git/next/linux-next.git;a=blob_plain;f=Documentation/filesystems/proc.txt;hb = e8883f8057c0f7c9950fa9f20568f37bfa62f34a


Tak, wygląda na to, że działa. Jednak to rozwiązanie wymaga odczytania pliku tekstowego i przeanalizowania go. Zastanawiam się, czy jest coś lepszego, tj. API, które pozwala mi pobrać te treści w ustrukturyzowanym formacie binarnym.
Thomas Tempelmann


3

setserial z opcją -g wydaje się robić to, co chcesz, a źródło C jest dostępne pod adresem http://www.koders.com/c/fid39344DABD14604E70DF1B8FEA7D920A94AF78BF8.aspx .


Spojrzałem na kod i ma wadę, którą wyjaśniam w moim pytaniu na końcu, ponieważ musi otworzyć urządzenie, co może już prowadzić do próby połączenia - co z kolei nie jest dobre. Ale może sterowniki Linuksa są mądrzejsze niż obecny sterownik OSX, jeśli chodzi o obsługę bluetooth, ponieważ nie otworzą połączenia od razu? Kto wie? Może powinienem rozpocząć nowe pytanie, aby to dokładnie wyjaśnić. Jeśli okaże się, że to w porządku, również tutaj przyjmuję twoją odpowiedź. Hmmm ...
Thomas Tempelmann

3

Nie mam tutaj urządzenia szeregowego, aby to przetestować, ale jeśli masz Pythona i dbus, możesz spróbować samemu.

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

Jeśli to się nie powiedzie, możesz przeszukać wewnątrz, hwmanager_i.GetAllDevicesWithProperties()aby sprawdzić, czy nazwa funkcji „serial”, którą właśnie odgadłem, ma inną nazwę.

HTH


2

Nie mam urządzenia szeregowego USB, ale musi istnieć sposób na znalezienie rzeczywistych portów przy użyciu bezpośrednio bibliotek 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

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

Opublikowany kod python-dbus ani ten skrypt sh zawiera listę urządzeń bluetooth / dev / rfcomm *, więc nie jest to najlepsze rozwiązanie.

Zauważ, że na innych platformach unix porty szeregowe nie mają nazw ttyS? a nawet w Linuksie niektóre karty szeregowe pozwalają na nazwanie urządzeń. Zakładanie, że wzór w nazwach urządzeń szeregowych jest nieprawidłowy.


Szkoda, że ​​HAL został usunięty z Ubuntu (po 12.04), miał kilka fajnych, łatwych w użyciu narzędzi. Czy ktoś wie, czy jest zamiennik do powyższego? Ale jeśli masz wersję / dystrybucję, która ma HAL, wygląda to ładnie.
Reed Hedges

2

Użycie / proc / tty / drivers wskazuje tylko, które sterowniki tty są ładowane. Jeśli szukasz listy portów szeregowych, sprawdź / dev / serial, będzie ona zawierać dwa podkatalogi: według identyfikatora i ścieżki.

DAWNY:

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

Dzięki temu postowi: /superuser/131044/how-do-i-know-which-dev-ttys-is-my-serial-port


Najwyraźniej jest to zależne od dystrybucji. Nie mogę znaleźć / dev / serial na moim komputerze (z systemem Debian)
SimonC,

0

Moje podejście poprzez grupowe wybieranie numerów, aby każdy terminal z 'dialoutem' użytkownika pobierał ls -l /dev/tty* | grep 'dialout' tylko swój folder ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev

łatwe odsłuchiwanie wyjścia tty np. przy wyjściu szeregowym arduino: head --lines 1 < /dev/ttyUSB0

słuchaj każdego tty tylko dla jednej linii: for i in $(ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev); do head --lines 1 < $i; done

Bardzo podoba mi się podejście polegające na szukaniu sterowników: ll /sys/class/tty/*/device/driver

Możesz teraz wybrać nazwę tty: ls /sys/class/tty/*/device/driver | grep 'driver' | cut -d "/" -f 5


0

Biblioteka menedżera komunikacji szeregowej ma wiele interfejsów API i funkcji przeznaczonych dla żądanego zadania. Jeśli urządzenie to USB-UART, można użyć jego VID / PID. Jeśli urządzenie jest zgodne z BT-SPP, można używać interfejsów API specyficznych dla platformy. Spójrz na ten projekt dotyczący programowania portu szeregowego: https://github.com/RishiGupta12/serial-communication-manager


0

tak, wiem, spóźniłem się (jak zawsze). Oto mój fragment kodu (na podstawie odpowiedzi mk2). Może to komuś pomoże:

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

Wygląda na to, że Twój kod analizuje, do czego odnosi się odpowiedź stackoverflow.com/a/4701610/43615 . Jeśli tak, czy mógłbyś wspomnieć o tym w swojej odpowiedzi?
Thomas Tempelmann
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.