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 Device Descriptor to get the ID. Can someone post some code to determine the Device Descriptor information under Linux?
6 Answers 6
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
$ 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=$ sys_dev=count/2 sys_class=0 local -A classes while [ $sys_dev -lt $count ]; do classes["$"]="$class" sys_dev+=1 sys_class+=1 done fi readarray -t uevents for u in "$"; 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="$" while [ "$path" != "/sys/devices" ] && ! [ -f "$path"/idVendor ]; do path="$" done [ "$path" != "/sys/devices" ] && < read readIdVendor < "$path"/idVendor read readIdProduct < "$path"/idProduct >|| < readIdVendor=---- readIdProduct=---- >echo "$<1:-$>" "$DEVTYPE" "$readIdVendor" "$readIdProduct" "$DEVNAME" fi done > | grep "^$ $ $ $" | 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 "$"; do path="$" while [ "$path" != "/sys/devices" ] && ! [ -f "$path"/idVendor ]; do 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 [ $-gt 0 ] && sed -n 's,DEVNAME=\(.*\),/dev/\1,p' "$" > >
Get unique serial number of USB device mounted to /dev folder
Just ran into this same problem and it took a bit to find the solution. Any solution which starts with «just use lsusb» is incorrect. You can figure out the devices serial, but none of the extra information it provides help you determine which /dev/video it links to.
/bin/udevadm info --name=/dev/video1 | grep SERIAL_SHORT
E: ID_SERIAL_SHORT=256DEC57
@debuti This problem came from your USB devices. They were produced with same serial number or they haven’t serial number capability which cause the same serial number. You can reconfigure their serial number for some hardware. For example you can use CP210xSetIDs.exe if you are using CP2102 USB to UART.
Based on the hint of using udevadm and the tutorial from http://www.signal11.us/oss/udev/ I got below code to get the serial info of my webcam.
#include "stdio.h" #include int main(int argc, char **argv) < struct udev *udev; struct udev_device *dev; struct udev_enumerate *enumerate; struct udev_list_entry *list, *node; const char *path; udev = udev_new(); if (!udev) < printf("can not create udev"); return 0; >enumerate = udev_enumerate_new(udev); udev_enumerate_add_match_subsystem(enumerate, "video4linux"); udev_enumerate_scan_devices(enumerate); list = udev_enumerate_get_list_entry(enumerate); udev_list_entry_foreach(node, list) < path = udev_list_entry_get_name(node); dev = udev_device_new_from_syspath(udev, path); printf("Printing serial for %s\n", path); printf("ID_SERIAL=%s\n", udev_device_get_property_value(dev, "ID_SERIAL")); printf("ID_SERIAL_SHORT=%s\n", udev_device_get_property_value(dev, "ID_SERIAL_SHORT")); udev_device_unref(dev); >return 0; >
You can use lsusb , but you need to add verbose flag and make sure you use sudo with it, otherwise the serial will be incorrect.
If that is too verbose, then run lsusb to get the device id:
$ lsusb Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 012: ID 1ab1:0e11 Rigol Technologies Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Then run lsusb with device flag and grep the serial number.
So for the serial number of Rigol device:
$ sudo lsusb -s 012 -v|grep -i iserial iSerial 3 DP8C221100000
Thanks! This reply deserves to receive a lot more attention IMO, because it (ie lsusb with the verbose flag) is likely to be what a lot of people coming here are looking for 🙂
Playing around with libusb, it looks like there’s a standard getSerialNumber() method. Unfortunately, not all USB devices implement this. I have a couple cheap $4 webcams that return None for it. These interfaces expose other metadata, like VendorID and ProductID, which I’ve seen some code try and use as a unique identifier, but it’s not guaranteed to be unique, especially if you have multiple devices of the same make and model.
But assuming you get a serial number for your device, the next problem is figuring out which /dev/videoN file it corresponds to. I have an old version of libusb installed, so I couldn’t get the method working that returned the full sysfs path of the USB device, so instead I scrapped the output from hwinfo . I extracted all the chunks corresponding to cameras, and then from those I extracted the piece that looked like:
USB devices actually form a complicated tree, and that BusID encodes where the device is located in that tree.
You can then take that BusID to find where the device lives in the filesystem as well as the video path, which should be at:
/sys/bus/usb/devices//video4linux/
That’s a directory, and inside it you’ll find a videoN file matching one in /dev.
how to get bus id of an usb device
You can read off the sequence from the device tree you get with lsusb -t . The number before the hyphen is the bus, the numbers after the hyphen are the port sequence. Your device is on bus 01 , on port 1 of the root hub for this bus is another hub, and on port 3 of this hub is your device: So you get 1-1.3 .
If you know the vendor id from lsusb (like 148f for Ralink), you can also grep for it with
grep 148f /sys/bus/usb/devices/*/idVendor
and you’ll get something like
/sys/bus/usb/devices/1-1.3/idVendor:148f
as answer. If there are several devices from the same vendor, you can narrow it down with idProduct .
I agree that the recent suggested edit wasn’t very good, but it did highlight the fact that this answer could be improved by expanding on the last sentence.
As I was grepping for the productVendor 😭 I kept thinking this can’t be so hard (though inventive). Might as well RTFM. man lsusb :
-t, --tree Tells lsusb to dump the physical USB device hierarchy as a tree. Verbosity can be increased twice with the v option.
/: Bus 06.Port 1: Dev 1, Driver=uhci_hcd/2p, 12M ID 1d6b:0001 Linux Foundation 1.1 root hub /sys/bus/usb/devices/usb6 /dev/bus/usb/006/001 |__ Port 2: Dev 2, If 0, Driver=btusb, 12M ID 0bda:8771 Realtek Semiconductor Corp. /sys/bus/usb/devices/6-2 /dev/bus/usb/006/002
Thanks for that! Here is a oneliner with awk to get the deviceId given you know the vendor/product: lsusb -tvv | awk '/046d:c52b/ < getline; print gensub(/.*devices\/([0-9\-\.]*)/, "\\1", "g", $1); exit; >'
finally I found the right bus ID of the usb device. There is a file listing all the IDs - /sys/bus/usb/devices/ and the content is the following:
root@raspberrypi:/home/pi# ls /sys/bus/usb/devices 1-0:1.0 1-1 1-1.1 1-1:1.0 1-1.1:1.0 1-1.3 1-1.3:1.0 usb1
and the corresponding lsusb:
root@raspberrypi:/home/pi# lsusb -t /: Bus 01.Port 1: Dev 1, Driver=dwc_otg/1p, 480M |__ Port 1: Dev 2, If 0, Driver=hub/3p, 480M |__ Port 1: Dev 3, If 0, Driver=smsc95xx, 480M |__ Port 3: Dev 17, If 0, Driver=rt73usb, 480M root@raspberrypi:/home/pi# lsusb Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp. Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. Bus 001 Device 017: ID 148f:2573 Ralink Technology, Corp. RT2501/RT2573 Wireless Adapter
so I tried 1-1.3 as an ID and it worked. But 1-3 did not.
root@raspberrypi:/home/pi# echo -n "1-3" > /sys/bus/usb/drivers/usb/unbind bash: echo: write error: No such device
Here is an example where I was trying to do the same thing on Ubuntu 16.04 for USB-Ethernet device:
anurag@anurag-ThinkPad-E470:~$ lsusb Bus 002 Device 003: ID 0b95:1790 ASIX Electronics Corp. AX88179 Gigabit Ethernet Bus 002 Device 002: ID 0424:5744 Standard Microsystems Corp. Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 007: ID 138a:0011 Validity Sensors, Inc. VFS5011 Fingerprint Reader
We want to unbind the first device. You can see the driver instance created in sysfs and within the driver, an instance of the USB bus address of the Ethernet Dongle:
anurag@anurag-ThinkPad-E470:/sys/bus/usb/drivers$ ls ax88179_178a btusb hub r8188eu usb usbfs usbhid uvcvideo anurag@anurag-ThinkPad-E470:/sys/bus/usb/drivers/ax88179_178a$ ls 2-2.2:1.0 bind module new_id remove_id uevent unbind
If you plug in another dongle of same type, you can now see two devices:
anurag@anurag-ThinkPad-E470:/sys/bus/usb/drivers/ax88179_178a$ ls 2-2.1:1.0 2-2.2:1.0 bind module new_id remove_id uevent unbind
At this point you can see two net devices:
anurag@anurag-ThinkPad-E470:~$ ip link show . 6: enx000ec6cd8d75: mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000 link/ether 00:0e:c6:cd:8d:75 brd ff:ff:ff:ff:ff:ff 7: enx000ec6cd8d90: mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000 link/ether 00:0e:c6:cd:8d:90 brd ff:ff:ff:ff:ff:ff
anurag@anurag-ThinkPad-E470:/sys/bus/usb/drivers/ax88179_178a$ sudo sh -c "echo 2-2.2:1.0 > unbind" [sudo] password for anurag: anurag@anurag-ThinkPad-E470:/sys/bus/usb/drivers/ax88179_178a$ ls 2-2.1:1.0 bind module new_id remove_id uevent unbind
And the device will no longer show up in the interface list:
anurag@anurag-ThinkPad-E470:~$ ip link show . 6: enx000ec6cd8d75: mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000 link/ether 00:0e:c6:cd:8d:75 brd ff:ff:ff:ff:ff:ff
Note that just issuing sudo echo 2-2.2:1.0 > unbind doesn’t work because that only elevates permission for the echo command and not for the redirection. Hence we use temporarily elevated shell. The unbind is writable only by root user.