Merge ~jocave/checkbox-support:add-lsusb-py-script into checkbox-support:master

Proposed by Jonathan Cave
Status: Merged
Approved by: Jonathan Cave
Approved revision: 477e4845272bc5df9b084c980281ca9725806a40
Merged at revision: c8e97c2cfed156d2e2171a9f54dac9125ef44630
Proposed branch: ~jocave/checkbox-support:add-lsusb-py-script
Merge into: checkbox-support:master
Diff against target: 566 lines (+549/-0)
2 files modified
checkbox_support/scripts/lsusb.py (+547/-0)
setup.py (+2/-0)
Reviewer Review Type Date Requested Status
Sylvain Pineau (community) Approve
Review via email: mp+369382@code.launchpad.net

Description of the change

Add lsusb.oy script and expose in setup.py. This is used by a variety of jobs and launchers so keeping just one copy here now seems sensible.

To post a comment you must log in.
Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/checkbox_support/scripts/lsusb.py b/checkbox_support/scripts/lsusb.py
2new file mode 100755
3index 0000000..c243835
4--- /dev/null
5+++ b/checkbox_support/scripts/lsusb.py
6@@ -0,0 +1,547 @@
7+#!/usr/bin/env python3
8+# lsusb.py
9+# Displays your USB devices in reasonable form.
10+# (c) Kurt Garloff <garloff@suse.de>, 2/2009, GPL v2 or v3.
11+#
12+# Copyright 2016 Canonical Ltd.
13+# Sylvain Pineau <sylvain.pineau@canonical.com>
14+#
15+# Usage: See usage()
16+
17+from functools import total_ordering
18+import getopt
19+import os
20+import re
21+import sys
22+
23+# Global options
24+showint = False
25+showhubint = False
26+noemptyhub = False
27+nohub = False
28+warnsort = False
29+shortmode = False
30+
31+prefix = "/sys/bus/usb/devices/"
32+usbids = "/usr/share/usb.ids"
33+
34+esc = chr(27)
35+norm = esc + "[0;0m"
36+bold = esc + "[0;1m"
37+red = esc + "[0;31m"
38+green = esc + "[0;32m"
39+amber = esc + "[0;33m"
40+
41+cols = ("", "", "", "", "")
42+
43+usbvendors = []
44+usbproducts = []
45+usbclasses = []
46+
47+devlst = (
48+ 'host', # usb-storage
49+ 'video4linux/video', # uvcvideo et al.
50+ 'sound/card', # snd-usb-audio
51+ 'net/', # cdc_ether, ...
52+ 'input/input', # usbhid
53+ 'usb:hiddev', # usb hid
54+ 'bluetooth/hci', # btusb
55+ 'ttyUSB', # btusb
56+ 'tty/', # cdc_acm
57+ 'usb:lp', # usblp
58+ 'usb/', # hiddev, usblp
59+ )
60+
61+
62+def readattr(path, name):
63+ "Read attribute from sysfs and return as string"
64+ f = open(prefix + path + "/" + name)
65+ return f.readline().rstrip("\n")
66+
67+
68+def readlink(path, name):
69+ "Read symlink and return basename"
70+ return os.path.basename(os.readlink(prefix + path + "/" + name))
71+
72+
73+@total_ordering
74+class UsbClass:
75+ "Container for USB Class/Subclass/Protocol"
76+
77+ def __init__(self, cl, sc, pr, str=""):
78+ self.pclass = cl
79+ self.subclass = sc
80+ self.proto = pr
81+ self.desc = str
82+
83+ def __repr__(self):
84+ return self.desc
85+
86+ def __lt__(self, oth):
87+ if self.pclass != oth.pclass:
88+ return self.pclass - oth.pclass
89+ if self.subclass != oth.subclass:
90+ return self.subclass - oth.subclass
91+ return self.proto - oth.proto
92+
93+ def __eq__(self, oth):
94+ if self.pclass != oth.pclass:
95+ return False
96+ if self.subclass != oth.subclass:
97+ return False
98+ return self.proto == oth.proto
99+
100+
101+@total_ordering
102+class UsbVendor:
103+ "Container for USB Vendors"
104+
105+ def __init__(self, vid, vname=""):
106+ self.vid = vid
107+ self.vname = vname
108+
109+ def __repr__(self):
110+ return self.vname
111+
112+ def __lt__(self, oth):
113+ return self.vid - oth.vid
114+
115+ def __eq__(self, oth):
116+ return self.vid == oth.vid
117+
118+
119+@total_ordering
120+class UsbProduct:
121+ "Container for USB VID:PID devices"
122+
123+ def __init__(self, vid, pid, pname=""):
124+ self.vid = vid
125+ self.pid = pid
126+ self.pname = pname
127+
128+ def __repr__(self):
129+ return self.pname
130+
131+ def __lt__(self, oth):
132+ if self.vid != oth.vid:
133+ return self.vid - oth.vid
134+ return self.pid - oth.pid
135+
136+ def __eq__(self, oth):
137+ if self.vid != oth.vid:
138+ return False
139+ return self.pid == oth.pid
140+
141+
142+def ishexdigit(str):
143+ "return True if all digits are valid hex digits"
144+ for dg in str:
145+ if not dg.isdigit() and dg not in 'abcdef':
146+ return False
147+ return True
148+
149+
150+def parse_usb_ids():
151+ "Parse /usr/share/usb.ids and fill usbvendors, usbproducts, usbclasses"
152+ id = 0
153+ sid = 0
154+ mode = 0
155+ strg = ""
156+ cstrg = ""
157+ with open(usbids, encoding="utf-8", errors='ignore') as f:
158+ for ln in f:
159+ if ln[0] == '#':
160+ continue
161+ ln = ln.rstrip('\n')
162+ if len(ln) == 0:
163+ continue
164+ if ishexdigit(ln[0:4]):
165+ mode = 0
166+ id = int(ln[:4], 16)
167+ usbvendors.append(UsbVendor(id, ln[6:]))
168+ continue
169+ if ln[0] == '\t' and ishexdigit(ln[1:3]):
170+ sid = int(ln[1:5], 16)
171+ # USB devices
172+ if mode == 0:
173+ usbproducts.append(UsbProduct(id, sid, ln[7:]))
174+ continue
175+ elif mode == 1:
176+ nm = ln[5:]
177+ if nm != "Unused":
178+ strg = cstrg + ":" + nm
179+ else:
180+ strg = cstrg + ":"
181+ usbclasses.append(UsbClass(id, sid, -1, strg))
182+ continue
183+ if ln[0] == 'C':
184+ mode = 1
185+ id = int(ln[2:4], 16)
186+ cstrg = ln[6:]
187+ usbclasses.append(UsbClass(id, -1, -1, cstrg))
188+ continue
189+ if (
190+ mode == 1 and ln[0] == '\t' and ln[1] == '\t' and
191+ ishexdigit(ln[2:4])
192+ ):
193+ prid = int(ln[2:4], 16)
194+ usbclasses.append(UsbClass(id, sid, prid, strg + ":" + ln[6:]))
195+ continue
196+ mode = 2
197+
198+
199+def find_usb_prod(vid, pid):
200+ "Return device name from USB Vendor:Product list"
201+ strg = ""
202+ dev = UsbVendor(vid, "")
203+ try:
204+ strg = [v for v in usbvendors if v == dev][0].__repr__()
205+ except IndexError:
206+ return ""
207+ dev = UsbProduct(vid, pid, "")
208+ try:
209+ strg += " " + [p for p in usbproducts if p == dev][0].__repr__()
210+ except IndexError:
211+ return strg
212+ return strg
213+
214+
215+def find_usb_class(cid, sid, pid):
216+ "Return USB protocol from usbclasses list"
217+ if cid == 0xff and sid == 0xff and pid == 0xff:
218+ return "Vendor Specific"
219+ dev = UsbClass(cid, sid, pid, "")
220+ try:
221+ return [c for c in usbclasses if c == dev][0].__repr__()
222+ except IndexError:
223+ pass
224+ dev = UsbClass(cid, sid, -1, "")
225+ try:
226+ return [c for c in usbclasses if c == dev][0].__repr__()
227+ except IndexError:
228+ pass
229+ dev = UsbClass(cid, -1, -1, "")
230+ try:
231+ return [c for c in usbclasses if c == dev][0].__repr__()
232+ except IndexError:
233+ return ""
234+
235+
236+def find_storage(hostno):
237+ "Return SCSI block dev names for host"
238+ res = ""
239+ for ent in os.listdir("/sys/class/scsi_device/"):
240+ (host, bus, tgt, lun) = ent.split(":")
241+ if host == hostno:
242+ try:
243+ path = "/sys/class/scsi_device/%s/device/block" % ent
244+ for ent2 in os.listdir(path):
245+ res += ent2 + " "
246+ except:
247+ pass
248+ return res
249+
250+
251+def find_dev(driver, usbname):
252+ "Return pseudo devname that's driven by driver"
253+ res = ""
254+ for nm in devlst:
255+ dir = prefix + usbname
256+ prep = ""
257+ idx = nm.find('/')
258+ if idx != -1:
259+ prep = nm[:idx+1]
260+ dir += "/" + nm[:idx]
261+ nm = nm[idx+1:]
262+ ln = len(nm)
263+ try:
264+ for ent in os.listdir(dir):
265+ if ent[:ln] == nm:
266+ res += prep+ent+" "
267+ if nm == "host":
268+ res += "(" + find_storage(ent[ln:])[:-1] + ")"
269+ except:
270+ pass
271+ return res
272+
273+
274+class UsbInterface:
275+ "Container for USB interface info"
276+
277+ def __init__(self, parent=None, level=1):
278+ self.parent = parent
279+ self.level = level
280+ self.fname = ""
281+ self.iclass = 0
282+ self.isclass = 0
283+ self.iproto = 0
284+ self.noep = 0
285+ self.driver = ""
286+ self.devname = ""
287+ self.protoname = ""
288+
289+ def read(self, fname):
290+ fullpath = ""
291+ if self.parent:
292+ fullpath += self.parent.fname + "/"
293+ fullpath += fname
294+ self.fname = fname
295+ self.iclass = int(readattr(fullpath, "bInterfaceClass"), 16)
296+ self.isclass = int(readattr(fullpath, "bInterfaceSubClass"), 16)
297+ self.iproto = int(readattr(fullpath, "bInterfaceProtocol"), 16)
298+ self.noep = int(readattr(fullpath, "bNumEndpoints"))
299+ try:
300+ self.driver = readlink(fname, "driver")
301+ self.devname = find_dev(self.driver, fname)
302+ except:
303+ pass
304+ self.protoname = find_usb_class(self.iclass, self.isclass, self.iproto)
305+
306+ def __str__(self):
307+ return "%-16s(IF) %02x:%02x:%02x %iEPs (%s) %s%s %s%s%s\n" % \
308+ (" " * self.level+self.fname, self.iclass,
309+ self.isclass, self.iproto, self.noep,
310+ self.protoname,
311+ cols[3], self.driver,
312+ cols[4], self.devname, cols[0])
313+
314+
315+class UsbDevice:
316+ "Container for USB device info"
317+
318+ def __init__(self, parent=None, level=0):
319+ self.parent = parent
320+ self.level = level
321+ self.display_name = ""
322+ self.fname = ""
323+ self.busnum = 0
324+ self.devnum = 0
325+ self.iclass = 0
326+ self.isclass = 0
327+ self.iproto = 0
328+ self.vid = 0
329+ self.pid = 0
330+ self.name = ""
331+ self.usbver = ""
332+ self.speed = ""
333+ self.maxpower = ""
334+ self.noports = 0
335+ self.nointerfaces = 0
336+ self.driver = ""
337+ self.devname = ""
338+ self.interfaces = []
339+ self.children = []
340+
341+ def read(self, fname):
342+ self.fname = fname
343+ self.iclass = int(readattr(fname, "bDeviceClass"), 16)
344+ self.isclass = int(readattr(fname, "bDeviceSubClass"), 16)
345+ self.iproto = int(readattr(fname, "bDeviceProtocol"), 16)
346+ self.vid = int(readattr(fname, "idVendor"), 16)
347+ self.pid = int(readattr(fname, "idProduct"), 16)
348+ self.busnum = int(readattr(fname, "busnum"))
349+ self.devnum = int(readattr(fname, "devnum"))
350+ self.usbver = readattr(fname, "version")
351+ try:
352+ self.name = readattr(fname, "manufacturer") + " " \
353+ + readattr(fname, "product")
354+ if self.name[:5] == "Linux":
355+ rx = re.compile(r"Linux [^ ]* .hci[-_]hcd")
356+ mch = rx.match(self.name)
357+ if mch:
358+ self.name = "Linux Foundation %.2f root hub" % float(
359+ self.usbver)
360+ except:
361+ pass
362+ if not self.name:
363+ self.name = find_usb_prod(self.vid, self.pid)
364+ # Some USB Card readers have a better name than Generic ...
365+ if self.name[:7] == "Generic":
366+ oldnm = self.name
367+ self.name = find_usb_prod(self.vid, self.pid)
368+ if not self.name:
369+ self.name = oldnm
370+ self.speed = readattr(fname, "speed")
371+ self.maxpower = readattr(fname, "bMaxPower")
372+ self.noports = int(readattr(fname, "maxchild"))
373+ try:
374+ self.nointerfaces = int(readattr(fname, "bNumInterfaces"))
375+ except:
376+ self.nointerfaces = 0
377+ try:
378+ self.driver = readlink(fname, "driver")
379+ self.devname = find_dev(self.driver, fname)
380+ except:
381+ pass
382+
383+ def readchildren(self):
384+ if self.fname[0:3] == "usb":
385+ fname = self.fname[3:]
386+ else:
387+ fname = self.fname
388+ for dirent in os.listdir(prefix + self.fname):
389+ if not dirent[0:1].isdigit():
390+ continue
391+ if os.access(prefix + dirent + "/bInterfaceClass", os.R_OK):
392+ iface = UsbInterface(self, self.level+1)
393+ iface.read(dirent)
394+ self.interfaces.append(iface)
395+ else:
396+ usbdev = UsbDevice(self, self.level+1)
397+ usbdev.read(dirent)
398+ usbdev.readchildren()
399+ self.children.append(usbdev)
400+
401+ def __str__(self):
402+ if self.iclass == 9:
403+ col = cols[2]
404+ if noemptyhub and len(self.children) == 0:
405+ return ""
406+ if nohub:
407+ str = ""
408+ else:
409+ col = cols[1]
410+ if not nohub or self.iclass != 9:
411+ if shortmode:
412+ str = "ID %04x:%04x %s" % (self.vid, self.pid, self.name)
413+ else:
414+ str = "Bus %03d Device %03d: ID %04x:%04x %s" % \
415+ (self.busnum, self.devnum, self.vid, self.pid, self.name)
416+ str += "\n"
417+ if showint:
418+ for iface in self.interfaces:
419+ str += iface.__str__()
420+ for child in self.children:
421+ str += child.__str__()
422+ return str
423+
424+
425+def deepcopy(lst):
426+ "Returns a deep copy from the list lst"
427+ copy = []
428+ for item in lst:
429+ copy.append(item)
430+ return copy
431+
432+
433+def display_diff(lst1, lst2, fmtstr, args):
434+ "Compare lists (same length!) and display differences"
435+ for idx in range(0, len(lst1)):
436+ if lst1[idx] != lst2[idx]:
437+ print("Warning: " + fmtstr % args(lst2[idx]))
438+
439+
440+def fix_usbvend():
441+ "Sort USB vendor list and (optionally) display diffs"
442+ if warnsort:
443+ oldusbvend = deepcopy(usbvendors)
444+ usbvendors.sort()
445+ if warnsort:
446+ display_diff(usbvendors, oldusbvend, "Unsorted Vendor ID %04x",
447+ lambda x: (x.vid,))
448+
449+
450+def fix_usbprod():
451+ "Sort USB products list"
452+ if warnsort:
453+ oldusbprod = deepcopy(usbproducts)
454+ usbproducts.sort()
455+ if warnsort:
456+ display_diff(usbproducts, oldusbprod,
457+ "Unsorted Vendor:Product ID %04x:%04x",
458+ lambda x: (x.vid, x.pid))
459+
460+
461+def fix_usbclass():
462+ "Sort USB class list"
463+ if warnsort:
464+ oldusbcls = deepcopy(usbclasses)
465+ usbclasses.sort()
466+ if warnsort:
467+ display_diff(usbclasses, oldusbcls,
468+ "Unsorted USB class %02x:%02x:%02x",
469+ lambda x: (x.pclass, x.subclass, x.proto))
470+
471+
472+def usage():
473+ "Displays usage information"
474+ print("Usage: lsusb.py [options]")
475+ print("Options:")
476+ print(" -h display this help")
477+ print(" -i display interface information")
478+ print(" -I display interface information, even for hubs")
479+ print(" -u suppress empty hubs")
480+ print(" -U suppress all hubs")
481+ print(" -c use colors")
482+ print(" -s short mode")
483+ print(" -w display warning if usb.ids is not sorted correctly")
484+ print(" -f FILE override filename for /usr/share/usb.ids")
485+ return 2
486+
487+
488+def read_usb():
489+ "Read toplevel USB entries and print"
490+ for dirent in os.listdir(prefix):
491+ if not dirent[0:3] == "usb":
492+ continue
493+ usbdev = UsbDevice(None, 0)
494+ usbdev.read(dirent)
495+ usbdev.readchildren()
496+ print(usbdev.__str__(), end="")
497+
498+
499+def main():
500+ "main entry point"
501+ global showint, showhubint, noemptyhub, nohub, warnsort, cols, usbids, \
502+ shortmode
503+ try:
504+ (optlist, args) = getopt.gnu_getopt(sys.argv[1:], "hiIuUwcsf:", ("help",))
505+ except getopt.GetoptError as exc:
506+ print("Error:", exc)
507+ sys.exit(usage())
508+ for opt in optlist:
509+ if opt[0] == "-h" or opt[0] == "--help":
510+ usage()
511+ sys.exit(0)
512+ if opt[0] == "-i":
513+ showint = True
514+ continue
515+ if opt[0] == "-I":
516+ showint = True
517+ showhubint = True
518+ continue
519+ if opt[0] == "-u":
520+ noemptyhub = True
521+ continue
522+ if opt[0] == "-U":
523+ noemptyhub = True
524+ nohub = True
525+ continue
526+ if opt[0] == "-c":
527+ cols = (norm, bold, red, green, amber)
528+ continue
529+ if opt[0] == "-w":
530+ warnsort = True
531+ continue
532+ if opt[0] == "-f":
533+ usbids = opt[1]
534+ continue
535+ if opt[0] == "-s":
536+ shortmode = True
537+ continue
538+ if len(args) > 0:
539+ print("Error: excess args %s ..." % args[0])
540+ sys.exit(usage())
541+ try:
542+ parse_usb_ids()
543+ fix_usbvend()
544+ fix_usbprod()
545+ fix_usbclass()
546+ except:
547+ print(" WARNING: Failure to read usb.ids", file=sys.stderr)
548+ print(sys.exc_info(), file=sys.stderr)
549+ read_usb()
550+
551+# Entry point
552+if __name__ == "__main__":
553+ main()
554diff --git a/setup.py b/setup.py
555index 4f4c992..7f115f8 100755
556--- a/setup.py
557+++ b/setup.py
558@@ -88,6 +88,8 @@ setup(
559 "checkbox_support.scripts.snap_connect:main"),
560 ("checkbox-support-eddystone_scanner="
561 "checkbox_support.scripts.eddystone_scanner:main"),
562+ ("checkbox-support-lsusb="
563+ "checkbox_support.scripts.lsusb:main"),
564 ],
565 },
566 )

Subscribers

People subscribed via source and target branches