blob: 14d9a3135cd4e99d4f9aff9b680a03042587ea7c [file] [log] [blame]
#!/usr/bin/env python
# lsusb.py
# Displays your USB devices in reasonable form.
# (c) Kurt Garloff <garloff@suse.de>, 2/2009, GPL v2 or v3.
# Usage: See usage()
import os, sys, re, getopt
# from __future__ import print_function
# Global options
showint = False
showhubint = False
noemptyhub = False
nohub = False
warnsort = False
prefix = "/sys/bus/usb/devices/"
usbids = "/usr/share/usb.ids"
esc = chr(27)
norm = esc + "[0;0m"
bold = esc + "[0;1m"
red = esc + "[0;31m"
green= esc + "[0;32m"
amber= esc + "[0;33m"
cols = ("", "", "", "", "")
def readattr(path, name):
"Read attribute from sysfs and return as string"
f = open(prefix + path + "/" + name);
return f.readline().rstrip("\n");
def readlink(path, name):
"Read symlink and return basename"
return os.path.basename(os.readlink(prefix + path + "/" + name));
class UsbClass:
"Container for USB Class/Subclass/Protocol"
def __init__(self, cl, sc, pr, str = ""):
self.pclass = cl
self.subclass = sc
self.proto = pr
self.desc = str
def __repr__(self):
return self.desc
def __cmp__(self, oth):
# Works only on 64bit systems:
#return self.pclass*0x10000+self.subclass*0x100+self.proto \
# - oth.pclass*0x10000-oth.subclass*0x100-oth.proto
if self.pclass != oth.pclass:
return self.pclass - oth.pclass
if self.subclass != oth.subclass:
return self.subclass - oth.subclass
return self.proto - oth.proto
class UsbVendor:
"Container for USB Vendors"
def __init__(self, vid, vname = ""):
self.vid = vid
self.vname = vname
def __repr__(self):
return self.vname
def __cmp__(self, oth):
return self.vid - oth.vid
class UsbProduct:
"Container for USB VID:PID devices"
def __init__(self, vid, pid, pname = ""):
self.vid = vid
self.pid = pid
self.pname = pname
def __repr__(self):
return self.pname
def __cmp__(self, oth):
# Works only on 64bit systems:
# return self.vid*0x10000 + self.pid \
# - oth.vid*0x10000 - oth.pid
if self.vid != oth.vid:
return self.vid - oth.vid
return self.pid - oth.pid
usbvendors = []
usbproducts = []
usbclasses = []
def ishexdigit(str):
"return True if all digits are valid hex digits"
for dg in str:
if not dg.isdigit() and not dg in 'abcdef':
return False
return True
def parse_usb_ids():
"Parse /usr/share/usb.ids and fill usbvendors, usbproducts, usbclasses"
id = 0
sid = 0
mode = 0
strg = ""
cstrg = ""
for ln in file(usbids, "r").readlines():
if ln[0] == '#':
continue
ln = ln.rstrip('\n')
if len(ln) == 0:
continue
if ishexdigit(ln[0:4]):
mode = 0
id = int(ln[:4], 16)
usbvendors.append(UsbVendor(id, ln[6:]))
continue
if ln[0] == '\t' and ishexdigit(ln[1:3]):
sid = int(ln[1:5], 16)
# USB devices
if mode == 0:
usbproducts.append(UsbProduct(id, sid, ln[7:]))
continue
elif mode == 1:
nm = ln[5:]
if nm != "Unused":
strg = cstrg + ":" + nm
else:
strg = cstrg + ":"
usbclasses.append(UsbClass(id, sid, -1, strg))
continue
if ln[0] == 'C':
mode = 1
id = int(ln[2:4], 16)
cstrg = ln[6:]
usbclasses.append(UsbClass(id, -1, -1, cstrg))
continue
if mode == 1 and ln[0] == '\t' and ln[1] == '\t' and ishexdigit(ln[2:4]):
prid = int(ln[2:4], 16)
usbclasses.append(UsbClass(id, sid, prid, strg + ":" + ln[6:]))
continue
mode = 2
def bin_search(first, last, item, list):
"binary search on list, returns -1 on fail, match idx otherwise, recursive"
#print "bin_search(%i,%i)" % (first, last)
if first == last:
return -1
if first == last-1:
if item == list[first]:
return first
else:
return -1
mid = (first+last) // 2
if item == list[mid]:
return mid
elif item < list[mid]:
return bin_search(first, mid, item, list)
else:
return bin_search(mid, last, item, list)
def find_usb_prod(vid, pid):
"Return device name from USB Vendor:Product list"
strg = ""
dev = UsbVendor(vid, "")
lnvend = len(usbvendors)
ix = bin_search(0, lnvend, dev, usbvendors)
if ix != -1:
strg = usbvendors[ix].__repr__()
else:
return ""
dev = UsbProduct(vid, pid, "")
lnprod = len(usbproducts)
ix = bin_search(0, lnprod, dev, usbproducts)
if ix != -1:
return strg + " " + usbproducts[ix].__repr__()
return strg
def find_usb_class(cid, sid, pid):
"Return USB protocol from usbclasses list"
if cid == 0xff and sid == 0xff and pid == 0xff:
return "Vendor Specific"
lnlst = len(usbclasses)
dev = UsbClass(cid, sid, pid, "")
ix = bin_search(0, lnlst, dev, usbclasses)
if ix != -1:
return usbclasses[ix].__repr__()
dev = UsbClass(cid, sid, -1, "")
ix = bin_search(0, lnlst, dev, usbclasses)
if ix != -1:
return usbclasses[ix].__repr__()
dev = UsbClass(cid, -1, -1, "")
ix = bin_search(0, lnlst, dev, usbclasses)
if ix != -1:
return usbclasses[ix].__repr__()
return ""
devlst = ( 'host', # usb-storage
'video4linux/video', # uvcvideo et al.
'sound/card', # snd-usb-audio
'net/', # cdc_ether, ...
'input/input', # usbhid
'usb:hiddev', # usb hid
'bluetooth/hci', # btusb
'ttyUSB', # btusb
'tty/', # cdc_acm
'usb:lp', # usblp
#'usb/lp', # usblp
'usb/', # hiddev, usblp
)
def find_storage(hostno):
"Return SCSI block dev names for host"
res = ""
for ent in os.listdir("/sys/class/scsi_device/"):
(host, bus, tgt, lun) = ent.split(":")
if host == hostno:
try:
for ent2 in os.listdir("/sys/class/scsi_device/%s/device/block" % ent):
res += ent2 + " "
except:
pass
return res
def find_dev(driver, usbname):
"Return pseudo devname that's driven by driver"
res = ""
for nm in devlst:
dir = prefix + usbname
prep = ""
#print nm
idx = nm.find('/')
if idx != -1:
prep = nm[:idx+1]
dir += "/" + nm[:idx]
nm = nm[idx+1:]
ln = len(nm)
try:
for ent in os.listdir(dir):
if ent[:ln] == nm:
res += prep+ent+" "
if nm == "host":
res += "(" + find_storage(ent[ln:])[:-1] + ")"
except:
pass
return res
class UsbInterface:
"Container for USB interface info"
def __init__(self, parent = None, level = 1):
self.parent = parent
self.level = level
self.fname = ""
self.iclass = 0
self.isclass = 0
self.iproto = 0
self.noep = 0
self.driver = ""
self.devname = ""
self.protoname = ""
def read(self, fname):
fullpath = ""
if self.parent:
fullpath += self.parent.fname + "/"
fullpath += fname
#self.fname = fullpath
self.fname = fname
self.iclass = int(readattr(fullpath, "bInterfaceClass"),16)
self.isclass = int(readattr(fullpath, "bInterfaceSubClass"),16)
self.iproto = int(readattr(fullpath, "bInterfaceProtocol"),16)
self.noep = int(readattr(fullpath, "bNumEndpoints"))
try:
self.driver = readlink(fname, "driver")
self.devname = find_dev(self.driver, fname)
except:
pass
self.protoname = find_usb_class(self.iclass, self.isclass, self.iproto)
def __str__(self):
return "%-16s(IF) %02x:%02x:%02x %iEPs (%s) %s%s %s%s%s\n" % \
(" " * self.level+self.fname, self.iclass,
self.isclass, self.iproto, self.noep,
self.protoname,
cols[3], self.driver,
cols[4], self.devname, cols[0])
class UsbDevice:
"Container for USB device info"
def __init__(self, parent = None, level = 0):
self.parent = parent
self.level = level
self.fname = ""
self.iclass = 0
self.isclass = 0
self.iproto = 0
self.vid = 0
self.pid = 0
self.name = ""
self.usbver = ""
self.speed = ""
self.maxpower = ""
self.noports = 0
self.nointerfaces = 0
self.driver = ""
self.devname = ""
self.interfaces = []
self.children = []
def read(self, fname):
self.fname = fname
self.iclass = int(readattr(fname, "bDeviceClass"), 16)
self.isclass = int(readattr(fname, "bDeviceSubClass"), 16)
self.iproto = int(readattr(fname, "bDeviceProtocol"), 16)
self.vid = int(readattr(fname, "idVendor"), 16)
self.pid = int(readattr(fname, "idProduct"), 16)
try:
self.name = readattr(fname, "manufacturer") + " " \
+ readattr(fname, "product")
#self.name += " " + readattr(fname, "serial")
if self.name[:5] == "Linux":
rx = re.compile(r"Linux [^ ]* (.hci_hcd) .HCI Host Controller")
mch = rx.match(self.name)
if mch:
self.name = mch.group(1)
except:
pass
if not self.name:
self.name = find_usb_prod(self.vid, self.pid)
# Some USB Card readers have a better name then Generic ...
if self.name[:7] == "Generic":
oldnm = self.name
self.name = find_usb_prod(self.vid, self.pid)
if not self.name:
self.name = oldnm
try:
ser = readattr(fname, "serial")
# Some USB devs report "serial" as serial no. suppress
if (ser and ser != "serial"):
self.name += " " + ser
except:
pass
self.usbver = readattr(fname, "version")
self.speed = readattr(fname, "speed")
self.maxpower = readattr(fname, "bMaxPower")
self.noports = int(readattr(fname, "maxchild"))
try:
self.nointerfaces = int(readattr(fname, "bNumInterfaces"))
except:
#print "ERROR: %s/bNumInterfaces = %s" % (fname,
# readattr(fname, "bNumInterfaces"))a
self.nointerfaces = 0
try:
self.driver = readlink(fname, "driver")
self.devname = find_dev(self.driver, fname)
except:
pass
def readchildren(self):
if self.fname[0:3] == "usb":
fname = self.fname[3:]
else:
fname = self.fname
for dirent in os.listdir(prefix + self.fname):
if not dirent[0:1].isdigit():
continue
#print dirent
if os.access(prefix + dirent + "/bInterfaceClass", os.R_OK):
iface = UsbInterface(self, self.level+1)
iface.read(dirent)
self.interfaces.append(iface)
else:
usbdev = UsbDevice(self, self.level+1)
usbdev.read(dirent)
usbdev.readchildren()
self.children.append(usbdev)
def __str__(self):
#str = " " * self.level + self.fname
if self.iclass == 9:
col = cols[2]
if noemptyhub and len(self.children) == 0:
return ""
if nohub:
str = ""
else:
col = cols[1]
if not nohub or self.iclass != 9:
str = "%-16s%s%04x:%04x%s %02x %s%5sMBit/s %s %iIFs (%s%s%s)" % \
(" " * self.level + self.fname,
cols[1], self.vid, self.pid, cols[0],
self.iclass, self.usbver, self.speed, self.maxpower,
self.nointerfaces, col, self.name, cols[0])
#if self.driver != "usb":
# str += " %s" % self.driver
if self.iclass == 9 and not showhubint:
str += " %shub%s\n" % (cols[2], cols[0])
else:
str += "\n"
if showint:
for iface in self.interfaces:
str += iface.__str__()
for child in self.children:
str += child.__str__()
return str
def deepcopy(lst):
"Returns a deep copy from the list lst"
copy = []
for item in lst:
copy.append(item)
return copy
def display_diff(lst1, lst2, fmtstr, args):
"Compare lists (same length!) and display differences"
for idx in range(0, len(lst1)):
if lst1[idx] != lst2[idx]:
print "Warning: " + fmtstr % args(lst2[idx])
def fix_usbvend():
"Sort USB vendor list and (optionally) display diffs"
if warnsort:
oldusbvend = deepcopy(usbvendors)
usbvendors.sort()
if warnsort:
display_diff(usbvendors, oldusbvend,
"Unsorted Vendor ID %04x",
lambda x: (x.vid,))
def fix_usbprod():
"Sort USB products list"
if warnsort:
oldusbprod = deepcopy(usbproducts)
usbproducts.sort()
if warnsort:
display_diff(usbproducts, oldusbprod,
"Unsorted Vendor:Product ID %04x:%04x",
lambda x: (x.vid, x.pid))
def fix_usbclass():
"Sort USB class list"
if warnsort:
oldusbcls = deepcopy(usbclasses)
usbclasses.sort()
if warnsort:
display_diff(usbclasses, oldusbcls,
"Unsorted USB class %02x:%02x:%02x",
lambda x: (x.pclass, x.subclass, x.proto))
def usage():
"Displays usage information"
print "Usage: lsusb.py [options]"
print "Options:"
print " -h display this help"
print " -i display interface information"
print " -I display interface information, even for hubs"
print " -u suppress empty hubs"
print " -U suppress all hubs"
print " -c use colors"
print " -w display warning if usb.ids is not sorted correctly"
print " -f FILE override filename for /usr/share/usb.ids"
return 2
def read_usb():
"Read toplevel USB entries and print"
for dirent in os.listdir(prefix):
#print dirent,
if not dirent[0:3] == "usb":
continue
usbdev = UsbDevice(None, 0)
usbdev.read(dirent)
usbdev.readchildren()
os.write(sys.stdout.fileno(), usbdev.__str__())
def main(argv):
"main entry point"
global showint, showhubint, noemptyhub, nohub, warnsort, cols, usbids
try:
(optlist, args) = getopt.gnu_getopt(argv[1:], "hiIuUwcf:", ("help",))
except getopt.GetoptError, exc:
print "Error:", exc
sys.exit(usage())
for opt in optlist:
if opt[0] == "-h" or opt[0] == "--help":
usage()
sys.exit(0)
if opt[0] == "-i":
showint = True
continue
if opt[0] == "-I":
showint = True
showhubint = True
continue
if opt[0] == "-u":
noemptyhub = True
continue
if opt[0] == "-U":
noemptyhub = True
nohub = True
continue
if opt[0] == "-c":
cols = (norm, bold, red, green, amber)
continue
if opt[0] == "-w":
warnsort = True
continue
if opt[0] == "-f":
usbids = opt[1]
continue
if len(args) > 0:
print "Error: excess args %s ..." % args[0]
sys.exit(usage())
try:
parse_usb_ids()
fix_usbvend()
fix_usbprod()
fix_usbclass()
except:
print >>sys.stderr, " WARNING: Failure to read usb.ids"
print >>sys.stderr, sys.exc_info()
read_usb()
# Entry point
if __name__ == "__main__":
main(sys.argv)