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