usbutils/lsusb.py.in
Greg Kroah-Hartman 86dcab8964 usbutils: convert build system to use meson
Autotools is great for what it is designed for, but that epoch is long
ago, and shipping random files/scripts that are not actually checked
into the build system is ripe for problems.

Convert the build over to use meson instead.  Bonus is that the build
logic is simpler (if not still messy in places), and that the build
itself is way faster.

Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2024-09-02 19:26:57 +02:00

561 lines
14 KiB
Python
Executable File

#!/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 = [
"@DATADIR@/usb.ids",
"/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)