usertools: replace explicit boolean checks
[dpdk.git] / usertools / dpdk-pmdinfo.py
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: BSD-3-Clause
3 # Copyright(c) 2016  Neil Horman <nhorman@tuxdriver.com>
4
5 # -------------------------------------------------------------------------
6 #
7 # Utility to dump PMD_INFO_STRING support from an object file
8 #
9 # -------------------------------------------------------------------------
10 import json
11 import os
12 import platform
13 import sys
14 from elftools.common.exceptions import ELFError
15 from elftools.common.py3compat import byte2int
16 from elftools.elf.elffile import ELFFile
17 from optparse import OptionParser
18
19 # For running from development directory. It should take precedence over the
20 # installed pyelftools.
21 sys.path.insert(0, '.')
22
23 raw_output = False
24 pcidb = None
25
26 # ===========================================
27
28 class Vendor:
29     """
30     Class for vendors. This is the top level class
31     for the devices belong to a specific vendor.
32     self.devices is the device dictionary
33     subdevices are in each device.
34     """
35
36     def __init__(self, vendorStr):
37         """
38         Class initializes with the raw line from pci.ids
39         Parsing takes place inside __init__
40         """
41         self.ID = vendorStr.split()[0]
42         self.name = vendorStr.replace("%s " % self.ID, "").rstrip()
43         self.devices = {}
44
45     def addDevice(self, deviceStr):
46         """
47         Adds a device to self.devices
48         takes the raw line from pci.ids
49         """
50         s = deviceStr.strip()
51         devID = s.split()[0]
52         if devID in self.devices:
53             pass
54         else:
55             self.devices[devID] = Device(deviceStr)
56
57     def report(self):
58         print(self.ID, self.name)
59         for id, dev in self.devices.items():
60             dev.report()
61
62     def find_device(self, devid):
63         # convert to a hex string and remove 0x
64         devid = hex(devid)[2:]
65         try:
66             return self.devices[devid]
67         except:
68             return Device("%s  Unknown Device" % devid)
69
70
71 class Device:
72
73     def __init__(self, deviceStr):
74         """
75         Class for each device.
76         Each vendor has its own devices dictionary.
77         """
78         s = deviceStr.strip()
79         self.ID = s.split()[0]
80         self.name = s.replace("%s  " % self.ID, "")
81         self.subdevices = {}
82
83     def report(self):
84         print("\t%s\t%s" % (self.ID, self.name))
85         for subID, subdev in self.subdevices.items():
86             subdev.report()
87
88     def addSubDevice(self, subDeviceStr):
89         """
90         Adds a subvendor, subdevice to device.
91         Uses raw line from pci.ids
92         """
93         s = subDeviceStr.strip()
94         spl = s.split()
95         subVendorID = spl[0]
96         subDeviceID = spl[1]
97         subDeviceName = s.split("  ")[-1]
98         devID = "%s:%s" % (subVendorID, subDeviceID)
99         self.subdevices[devID] = SubDevice(
100             subVendorID, subDeviceID, subDeviceName)
101
102     def find_subid(self, subven, subdev):
103         subven = hex(subven)[2:]
104         subdev = hex(subdev)[2:]
105         devid = "%s:%s" % (subven, subdev)
106
107         try:
108             return self.subdevices[devid]
109         except:
110             if (subven == "ffff" and subdev == "ffff"):
111                 return SubDevice("ffff", "ffff", "(All Subdevices)")
112             return SubDevice(subven, subdev, "(Unknown Subdevice)")
113
114
115 class SubDevice:
116     """
117     Class for subdevices.
118     """
119
120     def __init__(self, vendor, device, name):
121         """
122         Class initializes with vendorid, deviceid and name
123         """
124         self.vendorID = vendor
125         self.deviceID = device
126         self.name = name
127
128     def report(self):
129         print("\t\t%s\t%s\t%s" % (self.vendorID, self.deviceID, self.name))
130
131
132 class PCIIds:
133     """
134     Top class for all pci.ids entries.
135     All queries will be asked to this class.
136     PCIIds.vendors["0e11"].devices["0046"].\
137     subdevices["0e11:4091"].name  =  "Smart Array 6i"
138     """
139
140     def __init__(self, filename):
141         """
142         Prepares the directories.
143         Checks local data file.
144         Tries to load from local, if not found, downloads from web
145         """
146         self.version = ""
147         self.date = ""
148         self.vendors = {}
149         self.contents = None
150         self.readLocal(filename)
151         self.parse()
152
153     def reportVendors(self):
154         """Reports the vendors
155         """
156         for vid, v in self.vendors.items():
157             print(v.ID, v.name)
158
159     def report(self, vendor=None):
160         """
161         Reports everything for all vendors or a specific vendor
162         PCIIds.report()  reports everything
163         PCIIDs.report("0e11") reports only "Compaq Computer Corporation"
164         """
165         if vendor is not None:
166             self.vendors[vendor].report()
167         else:
168             for vID, v in self.vendors.items():
169                 v.report()
170
171     def find_vendor(self, vid):
172         # convert vid to a hex string and remove the 0x
173         vid = hex(vid)[2:]
174
175         try:
176             return self.vendors[vid]
177         except:
178             return Vendor("%s Unknown Vendor" % (vid))
179
180     def findDate(self, content):
181         for l in content:
182             if l.find("Date:") > -1:
183                 return l.split()[-2].replace("-", "")
184         return None
185
186     def parse(self):
187         if len(self.contents) < 1:
188             print("data/%s-pci.ids not found" % self.date)
189         else:
190             vendorID = ""
191             deviceID = ""
192             for l in self.contents:
193                 if l[0] == "#":
194                     continue
195                 elif len(l.strip()) == 0:
196                     continue
197                 else:
198                     if l.find("\t\t") == 0:
199                         self.vendors[vendorID].devices[
200                             deviceID].addSubDevice(l)
201                     elif l.find("\t") == 0:
202                         deviceID = l.strip().split()[0]
203                         self.vendors[vendorID].addDevice(l)
204                     else:
205                         vendorID = l.split()[0]
206                         self.vendors[vendorID] = Vendor(l)
207
208     def readLocal(self, filename):
209         """
210         Reads the local file
211         """
212         with open(filename, 'r', encoding='utf-8') as f:
213             self.contents = f.readlines()
214         self.date = self.findDate(self.contents)
215
216     def loadLocal(self):
217         """
218         Loads database from local. If there is no file,
219         it creates a new one from web
220         """
221         self.date = idsfile[0].split("/")[1].split("-")[0]
222         self.readLocal()
223
224
225 # =======================================
226
227 def search_file(filename, search_path):
228     """ Given a search path, find file with requested name """
229     for path in search_path.split(':'):
230         candidate = os.path.join(path, filename)
231         if os.path.exists(candidate):
232             return os.path.abspath(candidate)
233     return None
234
235
236 class ReadElf(object):
237     """ display_* methods are used to emit output into the output stream
238     """
239
240     def __init__(self, file, output):
241         """ file:
242                 stream object with the ELF file to read
243
244             output:
245                 output stream to write to
246         """
247         self.elffile = ELFFile(file)
248         self.output = output
249
250         # Lazily initialized if a debug dump is requested
251         self._dwarfinfo = None
252
253         self._versioninfo = None
254
255     def _section_from_spec(self, spec):
256         """ Retrieve a section given a "spec" (either number or name).
257             Return None if no such section exists in the file.
258         """
259         try:
260             num = int(spec)
261             if num < self.elffile.num_sections():
262                 return self.elffile.get_section(num)
263             return None
264         except ValueError:
265             # Not a number. Must be a name then
266             section = self.elffile.get_section_by_name(force_unicode(spec))
267             if section is None:
268                 # No match with a unicode name.
269                 # Some versions of pyelftools (<= 0.23) store internal strings
270                 # as bytes. Try again with the name encoded as bytes.
271                 section = self.elffile.get_section_by_name(force_bytes(spec))
272             return section
273
274     def pretty_print_pmdinfo(self, pmdinfo):
275         global pcidb
276
277         for i in pmdinfo["pci_ids"]:
278             vendor = pcidb.find_vendor(i[0])
279             device = vendor.find_device(i[1])
280             subdev = device.find_subid(i[2], i[3])
281             print("%s (%s) : %s (%s) %s" %
282                   (vendor.name, vendor.ID, device.name,
283                    device.ID, subdev.name))
284
285     def parse_pmd_info_string(self, mystring):
286         global raw_output
287         global pcidb
288
289         optional_pmd_info = [
290             {'id': 'params', 'tag': 'PMD PARAMETERS'},
291             {'id': 'kmod', 'tag': 'PMD KMOD DEPENDENCIES'}
292         ]
293
294         i = mystring.index("=")
295         mystring = mystring[i + 2:]
296         pmdinfo = json.loads(mystring)
297
298         if raw_output:
299             print(json.dumps(pmdinfo))
300             return
301
302         print("PMD NAME: " + pmdinfo["name"])
303         for i in optional_pmd_info:
304             try:
305                 print("%s: %s" % (i['tag'], pmdinfo[i['id']]))
306             except KeyError:
307                 continue
308
309         if len(pmdinfo["pci_ids"]) != 0:
310             print("PMD HW SUPPORT:")
311             if pcidb is not None:
312                 self.pretty_print_pmdinfo(pmdinfo)
313             else:
314                 print("VENDOR\t DEVICE\t SUBVENDOR\t SUBDEVICE")
315                 for i in pmdinfo["pci_ids"]:
316                     print("0x%04x\t 0x%04x\t 0x%04x\t\t 0x%04x" %
317                           (i[0], i[1], i[2], i[3]))
318
319         print("")
320
321     def display_pmd_info_strings(self, section_spec):
322         """ Display a strings dump of a section. section_spec is either a
323             section number or a name.
324         """
325         section = self._section_from_spec(section_spec)
326         if section is None:
327             return
328
329         data = section.data()
330         dataptr = 0
331
332         while dataptr < len(data):
333             while (dataptr < len(data) and
334                     not 32 <= byte2int(data[dataptr]) <= 127):
335                 dataptr += 1
336
337             if dataptr >= len(data):
338                 break
339
340             endptr = dataptr
341             while endptr < len(data) and byte2int(data[endptr]) != 0:
342                 endptr += 1
343
344             # pyelftools may return byte-strings, force decode them
345             mystring = force_unicode(data[dataptr:endptr])
346             rc = mystring.find("PMD_INFO_STRING")
347             if rc != -1:
348                 self.parse_pmd_info_string(mystring)
349
350             dataptr = endptr
351
352     def find_librte_eal(self, section):
353         for tag in section.iter_tags():
354             # pyelftools may return byte-strings, force decode them
355             if force_unicode(tag.entry.d_tag) == 'DT_NEEDED':
356                 if "librte_eal" in force_unicode(tag.needed):
357                     return force_unicode(tag.needed)
358         return None
359
360     def search_for_autoload_path(self):
361         scanelf = self
362         scanfile = None
363         library = None
364
365         section = self._section_from_spec(".dynamic")
366         try:
367             eallib = self.find_librte_eal(section)
368             if eallib is not None:
369                 ldlibpath = os.environ.get('LD_LIBRARY_PATH')
370                 if ldlibpath is None:
371                     ldlibpath = ""
372                 dtr = self.get_dt_runpath(section)
373                 library = search_file(eallib,
374                                       dtr + ":" + ldlibpath +
375                                       ":/usr/lib64:/lib64:/usr/lib:/lib")
376                 if library is None:
377                     return (None, None)
378                 if not raw_output:
379                     print("Scanning for autoload path in %s" % library)
380                 scanfile = open(library, 'rb')
381                 scanelf = ReadElf(scanfile, sys.stdout)
382         except AttributeError:
383             # Not a dynamic binary
384             pass
385         except ELFError:
386             scanfile.close()
387             return (None, None)
388
389         section = scanelf._section_from_spec(".rodata")
390         if section is None:
391             if scanfile is not None:
392                 scanfile.close()
393             return (None, None)
394
395         data = section.data()
396         dataptr = 0
397
398         while dataptr < len(data):
399             while (dataptr < len(data) and
400                     not 32 <= byte2int(data[dataptr]) <= 127):
401                 dataptr += 1
402
403             if dataptr >= len(data):
404                 break
405
406             endptr = dataptr
407             while endptr < len(data) and byte2int(data[endptr]) != 0:
408                 endptr += 1
409
410             # pyelftools may return byte-strings, force decode them
411             mystring = force_unicode(data[dataptr:endptr])
412             rc = mystring.find("DPDK_PLUGIN_PATH")
413             if rc != -1:
414                 rc = mystring.find("=")
415                 return (mystring[rc + 1:], library)
416
417             dataptr = endptr
418         if scanfile is not None:
419             scanfile.close()
420         return (None, None)
421
422     def get_dt_runpath(self, dynsec):
423         for tag in dynsec.iter_tags():
424             # pyelftools may return byte-strings, force decode them
425             if force_unicode(tag.entry.d_tag) == 'DT_RUNPATH':
426                 return force_unicode(tag.runpath)
427         return ""
428
429     def process_dt_needed_entries(self):
430         """ Look to see if there are any DT_NEEDED entries in the binary
431             And process those if there are
432         """
433         runpath = ""
434         ldlibpath = os.environ.get('LD_LIBRARY_PATH')
435         if ldlibpath is None:
436             ldlibpath = ""
437
438         dynsec = self._section_from_spec(".dynamic")
439         try:
440             runpath = self.get_dt_runpath(dynsec)
441         except AttributeError:
442             # dynsec is None, just return
443             return
444
445         for tag in dynsec.iter_tags():
446             # pyelftools may return byte-strings, force decode them
447             if force_unicode(tag.entry.d_tag) == 'DT_NEEDED':
448                 if 'librte_' in force_unicode(tag.needed):
449                     library = search_file(force_unicode(tag.needed),
450                                           runpath + ":" + ldlibpath +
451                                           ":/usr/lib64:/lib64:/usr/lib:/lib")
452                     if library is not None:
453                         with open(library, 'rb') as file:
454                             try:
455                                 libelf = ReadElf(file, sys.stdout)
456                             except ELFError:
457                                 print("%s is no an ELF file" % library)
458                                 continue
459                             libelf.process_dt_needed_entries()
460                             libelf.display_pmd_info_strings(".rodata")
461                             file.close()
462
463
464 # compat: remove force_unicode & force_bytes when pyelftools<=0.23 support is
465 # dropped.
466 def force_unicode(s):
467     if hasattr(s, 'decode') and callable(s.decode):
468         s = s.decode('latin-1')  # same encoding used in pyelftools py3compat
469     return s
470
471
472 def force_bytes(s):
473     if hasattr(s, 'encode') and callable(s.encode):
474         s = s.encode('latin-1')  # same encoding used in pyelftools py3compat
475     return s
476
477
478 def scan_autoload_path(autoload_path):
479     global raw_output
480
481     if not os.path.exists(autoload_path):
482         return
483
484     try:
485         dirs = os.listdir(autoload_path)
486     except OSError:
487         # Couldn't read the directory, give up
488         return
489
490     for d in dirs:
491         dpath = os.path.join(autoload_path, d)
492         if os.path.isdir(dpath):
493             scan_autoload_path(dpath)
494         if os.path.isfile(dpath):
495             try:
496                 file = open(dpath, 'rb')
497                 readelf = ReadElf(file, sys.stdout)
498             except ELFError:
499                 # this is likely not an elf file, skip it
500                 continue
501             except IOError:
502                 # No permission to read the file, skip it
503                 continue
504
505             if not raw_output:
506                 print("Hw Support for library %s" % d)
507             readelf.display_pmd_info_strings(".rodata")
508             file.close()
509
510
511 def scan_for_autoload_pmds(dpdk_path):
512     """
513     search the specified application or path for a pmd autoload path
514     then scan said path for pmds and report hw support
515     """
516     global raw_output
517
518     if not os.path.isfile(dpdk_path):
519         if not raw_output:
520             print("Must specify a file name")
521         return
522
523     file = open(dpdk_path, 'rb')
524     try:
525         readelf = ReadElf(file, sys.stdout)
526     except ElfError:
527         if not raw_output:
528             print("Unable to parse %s" % file)
529         return
530
531     (autoload_path, scannedfile) = readelf.search_for_autoload_path()
532     if not autoload_path:
533         if not raw_output:
534             print("No autoload path configured in %s" % dpdk_path)
535         return
536     if not raw_output:
537         if scannedfile is None:
538             scannedfile = dpdk_path
539         print("Found autoload path %s in %s" % (autoload_path, scannedfile))
540
541     file.close()
542     if not raw_output:
543         print("Discovered Autoload HW Support:")
544     scan_autoload_path(autoload_path)
545     return
546
547
548 def main(stream=None):
549     global raw_output
550     global pcidb
551
552     pcifile_default = "./pci.ids"  # For unknown OS's assume local file
553     if platform.system() == 'Linux':
554         # hwdata is the legacy location, misc is supported going forward
555         pcifile_default = "/usr/share/misc/pci.ids"
556         if not os.path.exists(pcifile_default):
557             pcifile_default = "/usr/share/hwdata/pci.ids"
558     elif platform.system() == 'FreeBSD':
559         pcifile_default = "/usr/local/share/pciids/pci.ids"
560         if not os.path.exists(pcifile_default):
561             pcifile_default = "/usr/share/misc/pci_vendors"
562
563     optparser = OptionParser(
564         usage='usage: %prog [-hrtp] [-d <pci id file] <elf-file>',
565         description="Dump pmd hardware support info",
566         add_help_option=True)
567     optparser.add_option('-r', '--raw',
568                          action='store_true', dest='raw_output',
569                          help='Dump raw json strings')
570     optparser.add_option("-d", "--pcidb", dest="pcifile",
571                          help="specify a pci database "
572                               "to get vendor names from",
573                          default=pcifile_default, metavar="FILE")
574     optparser.add_option("-t", "--table", dest="tblout",
575                          help="output information on hw support as a "
576                               "hex table",
577                          action='store_true')
578     optparser.add_option("-p", "--plugindir", dest="pdir",
579                          help="scan dpdk for autoload plugins",
580                          action='store_true')
581
582     options, args = optparser.parse_args()
583
584     if options.raw_output:
585         raw_output = True
586
587     if options.pcifile:
588         pcidb = PCIIds(options.pcifile)
589         if pcidb is None:
590             print("Pci DB file not found")
591             exit(1)
592
593     if options.tblout:
594         options.pcifile = None
595         pcidb = None
596
597     if len(args) == 0:
598         optparser.print_usage()
599         exit(1)
600
601     if options.pdir:
602         exit(scan_for_autoload_pmds(args[0]))
603
604     ldlibpath = os.environ.get('LD_LIBRARY_PATH')
605     if ldlibpath is None:
606         ldlibpath = ""
607
608     if os.path.exists(args[0]):
609         myelffile = args[0]
610     else:
611         myelffile = search_file(
612             args[0], ldlibpath + ":/usr/lib64:/lib64:/usr/lib:/lib")
613
614     if myelffile is None:
615         print("File not found")
616         sys.exit(1)
617
618     with open(myelffile, 'rb') as file:
619         try:
620             readelf = ReadElf(file, sys.stdout)
621             readelf.process_dt_needed_entries()
622             readelf.display_pmd_info_strings(".rodata")
623             sys.exit(0)
624
625         except ELFError as ex:
626             sys.stderr.write('ELF error: %s\n' % ex)
627             sys.exit(1)
628
629
630 # -------------------------------------------------------------------------
631 if __name__ == '__main__':
632     main()