Как однозначно идентифицировать USB-устройство?

Мне было интересно, как получить уникальный идентификатор запоминающего устройства USB. Я уже знаю, как получить серийный идентификатор SCSI из этого поста: Серийный номер USB-накопителя под Linux C++ В посте упоминается использование дескриптора устройства для получения идентификатора. Может кто-нибудь опубликовать некоторый код для определения информации дескриптора устройства под Linux?

7 ответов

Обобщая ответ Саймона Риге, я придумал эту функцию bash, которая при наличии необязательного идентификатора поставщика и идентификатора продукта возвращает список имен узлов устройства, связанных с идентификатором этого поставщика и идентификатором этого продукта, если он указан.

getDevNodes() {
    if [ -n "$1" ] && [ "$1" != "no_class" ]; then
        2>/dev/null find -L /sys/class/$1 -maxdepth 2 -mindepth 2 -name uevent -exec realpath "{}" +
    else
        find /sys/devices -name uevent
    fi | {        
        if [ -z "$1" ]; then
            readarray -t lines < <(find /sys/class -maxdepth 2 -mindepth 2 -type l -print -exec realpath "{}" +)

            local -i count=${#lines[@]} sys_dev=count/2 sys_class=0
            local -A classes

            while [ $sys_dev -lt $count ]; do
                    class="${lines[$sys_class]#/*/*/}"
                    class="${class%/*}"
                    classes["${lines[$sys_dev]}"]="$class"

                    sys_dev+=1
                    sys_class+=1                    
            done
        fi

        readarray -t uevents

        for u in "${uevents[@]}"; do       
            DEVNAME=; DEVTYPE=no_type; while IFS="=" read key value; do {
                [ "$key" = "DEVNAME" ] && DEVNAME=/dev/"$value" 
            } || {
                [ "$key" = "DEVTYPE" ] && DEVTYPE="$value"                 
            }; done < "$u"

            if [ -n "$DEVNAME" ]; then              
                path="${u%/uevent}"
                while [ "$path" != "/sys/devices" ] && ! [ -f "$path"/idVendor ]; do
                    path="${path%/*}"
                done

                [ "$path" != "/sys/devices" ] && {
                    read readIdVendor < "$path"/idVendor
                    read readIdProduct < "$path"/idProduct
                } || {
                    readIdVendor=----
                    readIdProduct=----
                }

                echo "${1:-${classes[${u%/uevent}]:-no_class}}" "$DEVTYPE" "$readIdVendor" "$readIdProduct" "$DEVNAME" 
            fi
        done
    } | grep "^${1:-[[:graph:]]\+} ${2:-[[:graph:]]\+} ${3:-....} ${4:-....}" | cat
}

Например, вот что говорит мне мой lsusb:

$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 008: ID 0bda:b719 Realtek Semiconductor Corp. 
Bus 001 Device 006: ID 0bda:57b5 Realtek Semiconductor Corp. 
Bus 001 Device 004: ID 0bda:0129 Realtek Semiconductor Corp. RTS5129 Card Reader Controller
Bus 001 Device 097: ID 1004:6344 LG Electronics, Inc. G2 Android Phone [tethering mode]
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Итак, если бы я хотел увидеть, какие узлы устройства связаны с идентификатором поставщика 0x1004 и идентификатором продукта 0x6344, я бы сделал следующее:

$ getDevNodes "" "" 1004 6344
no_class        usb_device      1004 6344 /dev/bus/usb/001/097 
tty             no_type         1004 6344 /dev/ttyACM0 

Итак, у нас есть два узла устройств, один из которых имеет класс tty без devtype, а другой - с неизвестным классом, но с devtype usb_device.

Также можно указать только идентификатор поставщика, например:

$ getDevNodes "" "" 0bda 
no_class        usb_device      0bda 0129 /dev/bus/usb/001/004 
no_class        usb_device      0bda b719 /dev/bus/usb/001/008 
no_class        no_type         0bda 57b5 /dev/media0 
video4linux     no_type         0bda 57b5 /dev/video0 
input           no_type         0bda 57b5 /dev/input/event14 
no_class        usb_device      0bda 57b5 /dev/bus/usb/001/006 

Если бы я хотел только устройства класса video4linux с идентификатором вендора 0bda, я бы сделал следующее:

$ getDevNodes video4linux "" "" 0bda
video4linux     no_type         0bda 57b5 /dev/video0 

Аргументы в основном фильтруют полный список узлов устройств и связанную с ними информацию. Если пропустить один из этих аргументов или использовать пустую строку "" в качестве аргумента, отключается фильтр для этого конкретного аргумента.

Аргументы приводятся в следующем порядке: 1: класс, 2: тип, 3: идентификатор поставщика, 4: идентификатор продукта.


Далее следует облегченная версия вышеуказанной функции, которая работает быстрее за счет некоторых функций: узлы устройства печатаются без дополнительной информации, и нет фильтра для типа устройства.

getDevNodesLite() {
    if [ -n "$1" ]; then
        2>/dev/null find -L /sys/class/$1 -maxdepth 2 -mindepth 2 -name uevent -exec realpath "{}" +
    else
        find /sys/devices -name uevent
    fi | {
        if [ -n "$2" ]; then
            readarray -t uevents              

            for u in "${uevents[@]}"; do
                path="${u%/uevent}"
                while [ "$path" != "/sys/devices" ] && ! [ -f "$path"/idVendor ]; do
                    path="${path%/*}"
                done

                [ "$path" != "/sys/devices" ] && read readValue < "$path"/idVendor && [ "$readValue" = "$2" ] && {
                    if [ -n "$idProduct" ]; then
                        read readValue < "$path"/idProduct && [ "$readValue" = "$3" ]
                    fi
                } && echo "$u"
            done
        else
            cat
        fi
    } | {
        readarray -t uevents              

        [ ${#uevents[@]} -gt 0 ] && sed -n 's,DEVNAME=\(.*\),/dev/\1,p' "${uevents[@]}"
    }
}
ls -l /dev/disk/by-id

С помощью USB "имя устройства" устройства может меняться в зависимости от порядка подключения устройства. Удивительно мало устройств имеют реальный серийный номер. Если вы не можете получить уникальную идентификацию от самого устройства, единственное решение - это зависеть от физического адреса соединения. Недостатком этого является то, что адрес меняется, если вы подключаете устройство к другому разъему USB.

Программно вы можете использовать sysfs для получения информации об устройстве, имеющейся в ядре. Sysfs - это представление устройств в файловой системе в том виде, в котором их видит ядро. (Это не настоящие файлы на диске)

С его помощью вы можете: - идентифицировать тип устройства с помощью идентификатора продукта и поставщика - прочитать серийный номер устройства, если оно есть. - прочитать номер физического соединения на USB-концентраторе

Вы можете начать с поиска вашего типа устройств в /sys/class. В этом примере я использую порт USB→LPT. Но принцип тот же.

$ ls -l /sys/class/usbmisc
lp1 -> ../../devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.5/4-1.5:1.0/usbmisc/lp1
lp2 -> ../../devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.6/4-1.6:1.0/usbmisc/lp2

Получите имя устройства из файла событий:

cat /sys/class/usbmisc/lp1/uevent
MAJOR=180
MINOR=1
DEVNAME=__usb/lp1__

добавьте /dev, чтобы вы открыли имя устройства: /dev / usb / lp1

Используйте реальный путь: $ cd -P /sys/class/usbmisc/lp1

Шаг назад 3 ветки:

$ cd ../../../
/sys/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.5

Этот каталог содержит много информации об устройстве:

idProduct и idVendor могут быть использованы для уникальной идентификации типа устройства.

Если есть серийный файл и он содержит уникальный серийный номер, все готово.

В противном случае вы можете использовать физическое соединение в качестве идентификатора, для которого это имя каталога "4-1.5". Оно уникально для физического соединения и, как вы уже упоминали, изменится, если вы подключите устройство к другому порту.

Я предлагаю использовать libusb. Вы можете найти документацию здесь.

Я делаю это с HAL в C++ / C с помощью QT. Нашел какой-то блог об этом:

Как определить, является ли /dev/* устройство USB

Было бы также возможно сделать это с помощью shell & HAL.

Чтобы добавить к тому, что все остальные сказали:

USB-устройства не всегда имеют серийный номер; даже если кто-то присутствует, он не гарантирован, чтобы быть глобально уникальным. (Например, моя клавиатура Apple USB не имеет серийного номера, а все камеры GoPro имеют один и тот же поддельный серийный номер:123456789ABC.) Таким образом, не всегда возможно однозначно идентифицировать устройство.

Делать sudo blkid и в нем будет указан идентификатор всех подключенных устройств с файловой системой

Другие вопросы по тегам