Làm thế nào để tìm tất cả các thiết bị nối tiếp (ttyS, ttyUSB, ..) trên Linux mà không cần mở chúng?


113

Cách thích hợp để lấy danh sách tất cả các cổng / thiết bị nối tiếp có sẵn trên hệ thống Linux là gì?

Nói cách khác, khi tôi lặp lại tất cả các thiết bị trong đó /dev/, làm cách nào để biết cái nào là cổng nối tiếp theo cách cổ điển, tức là những thiết bị thường hỗ trợ tốc độ truyền và điều khiển luồng RTS / CTS ?

Giải pháp sẽ được mã hóa bằng C.

Tôi hỏi vì tôi đang sử dụng thư viện của bên thứ ba, điều này rõ ràng là sai: Nó dường như chỉ lặp đi lặp lại /dev/ttyS*. Vấn đề là có, ví dụ, có các cổng nối tiếp qua USB (được cung cấp bởi bộ điều hợp USB-RS232) và những cổng đó được liệt kê trong / dev / ttyUSB *. Và khi đọc Serial-HOWTO tại Linux.org , tôi có ý tưởng rằng sẽ có những không gian tên khác, khi thời gian đến.

Vì vậy, tôi cần phải tìm cách chính thức để phát hiện các thiết bị nối tiếp. Vấn đề là dường như không có tài liệu nào được ghi lại, hoặc tôi không thể tìm thấy nó.

Tôi tưởng tượng một cách sẽ là mở tất cả các tệp từ /dev/tty*và gọi một tệp cụ thể ioctl()trên chúng mà chỉ khả dụng trên các thiết bị nối tiếp. Đó có phải là một giải pháp tốt không?

Cập nhật

hrickards đề nghị xem nguồn cho "setserial". Mã của nó thực hiện chính xác những gì tôi đã nghĩ đến:

Đầu tiên, nó mở một thiết bị với:

fd = open (path, O_RDWR | O_NONBLOCK)

Sau đó, nó gọi:

ioctl (fd, TIOCGSERIAL, &serinfo)

Nếu cuộc gọi đó không trả về lỗi, thì rõ ràng đó là một thiết bị nối tiếp.

Tôi tìm thấy mã tương tự trong Lập trình nối tiếp / thuật ngữ , điều này đề xuất thêm O_NOCTTYtùy chọn.

Tuy nhiên, có một vấn đề với cách tiếp cận này:

Khi tôi kiểm tra mã này trên BSD Unix (tức là Mac OS X), nó cũng hoạt động. Tuy nhiên , các thiết bị nối tiếp được cung cấp qua Bluetooth khiến hệ thống (trình điều khiển) cố gắng kết nối với thiết bị Bluetooth, điều này sẽ mất một lúc trước khi thiết bị sẽ quay lại với lỗi hết thời gian chờ. Nguyên nhân là do bạn vừa mở máy. Và tôi có thể tưởng tượng rằng những điều tương tự cũng có thể xảy ra trên Linux - lý tưởng nhất là tôi không cần phải mở thiết bị để tìm ra loại của nó. Tôi tự hỏi liệu có cách nào để gọi các ioctlchức năng mà không cần mở hoặc mở một thiết bị theo cách mà nó không gây ra kết nối không?

Tôi nên làm gì?


1
Ai đó ẩn danh đã đề xuất chỉnh sửa này, nhưng đã bị từ chối, vì vậy tôi để nó ở đây dưới dạng nhận xét thay thế: Nếu bạn sử dụng cờ TIOCGSERIAL trong lệnh gọi ioctl, thay vì TIOCMGET, thì lệnh gọi không trả về lỗi với một số đường dẫn sai không tham chiếu đến một cổng COM (nối tiếp). Với cờ TIOCMGET, ioctl chỉ hoạt động với các cổng COM có sẵn để truy cập trong cả hai đường dẫn có thể có TTY và TTYUSB.
Thomas Tempelmann

Câu trả lời:


78

