Як знайти всі послідовні пристрої (ttyS, ttyUSB, ..) в Linux, не відкриваючи їх?


113

Який правильний спосіб отримати список усіх доступних послідовних портів / пристроїв в системі Linux?

Іншими словами, коли я перебираю всі пристрої в /dev/, як я можу сказати, які з них є послідовними портами класичним способом, тобто тими, що зазвичай підтримують швидкість передачі даних та контроль потоку RTS / CTS ?

Розчин буде закодовано в С.

Я запитую, тому що я використовую сторонні бібліотеки, які роблять це явно неправильно: це, здається, лише повторюється /dev/ttyS*. Проблема полягає в тому, що є, наприклад, послідовні порти через USB (надаються адаптерами USB-RS232), і вони перелічені у розділі / dev / ttyUSB *. І читаючи Serial-HOWTO на Linux.org , я здогадуюсь, що з часом з'являться й інші простори імен.

Тому мені потрібно знайти офіційний спосіб виявлення серійних пристроїв. Проблема полягає в тому, що жоден не видається задокументованим, або я не можу його знайти.

Я думаю, що одним із способів було б відкрити всі файли /dev/tty*та викликати певний ioctl()на них, який доступний лише на послідовних пристроях. Невже це буде хорошим рішенням?

Оновлення

hrickards запропонував подивитися джерело для "setserial". Його код робить саме те, що я мав на увазі:

По-перше, він відкриває пристрій з:

fd = open (path, O_RDWR | O_NONBLOCK)

Потім він викликає:

ioctl (fd, TIOCGSERIAL, &serinfo)

Якщо цей виклик не повертає помилок, мабуть, це послідовний пристрій.

Я знайшов аналогічний код у " Послідовне програмування / терміни" , який запропонував також додати O_NOCTTYпараметр.

Однак є одна проблема з таким підходом:

Коли я тестував цей код на BSD Unix (тобто Mac OS X), він також працював. Однак послідовні пристрої, що надаються через Bluetooth, змушують систему (драйвер) намагатися підключитися до пристрою Bluetooth, який проходить деякий час, перш ніж він повернеться з помилкою очікування. Це викликано просто відкриттям пристрою. І я можу собі уявити, що подібні речі можуть траплятися і в Linux - в ідеалі мені не потрібно відкривати пристрій, щоб визначити його тип. Цікаво, чи існує також спосіб викликати ioctlфункції без відкритого чи відкрити пристрій таким чином, щоб він не викликав з'єднання?

Що я повинен зробити?


1
Хтось анонім запропонував це редагування, яке було відхилено, тому я залишаю його тут як коментар: Якщо ви використовуєте прапор TIOCGSERIAL у виклику ioctl, а не TIOCMGET, тоді виклик не повертає помилку з деякими неправильними шляхами, які не виконують зверніться до COM (послідовного) порту. З прапором TIOCMGET, ioctl працює лише з COM-портами, доступними для доступу в можливих шляхах TTY та TTYUSB.
Томас Темпельман

Відповіді:


78

/sysФайлова система повинна містити досхочу інформацію для ваших пошуків. Моя система (2.6.32-40-generic # 87-Ubuntu) пропонує:

/sys/class/tty

Це дає опис усіх відомих системі TTY пристроїв. Приклад підстрижений:

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

Перейшовши за одним із цих посилань:

# 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

Тут devфайл містить цю інформацію:

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

Це основний / другорядний вузол. Їх можна шукати в /devкаталозі, щоб отримати зручні імена:

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

Режим /sys/class/ttyмістить усі пристрої TTY, але ви можете виключити ці примхливі віртуальні термінали та псевдотермінали. Я пропоную вам вивчити лише ті, які мають 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/

@entalpi Ви знайдете /dev/zero. Ви справді думаєте, це серійний пристрій?
AH

Пошук в / dev марний, оскільки ви вже маєте ім'я в / sys / class / tty (як за замовчуванням udev створює вузол / dev / DEVNAME). Вас цікавить будь-яке "символічне" посилання в / dev, яке вказує на такий пристрій. Це набагато складніше знайти.
xryl669

28

В останніх ядрах (не впевнений, відколи) ви можете перелічити вміст / dev / serial, щоб отримати список послідовних портів у вашій системі. Вони насправді є посиланнями, що вказують на правильний / dev / вузол:

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

Це USB-послідовний адаптер, як ви бачите. Зауважте, що коли в системі немає послідовних портів, / / ​​dev / serial / каталог не існує. Сподіваюся, це допомагає :).


3
Це функція udev (конкретно її конфігурація в /lib/udev/rules.d/??-persistent-serial.rules), яка була введена в 2.5.
ergosys

4
Чудова порада! На жаль, я не думаю, що це буде показувати вбудовані в послідовні порти, лише USB-послідовні порти (бачити udev, коли вони додаються). Я не бачу нічого для / dev / serial в Ubuntu 14 у VMware VM (з ttyS0 / COM1, що постачається VM), а правила udev (60-стійкі-serial.rules) дивляться лише на пристрої udev - Я не думаю, що udev дізнається про "вбудовані" послідовні порти ttyS *, їх доведеться перевірити на ioctl або подібне, як в інших відповідях.
Рід Хеджес

ls / dev / serial / ls: не вдається отримати доступ / / dev / serial / ': Немає такого файлу чи каталогу Slackware 14.2 current x64
jpka

2
@jpka: Це станеться, якщо не знайдено серійного пристрою. Я зробив, як вище, і це спрацювало. Потім я від'єднав від USB-серійного пристрою (FTDI) і після цього він видав описану вами помилку.
Warpspace

13

