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