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