Como encontrar todos os dispositivos seriais (ttyS, ttyUSB, ..) no Linux sem abri-los?

113

Qual é a maneira correta de obter uma lista de todas as portas / dispositivos seriais disponíveis em um sistema Linux?

Em outras palavras, quando faço a iteração em todos os dispositivos /dev/, como posso saber quais são as portas seriais da maneira clássica, ou seja, aquelas que geralmente oferecem suporte a taxas de transmissão e controle de fluxo RTS / CTS ?

A solução seria codificada em C.

Eu pergunto porque estou usando uma biblioteca de terceiros que faz isso claramente errado: parece apenas repetir /dev/ttyS*. O problema é que existem, por exemplo, portas seriais sobre USB (fornecidas por adaptadores USB-RS232), e essas estão listadas em / dev / ttyUSB *. E lendo o Serial-HOWTO em Linux.org , tenho a idéia de que haverá outros espaços de nomes também, com o tempo.

Portanto, preciso encontrar a maneira oficial de detectar dispositivos seriais. O problema é que nada parece estar documentado ou não consigo encontrar.

Imagino que uma maneira seria abrir todos os arquivos do /dev/tty*e chamar um específico ioctl()sobre eles que está disponível apenas em dispositivos seriais. Isso seria uma boa solução, entretanto?

Atualizar

hrickards sugeriu olhar a fonte para "setserial". Seu código faz exatamente o que eu tinha em mente:

Primeiro, ele abre um dispositivo com:

fd = open (path, O_RDWR | O_NONBLOCK)

Em seguida, ele invoca:

ioctl (fd, TIOCGSERIAL, &serinfo)

Se essa chamada não retornar nenhum erro, então é um dispositivo serial, aparentemente.

Encontrei um código semelhante em Serial Programming / termios , o que sugeriu também adicionar a O_NOCTTYopção.

Porém, há um problema com essa abordagem:

Quando testei esse código no BSD Unix (ou seja, Mac OS X), ele também funcionou. No entanto , os dispositivos seriais fornecidos por meio do Bluetooth fazem com que o sistema (driver) tente se conectar ao dispositivo Bluetooth, o que leva um tempo antes de retornar com um erro de tempo limite. Isso é causado apenas pela abertura do dispositivo. E posso imaginar que coisas semelhantes também possam acontecer no Linux - de preferência, não preciso abrir o dispositivo para descobrir seu tipo. Gostaria de saber se também existe uma maneira de invocar ioctlfunções sem um aberto, ou abrir um dispositivo de uma forma que não faça com que as conexões sejam feitas?

O que devo fazer?

Thomas Tempelmann
fonte
1
Alguém anônimo sugeriu esta edição, que foi rejeitada, então deixo aqui como um comentário: Se você usar o sinalizador TIOCGSERIAL na chamada ioctl, em vez de TIOCMGET, a chamada não retorna erro com alguns caminhos errados que não consulte uma porta COM (serial). Com o sinalizador TIOCMGET, ioctl funciona apenas com as portas COM disponíveis para acessar os caminhos possíveis TTY e TTYUSB.
Thomas Tempelmann

Respostas:

78