Я роблю щось на кшталт наступного коду. Він працює для USB-пристроїв, а також дурних serial8250-devuices, яких у нас усього 30 - але лише пара з них справді працює.

В основному я використовую концепцію з попередніх відповідей. Спочатку перерахуйте всі пристрої tty в / sys / class / tty /. Пристрої, які не містять підкаталог / пристрій, відфільтровано. / sys / class / tty / console - це такий пристрій. Тоді пристрої, що фактично містять пристрої, тоді приймаються як дійсний послідовний порт залежно від цілі драйвера-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

і для 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

Усі драйвери, керовані serial8250, повинні мати зонди, використовуючи раніше згаданий 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

Дійсний лише порт, який повідомляє про тип пристрою.

Повне джерело для перерахування серійних портів виглядає приблизно так. Доповнення вітаються.

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

Одиноке посилання вважається поганою відповіддю, оскільки воно само по собі безглуздо і цільовий ресурс не гарантовано залишиться живим у майбутньому. Будь ласка, спробуйте включити принаймні резюме інформації, до якої ви посилаєтесь.
j0k

Завдяки Сорен за це, навіть ми знаємо API та деяку ідею щодо нього, але ви справді добрі Сорен, ще раз дякую.
ind79ra

12

Я думаю, що я знайшов відповідь у своїй вихідній документації на ядро: /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 

Ось посилання на цей файл: http://git.kernel.org/?p=linux/kernel/git/next/linux-next.git;a=blob_plain;f=Documentation/filesystems/proc.txt;hb = e8883f8057c0f7c9950fa9f20568f37bfa62f34a


Так, це, здається, працює. Однак це рішення вимагає від мене прочитати текстовий файл та проаналізувати його. Цікаво, чи немає кращого, тобто API, який дозволяє мені отримувати цей вміст у структурованому бінарному форматі.
Томас Темпельман


3

setserial з опцією -g, як видається, робить те, що ви хочете, і джерело C доступно за посиланням http://www.koders.com/c/fid39344DABD14604E70DF1B8FEA7D920A94AF78BF8.aspx .


Я подивився на код і у нього є недолік, який я пояснюю у своєму запитанні наприкінці, оскільки він повинен відкрити пристрій, що вже може призвести до спроби підключення - що, в свою чергу, не годиться. Але тоді, можливо, драйвери Linux розумніші за поточний драйвер OSX, коли мова йде про підтримку Bluetooth, оскільки вони не відкриють з'єднання відразу? Хто знає? Можливо, я повинен почати нове запитання, щоб конкретно уточнити це. Якщо виявиться, що це добре, то я можу прийняти і вашу відповідь тут. Гммм ...
Томас Темпельман

3

У мене немає серійного пристрою для тестування, але якщо у вас є python та dbus, ви можете спробувати його самостійно.

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

Якщо це не вдається, ви можете шукати всередині, hwmanager_i.GetAllDevicesWithProperties()щоб побачити, чи є назва функції "serial", про яку я тільки здогадався, має іншу назву.

HTH


2

У мене немає USB-послідовного пристрою, але повинен бути спосіб знайти справжні порти, використовуючи безпосередньо бібліотеки 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

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

Опублікований код python-dbus, ні цей скрипт, перелічені пристрої bluetooth / dev / rfcomm *, тому це не найкраще рішення.

Зауважте, що на інших платформах unix послідовні порти не мають імені ttyS? і навіть в Linux, деякі серійні карти дозволяють називати пристрої. Припустимо, що візерунок у назвах серійних пристроїв невірний.


Надто поганий HAL був видалений з Ubuntu (після 12.04), він мав кілька приємних простих у використанні інструментів. Хтось знає, чи є заміна вищезазначеного? Але якщо ви користуєтеся версією / дистрибутивом, який має HAL, це виглядає добре.
Рід Хеджес

2

Використання / proc / tty / driver лише вказує, які завантажуються драйвери tty. Якщо ви шукаєте список перевірки послідовних портів / dev / serial, він матиме два підкаталоги: by-id та 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

Завдяки цьому допису: /superuser/131044/how-do-i-know-which-dev-ttys-is-my-serial-port


Мабуть, це залежить від дистрофії. Я не можу знайти / dev / serial на своїй коробці (під керуванням Debian)
SimonC

0

Мій підхід за допомогою групового діалоту, щоб отримати кожну програму з діалотом користувача, ls -l /dev/tty* | grep 'dialout' щоб отримати лише її папку ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev

легко слухати вихід Tty, наприклад, коли ардуїно серійно виходить: head --lines 1 < /dev/ttyUSB0

прослуховуйте кожний тти лише для одного рядка: for i in $(ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev); do head --lines 1 < $i; done

Мені дуже подобається підхід через пошук водіїв: ll /sys/class/tty/*/device/driver

Ви можете вибрати tty-Name зараз: ls /sys/class/tty/*/device/driver | grep 'driver' | cut -d "/" -f 5


0

Бібліотека менеджера послідовних комунікацій має безліч API та функцій, орієнтованих на потрібне завдання. Якщо пристрій USB-UART, його можна використовувати VID / PID. Якщо пристрій BT-SPP, можна використовувати API, визначені для платформи. Погляньте на цей проект програмування послідовних портів: https://github.com/RishiGupta12/serial-communication-manager


0

так, я знаю, я запізнився (як завжди). Ось мій фрагмент коду (на основі відповіді mk2). Можливо, це комусь допомагає:

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

Здається, ваш код аналізує те, на що відповідає відповідь stackoverflow.com/a/4701610/43615 . Якщо так, чи не зазначаєте ви це у своїй відповіді?
Томас Темпельман
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.