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