O /syssistema de arquivos deve conter muitas informações para sua missão. Meu sistema (2.6.32-40-generic # 87-Ubuntu) sugere:

/sys/class/tty

Que fornece descrições de todos os dispositivos TTY conhecidos pelo sistema. Um exemplo reduzido:

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

Seguindo um destes links:

# 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

Aqui, o devarquivo contém esta informação:

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

Este é o nó principal / secundário. Eles podem ser pesquisados ​​no /devdiretório para obter nomes fáceis de usar:

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

O /sys/class/ttydir contém todos os dispositivos TTY, mas você pode querer excluir aqueles terminais virtuais e pseudo terminais irritantes. Eu sugiro que você examine apenas aqueles que têm uma device/driverentrada:

# 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
fonte
@entalpi você encontrará /dev/zero. Você realmente acha que este é um dispositivo serial?
AH
Pesquisar em / dev é inútil, uma vez que você já tem o nome em / sys / class / tty (já que por padrão o udev cria o nó / dev / DEVNAME). Você está interessado em qualquer link "simbólico" em / dev que aponte para tal dispositivo. Isso é muito mais difícil de encontrar.
xryl669
28

Em kernels recentes (não tenho certeza desde quando) você pode listar o conteúdo de / dev / serial para obter uma lista das portas seriais em seu sistema. Na verdade, eles são links simbólicos que apontam para o / dev / node correto:

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

Este é um adaptador USB-Serial, como você pode ver. Observe que quando não há portas seriais no sistema, o diretório / dev / serial / não existe. Espero que isto ajude :).

flu0
fonte
3
Esta é uma função do udev (especificamente sua configuração em /lib/udev/rules.d/??-persistent-serial.rules), que foi introduzida em 2.5.
ergosys de
4
Ótima dica! Infelizmente não acho que isso mostrará portas seriais integradas, apenas portas seriais USB (vistas pelo udev quando conectadas). Não vejo nada para / dev / serial no Ubuntu 14 em uma VM VMware (com ttyS0 / COM1 fornecido pela VM), e as regras de udev (60-persistent-serial.rules) estão olhando apenas para dispositivos udev - Eu não acho que o udev descubra sobre as portas seriais ttyS * "embutidas", elas terão que ser testadas com ioctl ou similar como nas outras respostas.
Reed Hedges
ls / dev / serial / ls: não é possível acessar '/ dev / serial /': Não
existe
2
@jpka: Isso acontece se não houver um dispositivo serial para localizar. Eu fiz como acima e funcionou. Em seguida, desconectei meu dispositivo serial (FTDI) do USB e depois ele produziu o erro que você descreveu.
Warpspace
13

Estou fazendo algo como o código a seguir. Ele funciona para dispositivos USB e também para os estúpidos dispositivos serial8250 que todos nós temos 30 - mas apenas alguns deles realmente funcionam.

Basicamente, uso o conceito de respostas anteriores. Primeiro enumere todos os dispositivos tty em / sys / class / tty /. Dispositivos que não contêm um subdiretório / device são filtrados. / sys / class / tty / console é um desses dispositivos. Em seguida, os dispositivos que realmente contêm dispositivos são aceitos como porta serial válida, dependendo do destino do driver de link simbólico.

$ 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

e para 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

Todos os drivers acionados pelo serial8250 devem ser probes usando o ioctl mencionado anteriormente.

        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

Apenas a porta que relata um tipo de dispositivo válido é válido.

A fonte completa para enumerar as portas seriais se parece com isto. Adições são bem-vindas.

#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;   
}
Søren Holm
fonte
O link solitário é considerado uma resposta insatisfatória, pois não faz sentido por si só e o recurso de destino não tem garantia de vida no futuro. Tente incluir pelo menos um resumo das informações para as quais você está criando um link.
j0k
Obrigado ao Soren por isso, até nós conhecemos as APIs e alguma ideia sobre isso, mas você fez muito bem Soren, obrigado novamente.
ind79ra de
12

Acho que encontrei a resposta na documentação do meu kernel: /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 

Aqui está um link para este arquivo: http://git.kernel.org/?p=linux/kernel/git/next/linux-next.git;a=blob_plain;f=Documentation/filesystems/proc.txt;hb = e8883f8057c0f7c9950fa9f20568f37bfa62f34a

mk2
fonte
Sim, parece estar funcionando. No entanto, essa solução requer que eu leia um arquivo de texto e o analise. Gostaria de saber se existe uma alternativa melhor, ou seja, uma API que me permite obter esses conteúdos em um formato binário estruturado.
Thomas Tempelmann
9

eu encontrei

dmesg | grep tty

fazendo o trabalho.

