blob: 592e5cfff4d0b5bb7e4d79dedc87ac7c9705657b [file] [log] [blame]
#!/usr/bin/python3
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
#
# lsusb-VERSION.py
#
# Displays your USB devices in reasonable form.
#
# Copyright (c) 2009 Kurt Garloff <garloff@suse.de>
# Copyright (c) 2013,2018 Kurt Garloff <kurt@garloff.de>
#
# Usage: See usage()
# Py2 compat
from __future__ import print_function
import getopt
import os
import re
import sys
HUB_ICLASS = 0x09
# Global options
showint = False
showhubint = False
noemptyhub = False
nohub = False
showeps = False
showwakeup = False
prefix = "/sys/bus/usb/devices/"
usbids = [
"@usbids@",
"/usr/share/hwdata/usb.ids",
"/usr/share/usb.ids",
]
cols = ("", "", "", "", "", "")
norm = "\033[0;0m"
bold = "\033[0;1m"
red = "\033[0;31m"
green = "\033[0;32m"
amber = "\033[0;33m"
blue = "\033[0;34m"
usbvendors = {}
usbproducts = {}
usbclasses = {}
def colorize(num, text):
return cols[num] + str(text) + cols[0]
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 open_read_ign(fn):
try:
return open(fn, 'r', errors='ignore')
except:
return open(fn, 'r')
def myenum(*args):
enums = dict(zip(args, range(len(args))))
return type('MyEnum', (), enums)
def parse_usb_ids():
"Parse /usr/share/usb.ids and fill usbvendors, usbproducts, usbclasses"
vid = 0
did = 0
modes = myenum('Vendor', 'Class', 'Misc')
mode = modes.Vendor
strg = ""
cstrg = ""
for unm in usbids:
if os.path.exists(unm):
break
for ln in open_read_ign(unm).readlines():
if ln[0] == '#':
continue
ln = ln.rstrip('\n')
if len(ln) == 0:
continue
if ishexdigit(ln[0:4]):
mode = modes.Vendor
vid = int(ln[:4], 16)
usbvendors[vid] = ln[6:]
continue
if ln[0] == '\t' and ishexdigit(ln[1:3]):
# usb.ids has a device id of 01xy, sigh
if ln[3:5] == "xy":
did = int(ln[1:3], 16)*256
else:
did = int(ln[1:5], 16)
# USB devices
if mode == modes.Vendor:
usbproducts[vid, did] = ln[7:]
continue
elif mode == modes.Class:
nm = ln[5:]
if nm != "Unused":
strg = cstrg + ":" + nm
else:
strg = cstrg + ":"
usbclasses[vid, did, -1] = strg
continue
if ln[0] == 'C':
mode = modes.Class
cid = int(ln[2:4], 16)
cstrg = ln[6:]
usbclasses[cid, -1, -1] = cstrg
continue
if mode == modes.Class and ln[0] == '\t' and ln[1] == '\t' and ishexdigit(ln[2:4]):
prid = int(ln[2:4], 16)
usbclasses[cid, did, prid] = ln[6:]
continue
mode = modes.Misc
usbclasses[0xFF, 0xFF, 0xFF] = "Vendor Specific"
def find_usb_prod(vid, pid):
"Return device name from USB Vendor:Product list"
strg = ""
vendor = usbvendors.get(vid)
if vendor:
strg = str(vendor)
else:
return ""
product = usbproducts.get((vid, pid))
if product:
return strg + " " + str(product)
return strg
def find_usb_class(cid, sid, pid):
"Return USB protocol from usbclasses list"
lnlst = len(usbclasses)
cls = usbclasses.get((cid, sid, pid)) \
or usbclasses.get((cid, sid, -1)) \
or usbclasses.get((cid, -1, -1))
if cls:
return str(cls)
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
#'usbhid', # hidraw
]
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 add_drv(path, drvnm):
res = ""
try:
for e2 in os.listdir(path+"/"+drvnm):
if e2[0:len(drvnm)] == drvnm:
res += e2 + " "
try:
if res:
res += "(" + os.path.basename(os.readlink(path+"/driver")) + ") "
except:
pass
except:
pass
return res
def find_dev(driver, usbname):
"Return pseudo devname that's driven by driver"
res = ""
for nm in devlst:
dirnm = prefix + usbname
prep = ""
idx = nm.find('/')
if idx != -1:
prep = nm[:idx+1]
dirnm += "/" + nm[:idx]
nm = nm[idx+1:]
ln = len(nm)
try:
for ent in os.listdir(dirnm):
if ent[:ln] == nm:
res += prep+ent+" "
if nm == "host":
res += "(" + find_storage(ent[ln:])[:-1] + ")"
except:
pass
if driver == "usbhid":
rg = re.compile(r'[0-9A-F]{4}:[0-9A-F]{4}:[0-9A-F]{4}\.[0-9A-F]{4}')
for ent in os.listdir(prefix + usbname):
m = rg.match(ent)
if m:
res += add_drv(prefix+usbname+"/"+ent, "hidraw")
add = add_drv(prefix+usbname+"/"+ent, "input")
if add:
res += add
else:
for ent2 in os.listdir(prefix+usbname+"/"+ent):
m = rg.match(ent2)
if m:
res += add_drv(prefix+usbname+"/"+ent+"/"+ent2, "input")
return res
class UsbObject:
def read_attr(self, name):
path = prefix + self.path + "/" + name
return open(path).readline().strip()
def read_link(self, name):
path = prefix + self.path + "/" + name
return os.path.basename(os.readlink(path))
class UsbEndpoint(UsbObject):
"Container for USB endpoint info"
def __init__(self, parent, fname, level):
self.parent = parent
self.level = level
self.fname = fname
self.path = ""
self.epaddr = 0
self.len = 0
self.ival = ""
self.type = ""
self.attr = 0
self.max = 0
if self.fname:
self.read(self.fname)
def read(self, fname):
self.fname = fname
self.path = self.parent.path + "/" + fname
self.epaddr = int(self.read_attr("bEndpointAddress"), 16)
ival = int(self.read_attr("bInterval"), 16)
if ival:
self.ival = " (%s)" % self.read_attr("interval")
self.len = int(self.read_attr("bLength"), 16)
self.type = self.read_attr("type")
self.attr = int(self.read_attr("bmAttributes"), 16)
self.max = int(self.read_attr("wMaxPacketSize"), 16)
def __repr__(self):
return "<UsbEndpoint[%r]>" % self.fname
def __str__(self):
indent = " " * self.level
#name = "%s/ep_%02X" % (self.parent.fname, self.epaddr)
name = ""
body = "(EP) %02x: %s%s attr %02x len %02x max %03x" % \
(self.epaddr, self.type, self.ival, self.attr, self.len, self.max)
body = colorize(5, body)
return "%-17s %s\n" % (indent + name, indent + body)
class UsbInterface(UsbObject):
"Container for USB interface info"
def __init__(self, parent, fname, level=1):
self.parent = parent
self.level = level
self.fname = fname
self.path = ""
self.iclass = 0
self.isclass = 0
self.iproto = 0
self.noep = 0
self.driver = ""
self.devname = ""
self.protoname = ""
self.eps = []
if self.fname:
self.read(self.fname)
def read(self, fname):
self.fname = fname
self.path = self.parent.path + "/" + fname
self.iclass = int(self.read_attr("bInterfaceClass"),16)
self.isclass = int(self.read_attr("bInterfaceSubClass"),16)
self.iproto = int(self.read_attr("bInterfaceProtocol"),16)
self.noep = int(self.read_attr("bNumEndpoints"))
try:
self.driver = self.read_link("driver")
self.devname = find_dev(self.driver, self.path)
except:
pass
self.protoname = find_usb_class(self.iclass, self.isclass, self.iproto)
if showeps:
for dirent in os.listdir(prefix + self.path):
if dirent.startswith("ep_"):
ep = UsbEndpoint(self, dirent, self.level+1)
self.eps.append(ep)
def __repr__(self):
return "<UsbInterface[%r]>" % self.fname
def __str__(self):
indent = " " * self.level
name = self.fname
plural = (" " if self.noep == 1 else "s")
body = "(IF) %02x:%02x:%02x %iEP%s (%s) %s %s" % \
(self.iclass, self.isclass, self.iproto, self.noep, plural,
self.protoname, colorize(3, self.driver), colorize(4, self.devname))
strg = "%-17s %s\n" % (indent + name, indent + body)
if showeps and self.eps:
for ep in self.eps:
strg += str(ep)
return strg
class UsbDevice(UsbObject):
"Container for USB device info"
def __init__(self, parent, fname, level=0):
self.parent = parent
self.level = level
self.fname = fname
self.path = ""
self.iclass = 0
self.isclass = 0
self.iproto = 0
self.vid = 0
self.pid = 0
self.name = ""
self.usbver = ""
self.speed = ""
self.maxpower = ""
self.wakeup = ""
self.noports = 0
self.nointerfaces = 0
self.driver = ""
self.devname = ""
self.interfaces = []
self.children = []
if self.fname:
self.read(self.fname)
self.readchildren()
def read(self, fname):
self.fname = fname
self.path = fname
self.iclass = int(self.read_attr("bDeviceClass"), 16)
self.isclass = int(self.read_attr("bDeviceSubClass"), 16)
self.iproto = int(self.read_attr("bDeviceProtocol"), 16)
self.vid = int(self.read_attr("idVendor"), 16)
self.pid = int(self.read_attr("idProduct"), 16)
try:
self.name = self.read_attr("manufacturer") + " " \
+ self.read_attr("product")
except:
pass
if self.name:
mch = re.match(r"Linux [^ ]* (.hci[_-]hcd) .HCI Host Controller", self.name)
if mch:
self.name = mch.group(1)
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.startswith("Generic"):
oldnm = self.name
self.name = find_usb_prod(self.vid, self.pid)
if not self.name:
self.name = oldnm
try:
ser = self.read_attr("serial")
# Some USB devs report "serial" as serial no. suppress
if (ser and ser != "serial"):
self.name += " " + ser
except:
pass
self.usbver = self.read_attr("version")
self.speed = self.read_attr("speed")
self.maxpower = self.read_attr("bMaxPower")
self.noports = int(self.read_attr("maxchild"))
try:
self.nointerfaces = int(self.read_attr("bNumInterfaces"))
except:
self.nointerfaces = 0
try:
self.driver = self.read_link("driver")
self.devname = find_dev(self.driver, self.path)
except:
pass
if showwakeup:
try:
self.wakeup = self.read_attr('power/wakeup')
except:
self.wakeup = "unsupported"
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
if os.access(prefix + dirent + "/bInterfaceClass", os.R_OK):
iface = UsbInterface(self, dirent, self.level+1)
self.interfaces.append(iface)
else:
usbdev = UsbDevice(self, dirent, self.level+1)
self.children.append(usbdev)
usbsortkey = lambda obj: [int(x) for x in re.split(r"[-:.]", obj.fname)]
self.interfaces.sort(key=usbsortkey)
self.children.sort(key=usbsortkey)
def __repr__(self):
return "<UsbDevice[%r]>" % self.fname
def __str__(self):
is_hub = (self.iclass == HUB_ICLASS)
if is_hub:
if noemptyhub and len(self.children) == 0:
return ""
strg = ""
if not (nohub and is_hub):
indent = " " * self.level
name = self.fname
plural = (" " if self.nointerfaces == 1 else "s")
body = "%s %02x %iIF%s [USB %s, %5s Mbps, %5s%s] (%s)%s" % \
(colorize(1, "%04x:%04x" % (self.vid, self.pid)),
self.iclass, self.nointerfaces, plural,
self.usbver.strip(), self.speed, self.maxpower,
("" if self.wakeup == "" else (", power wakeup: %s" % self.wakeup)),
colorize(2 if is_hub else 1, self.name),
colorize(2, " hub") if is_hub else "")
strg = "%-17s %s\n" % (indent + name, indent + body)
if not (is_hub and not showhubint):
if showeps:
ep = UsbEndpoint(self, "ep_00", self.level+1)
strg += str(ep)
if showint:
for iface in self.interfaces:
strg += str(iface)
for child in self.children:
strg += str(child)
return strg
def usage():
"Displays usage information"
print("Usage: lsusb.py [options]")
print()
print("Options:")
print(" -h, --help display this help")
print(" -i, --interfaces display interface information")
print(" -I, --hub-interfaces display interface information, even for hubs")
print(" -u, --hide-empty-hubs suppress empty hubs")
print(" -U, --hide-hubs suppress all hubs")
print(" -c, --color use colors")
print(" -C, --no-color disable colors")
print(" -e, --endpoints display endpoint info")
print(" -f FILE, --usbids-path FILE")
print(" override filename for /usr/share/usb.ids")
print(" -w, --wakeup display power wakeup setting")
print()
print("Use lsusb.py -ciu to get a nice overview of your USB devices.")
def read_usb():
"Read toplevel USB entries and print"
root_hubs = []
for dirent in os.listdir(prefix):
if not dirent[0:3] == "usb":
continue
usbdev = UsbDevice(None, dirent, 0)
root_hubs.append(usbdev)
root_hubs.sort(key=lambda x: int(x.fname[3:]))
for usbdev in root_hubs:
print(usbdev, end="")
def main(argv):
"main entry point"
global showint, showhubint, noemptyhub, nohub
global cols, usbids, showeps, showwakeup
usecols = None
long_options = [
"help",
"interfaces",
"hub-interfaces",
"hide-empty-hubs",
"hide-hubs",
"color",
"no-color",
"usbids-path=",
"endpoints",
"wakeup",
]
try:
(optlist, args) = getopt.gnu_getopt(argv[1:], "hiIuUwcCef:", long_options)
except getopt.GetoptError as exc:
print("Error:", exc, file=sys.stderr)
sys.exit(2)
for opt in optlist:
if opt[0] in {"-h", "--help"}:
usage()
sys.exit(0)
elif opt[0] in {"-i", "--interfaces"}:
showint = True
elif opt[0] in {"-I", "--hub-interfaces"}:
showint = True
showhubint = True
elif opt[0] in {"-u", "--hide-empty-hubs"}:
noemptyhub = True
elif opt[0] in {"-U", "--hide-hubs"}:
noemptyhub = True
nohub = True
elif opt[0] in {"-c", "--color"}:
usecols = True
elif opt[0] in {"-C", "--no-color"}:
usecols = False
elif opt[0] in {"-f", "--usbids-path"}:
usbids = [opt[1]]
elif opt[0] in {"-e", "--endpoints"}:
showeps = True
elif opt[0] in {"-w", "--wakeup"}:
showwakeup = True
if len(args) > 0:
print("Error: excess args %s ..." % args[0], file=sys.stderr)
sys.exit(2)
if usecols is None:
usecols = sys.stdout.isatty() and os.environ.get("TERM", "dumb") != "dumb"
if usecols:
cols = (norm, bold, red, green, amber, blue)
if usbids[0]:
try:
parse_usb_ids()
except:
print(" WARNING: Failure to read usb.ids", file=sys.stderr)
#print(sys.exc_info(), file=sys.stderr)
read_usb()
# Entry point
if __name__ == "__main__":
main(sys.argv)