usbutils/lsusb.py
Emil Velikov 5d19330927 Rename .in files to their final state
With the earlier commits we've removed any processing done on these
in(put) files. Just rename them to their final form.

Signed-off-by: Emil Velikov <emil.l.velikov@gmail.com>
2024-09-19 17:45:09 +01:00

558 lines
14 KiB
Python
Executable File

#!/usr/bin/python3
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
#
# 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 = [
"/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/{hwdata/,}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/{hwdata/,}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)