RealHuman75
fonte
3

setserial com a opção -g parece fazer o que você quer e a fonte C está disponível em http://www.koders.com/c/fid39344DABD14604E70DF1B8FEA7D920A94AF78BF8.aspx .

Hrickards
fonte
Olhei o código e ele tem uma falha que explico na minha pergunta no final, pois tem que abrir o aparelho, o que já pode levar a uma tentativa de conexão - o que por sua vez não é bom. Mas então, talvez os drivers Linux sejam mais inteligentes do que o driver OSX atual quando se trata de suporte a bluetooth, já que eles não abrirão uma conexão imediatamente? Quem sabe? Talvez eu deva começar uma nova pergunta para esclarecer isso especificamente. Se estiver tudo bem, então posso aceitar sua resposta aqui também. Hmmm ...
Thomas Tempelmann
3

Não tenho nenhum dispositivo serial aqui para testá-lo, mas se você tiver python e dbus, pode tentar sozinho.

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

Se falhar, você pode pesquisar dentro hwmanager_i.GetAllDevicesWithProperties()para ver se o nome do recurso "serial" que acabei de adivinhar tem um nome diferente.

HTH

baol
fonte
2

Não tenho um dispositivo serial USB, mas deve haver uma maneira de encontrar as portas reais usando as bibliotecas HAL diretamente:

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

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

O código python-dbus postado nem este script sh lista os dispositivos bluetooth / dev / rfcomm *, portanto, não é a melhor solução.

Observe que em outras plataformas Unix, as portas seriais não são nomeadas ttyS? e mesmo no Linux, algumas placas seriais permitem nomear os dispositivos. Presumindo que um padrão nos nomes dos dispositivos seriais esteja errado.

kelk1
fonte
Pena que o HAL foi removido do Ubuntu (após 12.04), ele tinha algumas ferramentas fáceis de usar. Alguém sabe se há um substituto para o anterior? Mas se você estiver em uma versão / distro que tem HAL, isso parece bom.
Reed Hedges
2

Usar / proc / tty / drivers indica apenas quais drivers tty estão carregados. Se você estiver procurando por uma lista de portas seriais, verifique / dev / serial, ela terá dois subdiretórios: by-id e 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

Graças a esta postagem: /superuser/131044/how-do-i-know-which-dev-ttys-is-my-serial-port

blarf
fonte
Aparentemente, isso depende da distro. Não consigo encontrar / dev / serial na minha
máquina
0

Minha abordagem via dialout de grupo para obter cada tty com o usuário 'dialout' ls -l /dev/tty* | grep 'dialout' para obter apenas sua pasta ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev

ouvir facilmente a saída tty, por exemplo, quando o arduino for serial out: head --lines 1 < /dev/ttyUSB0

ouça cada tty em apenas uma linha: for i in $(ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev); do head --lines 1 < $i; done

Eu realmente gosto da abordagem por meio da procura de drivers: ll /sys/class/tty/*/device/driver

Você pode escolher o nome tty agora: ls /sys/class/tty/*/device/driver | grep 'driver' | cut -d "/" -f 5

McPeppr
fonte
0

A biblioteca do gerenciador de comunicação serial possui muitos APIs e recursos direcionados para a tarefa que você deseja. Se o dispositivo for um USB-UART, seu VID / PID pode ser usado. Se o dispositivo for BT-SPP, APIs específicas da plataforma podem ser usadas. Dê uma olhada neste projeto para programação de porta serial: https://github.com/RishiGupta12/serial-communication-manager

samuel05051980
fonte
0

sim, eu sei, estou muito atrasado (como sempre). Aqui está meu código (baseado na resposta de mk2). Talvez isso ajude alguém:

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
fonte
Parece que seu código analisa a que resposta stackoverflow.com/a/4701610/43615 se refere. Em caso afirmativo, você mencionaria isso em sua resposta, por favor?
Thomas Tempelmann