#!/usr/bin/python3 # SPDX-License-Identifier: GPL-2.0 OR GPL-3.0 # # lsusb-VERSION.py # # Displays your USB devices in reasonable form. # # Copyright (c) 2009 Kurt Garloff # Copyright (c) 2013,2018 Kurt Garloff # # 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 prefix = "/sys/bus/usb/devices/" usbids = [ "@usbids@", "/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 "" % 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 "" % 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.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 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 "" % 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" % \ (colorize(1, "%04x:%04x" % (self.vid, self.pid)), self.iclass, self.nointerfaces, plural, self.usbver.strip(), self.speed, self.maxpower, 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() 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 usecols = None long_options = [ "help", "interfaces", "hub-interfaces", "hide-empty-hubs", "hide-hubs", "color", "no-color", "usbids-path=", "endpoints", ] 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] == "-w": print("Warning: option -w is no longer supported", file=sys.stderr) elif opt[0] in {"-f", "--usbids-path"}: usbids = [opt[1]] elif opt[0] in {"-e", "--endpoints"}: showeps = 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)