开发者

How do I uniquely identify an USB-device?

I was wondering how to get the unique id of a USB storage device. I already know how to fetch the SCSI serial id from this post : USB-drive serial number under linux C++ The post mentions using the Dev开发者_如何学编程ice Descriptor to get the ID. Can someone post some code to determine the Device Descriptor information under Linux?


ls -l /dev/disk/by-id


I suggest to use libusb. You can find the documentation here.


With USB, the "device name" of a device can change, depending on the order of which, the device was connected. Surprisingly few devices have a real serial number. If you can't get a unique identification from the device itself, the only solution is to depend on the physical address of connection. The drawback on this, is that the address changes, if you plug the device into another USB connector.

Programmatically you can use sysfs to get the information the kernel has, about the device. Sysfs is a file-system-like representation of devices as the kernel sees them. (Its not real files on the disk)

With it, you can: - identify the device type with product and vendor ID - read the serial number of the device, if it has one. - read the physical connection number on the USB hub

You could start by finding your type of devices in /sys/class. In this example I use an USB→LPT port. But the principle is the same.

$ 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

Grap the device name from the uevent file:

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

add /dev so you get the device name to open: /dev/usb/lp1

Use the real path: $ cd -P /sys/class/usbmisc/lp1

Step back 3 branches:

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

This directory contains a lot of the information on the device:

idProduct and idVendor can be used to uniquely identify the device type.

If there is a serial file and it contains a unique serial number, you are done.

Otherwise your option is to use the physical connection as identification, wich is this directory name “4-1.5” It is unique for the physical connection, and will as you already mentioned change if you plug the device to another port.


Generalizing Simon Rigét's answer, I came up with this bash function that, given optional vendor id and product id, returns a list of device node names, related to that vendor id and that product id if given.

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
}

For instance, this is what my lsusb tells me:

$ 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

So, if I wanted to see which device nodes are associated with the vendor id 0x1004 and the product id 0x6344 I'd do the following:

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

So we've got two device nodes, one of which is of class tty with no devtype and the other is of unknown class but with a devtype usb_device.

One can also only give the vendor id, like this:

$ 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 

If I only wanted video4linux class devices whose vendor id is 0bda, then I'd do the following:

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

Arguments are basically filters over the complete list of device nodes and their associated info. Omitting one of those arguments, or using the empty string "" as an argument, disables the filter for that specific argument.

Arguments are given in this order: 1: the class, 2: the type, 3: the vendor id, 4: the product id.


Here follows a lite version of the above function that runs faster at the expense of some functionalities: the device nodes are printed without the additional info and there's no filter for the device type.

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[@]}"
    }
}


To add to what everyone else has said:

USB devices do not always have a serial number; even when one is present, it is not guaranteed to be globally unique. (For instance, my Apple USB keyboard has no serial number, and GoPro cameras all have the same bogus serial number of 123456789ABC.) As such, it is not always possible to uniquely identify a device.


do sudo blkid and it will list the id of all mounted devices with a filesystem

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