Hệ /systhống tệp phải chứa nhiều thông tin cho nhiệm vụ của bạn. Hệ thống của tôi (2.6.32-40-generic # 87-Ubuntu) đề xuất:

/sys/class/tty

Điều này cung cấp cho bạn mô tả về tất cả các thiết bị TTY mà hệ thống biết. Một ví dụ rút gọn:

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

Theo một trong các liên kết sau:

# 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

Ở đây devtệp chứa thông tin này:

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

Đây là nút chính / nút phụ. Bạn có thể tìm kiếm những thứ này trong /devthư mục để lấy tên thân thiện với người dùng:

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

Các /sys/class/ttydir chứa tất cả các thiết bị TTY nhưng bạn có thể muốn loại trừ những pesky thiết bị đầu cuối ảo và thiết bị đầu cuối giả. Tôi đề nghị bạn chỉ kiểm tra những cái có device/drivermục nhập:

# 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 Bạn sẽ tìm thấy /dev/zero. Bạn có thực sự nghĩ rằng, đây là một thiết bị nối tiếp?
AH

Tìm kiếm trong / dev là vô ích, vì bạn đã có tên trong / sys / class / tty (như mặc định udev tạo nút / dev / DEVNAME). Điều bạn quan tâm là bất kỳ liên kết "tượng trưng" nào trong / dev trỏ đến thiết bị như vậy. Điều này khó tìm hơn nhiều.
xryl669,

28

Trong các hạt nhân gần đây (không rõ từ khi nào), bạn có thể liệt kê nội dung của / dev / serial để có được danh sách các cổng nối tiếp trên hệ thống của mình. Chúng thực sự là các liên kết tượng trưng trỏ đến đúng / 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

Đây là bộ điều hợp USB-Serial, như bạn có thể thấy. Lưu ý rằng khi không có cổng nối tiếp trên hệ thống, thư mục / dev / serial / không tồn tại. Hi vọng điêu nay co ich :).


3
Đây là một chức năng của udev (cụ thể là cấu hình của nó trong /lib/udev/rules.d/??-persists-serial.rules), được giới thiệu trong 2.5.
ergosys

4
Mẹo hay! Thật không may, tôi không nghĩ điều này sẽ hiển thị các cổng nối tiếp được tích hợp sẵn, chỉ có cổng nối tiếp USB (được udev nhìn thấy khi gắn vào). Tôi không thấy bất kỳ thứ gì cho / dev / serial trong Ubuntu 14 trong VMware VM (với ttyS0 / COM1 do VM cung cấp) và các quy tắc udev (60-dai dẳng-serial.rules) chỉ xem xét các thiết bị udev - Tôi không nghĩ udev phát hiện ra các cổng nối tiếp ttyS * "tích hợp sẵn", chúng sẽ phải được kiểm tra với ioctl hoặc tương tự như trong các câu trả lời khác.
Reed Hedges

ls / dev / / ls nối tiếp: không thể truy cập '/ dev / serial /': Không có tập tin hoặc thư mục Slackware 14,2 hiện nay x64
jpka

2
@jpka: Điều đó xảy ra nếu không có thiết bị nối tiếp nào để tìm. Tôi đã làm như trên và nó đã hoạt động. Sau đó, tôi đã rút thiết bị nối tiếp (FTDI) của mình khỏi USB và sau đó nó tạo ra lỗi như bạn đã mô tả.
Warpspace

13

Tôi đang làm một cái gì đó giống như mã sau đây. Nó hoạt động cho các thiết bị USB và cả các nhà phát triển serial8250 ngu ngốc mà tất cả chúng ta đều có 30 - nhưng chỉ một vài trong số chúng hoạt động thực sự.

Về cơ bản tôi sử dụng khái niệm từ các câu trả lời trước. Đầu tiên liệt kê tất cả các thiết bị tty trong / sys / class / tty /. Các thiết bị không chứa a / device con sẽ bị lọc đi. / sys / class / tty / console là một thiết bị như vậy. Sau đó, các thiết bị thực sự chứa thiết bị trong đó được chấp nhận là cổng nối tiếp hợp lệ tùy thuộc vào mục tiêu của fx liên kết biểu tượng trình điều khiển.

$ 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

và cho 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

Tất cả các trình điều khiển được điều khiển bởi serial8250 phải là đầu dò sử dụng ioctl đã đề cập trước đó.

        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

Chỉ cổng báo cáo loại thiết bị hợp lệ mới hợp lệ.

Nguồn hoàn chỉnh để liệt kê các cổng nối tiếp trông như thế này. Sự bổ sung được hoan nghênh.

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

Liên kết đơn được coi là một câu trả lời kém vì bản thân nó vô nghĩa và tài nguyên mục tiêu không được đảm bảo sẽ tồn tại trong tương lai. Vui lòng cố gắng bao gồm ít nhất bản tóm tắt thông tin mà bạn đang liên kết đến.
j0k

Cảm ơn Soren về điều này, ngay cả khi chúng tôi biết các API và một số ý tưởng về nó nhưng bạn đã làm rất tốt Soren, cảm ơn một lần nữa.
ind79ra

12

Tôi nghĩ rằng tôi đã tìm thấy câu trả lời trong tài liệu nguồn hạt nhân của mình: /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 

Đây là liên kết đến tệp này: http://git.kernel.org/?p=linux/kernel/git/next/linux-next.git;a=blob_plain;f=Documentation/filesystems/proc.txt;hb = e8883f8057c0f7c9950fa9f20568f37bfa62f34a


Vâng, điều đó dường như đang hoạt động. Tuy nhiên, giải pháp này yêu cầu tôi đọc một tệp văn bản và phân tích cú pháp nó. Tôi tự hỏi liệu có cách nào tốt hơn không, tức là một API cho phép tôi lấy những nội dung này ở định dạng nhị phân có cấu trúc.
Thomas Tempelmann

