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

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

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

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

Я прошу, потому что я использую стороннюю библиотеку, которая делает это явно неправильным: она, кажется, перебирает только /dev/ttyS*. Проблема в том, что есть, например, последовательные порты через USB (предоставляются адаптерами USB-RS232), и они перечислены в разделе /​​dev/ttyUSB *. И, прочитав Serial-HOWTO на Linux.org, я понял, что с появлением других пространств имен также будет время.

Поэтому мне нужно найти официальный способ обнаружения последовательных устройств. Проблема в том, что никто не документируется, или я не могу его найти.

Я предполагаю, что одним из способов было бы открыть все файлы из /dev/tty* и вызвать для них конкретный ioctl(), который доступен только на последовательных устройствах. Будет ли это хорошим решением?

Update

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

Файловая система /sys должна содержать много информации для вашего квеста. Моя система (2.6.32-40-generiС# 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

Это основной/младший node. Их можно найти в каталоге /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/

Ответ 2

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

[email protected]:~$ 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/
[email protected]:~$ 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
[email protected]:~$ 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

Я думаю, что нашел ответ в моей исходной документации ядра: /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 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  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

Ответ 4

Я делаю что-то вроде следующего кода. Он работает на 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;   
}

Ответ 6

У меня нет серийного устройства, чтобы проверить его, но если у вас есть 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", которое я только что догадался, другое имя.

НТН

Ответ 7

У меня нет последовательного 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 script перечислены устройства bluetooth/dev/rfcomm *, поэтому это не лучшее решение.

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

Ответ 8

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

Пример:

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

Благодаря этому сообщению: https://superuser.com/questions/131044/how-do-i-know-which-dev-ttys-is-my-serial-port

Ответ 9

Я нашел

dmesg | grep tty

выполнение задания.

Ответ 10

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

Ответ 11

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

легко прослушать вывод tty, например. когда серийный выход arduino: 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