Как найти все последовательные устройства (ttyS, ttyUSB, ..) в Linux, не открывая их?

113

Как правильно получить список всех доступных последовательных портов / устройств в системе Linux?

Другими словами, когда я перебираю все устройства /dev/, как мне классическим способом определить, какие из них являются последовательными, то есть те, которые обычно поддерживают скорость передачи данных и управление потоком RTS / CTS ?

Решение будет закодировано на C.

Я спрашиваю, потому что я использую стороннюю библиотеку, которая явно ошибается: кажется, что она только повторяется /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)

Если этот вызов не возвращает ошибки, значит, это, по-видимому, последовательное устройство.

Я нашел похожий код в Serial Programming / termios , который также предложил добавить эту 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/
AH
источник
@entalpi Вы найдете /dev/zero. Вы правда думаете, что это серийное устройство?
AH
Искать в / dev бесполезно, так как у вас уже есть имя в / sys / class / tty (поскольку по умолчанию udev создает узел / dev / DEVNAME). Что вас интересует, так это любая «символическая» ссылка в / dev, указывающая на такое устройство. Это гораздо труднее найти.
xryl669
28

В последних ядрах (не знаю, с каких пор) вы можете перечислить содержимое / dev / serial, чтобы получить список последовательных портов в вашей системе. На самом деле это символические ссылки, указывающие на правильный / 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

Как видите, это адаптер USB-Serial. Обратите внимание, что если в системе нет последовательных портов, каталог / dev / serial / не существует. Надеюсь это поможет :).

грипп0
источник
3
Это функция udev (в частности, ее конфигурация в /lib/udev/rules.d/??-persistent-serial.rules), которая была представлена ​​в 2.5.
ergosys
4
Отличный совет! К сожалению, я не думаю, что это покажет встроенные последовательные порты, только последовательные порты USB (видимые udev при подключении). Я не вижу ничего для / dev / serial в Ubuntu 14 на виртуальной машине VMware (с ttyS0 / COM1, предоставленным виртуальной машиной), а правила udev (60-persistent-serial.rules) смотрят только на устройства udev - Я не думаю, что udev узнает о «встроенных» последовательных портах ttyS *, их нужно будет протестировать с помощью ioctl или аналогичных, как в других ответах.
Reed Hedges
ls / dev / serial / ls: нет доступа к '/ dev / serial /': нет такого файла или каталога Slackware 14.2 current x64
jpka
2
@jpka: Это происходит, если нет последовательного устройства, которое можно найти. Я сделал то же самое, и это сработало. Затем я отключил свое последовательное устройство (FTDI) от USB, и после этого возникла описанная вами ошибка.
Warpspace
13

Я делаю что-то вроде следующего кода. Он работает с USB-устройствами, а также с тупыми serial8250-устройствами, которых у всех нас 30, но только пара из них действительно работает.

В основном я использую концепцию из предыдущих ответов. Сначала перечислите все tty-устройства в / sys / class / tty /. Устройства, не содержащие подкаталог / device, отфильтровываются. / sys / class / tty / console - такое устройство. Затем устройства, фактически содержащие устройства, затем принимаются как действительный последовательный порт в зависимости от цели символической ссылки драйвера 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 06
Спасибо Soren за это, даже мы знаем API и некоторые идеи по этому поводу, но вы сделали действительно хороший Soren, еще раз спасибо.
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

mk2
источник
Да, похоже, это работает. Однако это решение требует, чтобы я прочитал текстовый файл и проанализировал его. Интересно, есть ли что-то лучшее, то есть API, который позволяет мне получать это содержимое в структурированном двоичном формате.
Thomas Tempelmann
9

я нашел

dmesg | grep tty

делаю работу.

РеальныйЧеловеческий75
источник
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() чтобы увидеть, имеет ли имя возможности «серийный», которое я только что предположил, другое имя.

НТН

baol
источник
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 и этот сценарий sh не перечисляют устройства bluetooth / dev / rfcomm *, поэтому это не лучшее решение.

Обратите внимание, что на других платформах unix последовательные порты не называются ttyS? и даже в Linux некоторые последовательные карты позволяют вам присваивать имена устройствам. Предполагать, что шаблон в именах последовательных устройств неверен.

kelk1
источник
Жаль, что HAL был удален из Ubuntu (после 12.04), у него было несколько хороших простых в использовании инструментов. Кто-нибудь знает, есть ли замена вышеперечисленному? Но если вы используете версию / дистрибутив с HAL, это выглядит неплохо.
Reed Hedges
2

Использование / proc / tty / drivers указывает только на то, какие драйверы tty загружены. Если вы ищете список последовательных портов, проверьте / dev / serial, он будет иметь два подкаталога: по идентификатору и по пути.

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, например, при последовательном выходе Arduino: head --lines 1 < /dev/ttyUSB0

прослушивать каждый tty только для одной строки: 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

McPeppr
источник
0

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

Самуэль05051980
источник
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;
}
Cdannebe
источник
Похоже, ваш код анализирует, к чему относится ответ stackoverflow.com/a/4701610/43615 . Если да, не могли бы вы упомянуть об этом в своем ответе?
Томас Темпельманн