9

tôi đã tìm thấy

dmesg | grep tty

đang thực hiện công việc.


3

setserial với tùy chọn -g dường như thực hiện những gì bạn muốn và nguồn C có sẵn tại http://www.koders.com/c/fid39344DABD14604E70DF1B8FEA7D920A94AF78BF8.aspx .


Tôi đã xem mã và nó có lỗ hổng mà tôi giải thích trong câu hỏi của mình ở phần cuối vì nó phải mở thiết bị, điều này có thể đã dẫn đến nỗ lực kết nối - điều này không tốt. Nhưng sau đó, có lẽ trình điều khiển Linux thông minh hơn trình điều khiển OSX hiện tại khi hỗ trợ bluetooth, vì chúng sẽ không mở kết nối ngay lập tức? Ai biết? Có lẽ tôi nên bắt đầu một câu hỏi mới để làm rõ điều đó một cách cụ thể. Nếu điều đó là tốt, thì tôi cũng có thể chấp nhận câu trả lời của bạn ở đây. Hmmm ...
Thomas Tempelmann

3

Tôi không có thiết bị nối tiếp nào ở đây để kiểm tra nó, nhưng nếu bạn có python và dbus, bạn có thể tự thử.

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

Nếu nó không thành công, bạn có thể tìm kiếm bên trong hwmanager_i.GetAllDevicesWithProperties()để xem tên khả năng "nối tiếp" mà tôi vừa đoán có tên khác hay không.

HTH


2

Tôi không có thiết bị nối tiếp USB, nhưng phải có cách tìm cổng thực bằng cách sử dụng thư viện HAL trực tiếp:

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

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

Mã python-dbus đã đăng cũng như tập lệnh sh này liệt kê các thiết bị bluetooth / dev / rfcomm *, vì vậy nó không phải là giải pháp tốt nhất.

Lưu ý rằng trên các nền tảng unix khác, các cổng nối tiếp không được đặt tên là ttyS? và thậm chí trong linux, một số thẻ nối tiếp cho phép bạn đặt tên cho các thiết bị. Giả sử một mẫu trong tên các thiết bị nối tiếp là sai.


Thật tệ là HAL đã bị xóa khỏi Ubuntu (sau 12.04), nó có một số công cụ dễ sử dụng. Có ai biết nếu có một thay thế cho ở trên? Nhưng nếu bạn đang sử dụng phiên bản / bản phân phối có HAL, điều này sẽ đẹp.
Reed Hedges

2

Việc sử dụng / proc / tty / driver chỉ cho biết trình điều khiển tty nào được tải. Nếu bạn đang tìm kiếm danh sách các cổng nối tiếp, hãy kiểm tra / dev / serial, nó sẽ có hai thư mục con: by-id và by-path.

VÍ DỤ:

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

Cảm ơn bài đăng này: /superuser/131044/how-do-i-know-which-dev-ttys-is-my-serial-port


Rõ ràng điều này phụ thuộc vào bản phân phối. Tôi không thể tìm thấy / dev / serial trên hộp của tôi (chạy Debian)
SimonC

0

Cách tiếp cận của tôi qua nhóm dialout để có được tất cả các tty với người dùng dialout ' ls -l /dev/tty* | grep 'dialout' để chỉ nhận được thư mục của nó ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev

dễ dàng nghe đầu ra tty ví dụ khi arduino nối tiếp ra: head --lines 1 < /dev/ttyUSB0

chỉ nghe mọi thứ cho một dòng: for i in $(ls -l /dev/tty* | grep 'dialout' | rev | cut -d " " -f1 | rev); do head --lines 1 < $i; done

Tôi thực sự thích cách tiếp cận thông qua tìm kiếm trình điều khiển: ll /sys/class/tty/*/device/driver

Bạn có thể chọn tty-Name ngay bây giờ: ls /sys/class/tty/*/device/driver | grep 'driver' | cut -d "/" -f 5


0

Thư viện trình quản lý giao tiếp nối tiếp có nhiều API và các tính năng được nhắm mục tiêu cho tác vụ bạn muốn. Nếu thiết bị là USB-UART, VID / PID của nó có thể được sử dụng. Nếu thiết bị là BT-SPP thì có thể sử dụng các API dành riêng cho nền tảng. Hãy xem dự án này để lập trình cổng nối tiếp: https://github.com/RishiGupta12/serial-communication-manager


0

vâng, tôi biết, tôi đã quá muộn (như mọi khi). Đây là đoạn mã của tôi (dựa trên câu trả lời của mk2). Có thể điều này giúp ai đó:

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

Có vẻ như mã của bạn sẽ phân tích câu trả lời stackoverflow.com/a/4701610/43615 đề cập đến. Nếu vậy, bạn có đề cập đến điều đó trong thư trả lời của bạn không?
Thomas Tempelmann
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.