examples/vm_power: fix 32-bit build
[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         global raw_output
438         runpath = ""
439         ldlibpath = os.environ.get('LD_LIBRARY_PATH')
440         if ldlibpath is None:
441             ldlibpath = ""
442
443         dynsec = self._section_from_spec(".dynamic")
444         try:
445             runpath = self.get_dt_runpath(dynsec)
446         except AttributeError:
447             # dynsec is None, just return
448             return
449
450         for tag in dynsec.iter_tags():
451             # pyelftools may return byte-strings, force decode them
452             if force_unicode(tag.entry.d_tag) == 'DT_NEEDED':
453                 if 'librte_pmd' in force_unicode(tag.needed):
454                     library = search_file(force_unicode(tag.needed),
455                                           runpath + ":" + ldlibpath +
456                                           ":/usr/lib64:/lib64:/usr/lib:/lib")
457                     if library is not None:
458                         if raw_output is False:
459                             print("Scanning %s for pmd information" % library)
460                         with io.open(library, 'rb') as file:
461                             try:
462                                 libelf = ReadElf(file, sys.stdout)
463                             except ELFError:
464                                 print("%s is no an ELF file" % library)
465                                 continue
466                             libelf.process_dt_needed_entries()
467                             libelf.display_pmd_info_strings(".rodata")
468                             file.close()
469
470
471 # compat: remove force_unicode & force_bytes when pyelftools<=0.23 support is
472 # dropped.
473 def force_unicode(s):
474     if hasattr(s, 'decode') and callable(s.decode):
475         s = s.decode('latin-1')  # same encoding used in pyelftools py3compat
476     return s
477
478
479 def force_bytes(s):
480     if hasattr(s, 'encode') and callable(s.encode):
481         s = s.encode('latin-1')  # same encoding used in pyelftools py3compat
482     return s
483
484
485 def scan_autoload_path(autoload_path):
486     global raw_output
487
488     if os.path.exists(autoload_path) is False:
489         return
490
491     try:
492         dirs = os.listdir(autoload_path)
493     except OSError:
494         # Couldn't read the directory, give up
495         return
496
497     for d in dirs:
498         dpath = os.path.join(autoload_path, d)
499         if os.path.isdir(dpath):
500             scan_autoload_path(dpath)
501         if os.path.isfile(dpath):
502             try:
503                 file = io.open(dpath, 'rb')
504                 readelf = ReadElf(file, sys.stdout)
505             except ELFError:
506                 # this is likely not an elf file, skip it
507                 continue
508             except IOError:
509                 # No permission to read the file, skip it
510                 continue
511
512             if raw_output is False:
513                 print("Hw Support for library %s" % d)
514             readelf.display_pmd_info_strings(".rodata")
515             file.close()
516
517
518 def scan_for_autoload_pmds(dpdk_path):
519     """
520     search the specified application or path for a pmd autoload path
521     then scan said path for pmds and report hw support
522     """
523     global raw_output
524
525     if (os.path.isfile(dpdk_path) is False):
526         if raw_output is False:
527             print("Must specify a file name")
528         return
529
530     file = io.open(dpdk_path, 'rb')
531     try:
532         readelf = ReadElf(file, sys.stdout)
533     except ElfError:
534         if raw_output is False:
535             print("Unable to parse %s" % file)
536         return
537
538     (autoload_path, scannedfile) = readelf.search_for_autoload_path()
539     if not autoload_path:
540         if (raw_output is False):
541             print("No autoload path configured in %s" % dpdk_path)
542         return
543     if (raw_output is False):
544         if (scannedfile is None):
545             scannedfile = dpdk_path
546         print("Found autoload path %s in %s" % (autoload_path, scannedfile))
547
548     file.close()
549     if (raw_output is False):
550         print("Discovered Autoload HW Support:")
551     scan_autoload_path(autoload_path)
552     return
553
554
555 def main(stream=None):
556     global raw_output
557     global pcidb
558
559     pcifile_default = "./pci.ids"  # For unknown OS's assume local file
560     if platform.system() == 'Linux':
561         # hwdata is the legacy location, misc is supported going forward
562         pcifile_default = "/usr/share/misc/pci.ids"
563         if not os.path.exists(pcifile_default):
564             pcifile_default = "/usr/share/hwdata/pci.ids"
565     elif platform.system() == 'FreeBSD':
566         pcifile_default = "/usr/local/share/pciids/pci.ids"
567         if not os.path.exists(pcifile_default):
568             pcifile_default = "/usr/share/misc/pci_vendors"
569
570     optparser = OptionParser(
571         usage='usage: %prog [-hrtp] [-d <pci id file] <elf-file>',
572         description="Dump pmd hardware support info",
573         add_help_option=True)
574     optparser.add_option('-r', '--raw',
575                          action='store_true', dest='raw_output',
576                          help='Dump raw json strings')
577     optparser.add_option("-d", "--pcidb", dest="pcifile",
578                          help="specify a pci database "
579                               "to get vendor names from",
580                          default=pcifile_default, metavar="FILE")
581     optparser.add_option("-t", "--table", dest="tblout",
582                          help="output information on hw support as a "
583                               "hex table",
584                          action='store_true')
585     optparser.add_option("-p", "--plugindir", dest="pdir",
586                          help="scan dpdk for autoload plugins",
587                          action='store_true')
588
589     options, args = optparser.parse_args()
590
591     if options.raw_output:
592         raw_output = True
593
594     if options.pcifile:
595         pcidb = PCIIds(options.pcifile)
596         if pcidb is None:
597             print("Pci DB file not found")
598             exit(1)
599
600     if options.tblout:
601         options.pcifile = None
602         pcidb = None
603
604     if (len(args) == 0):
605         optparser.print_usage()
606         exit(1)
607
608     if options.pdir is True:
609         exit(scan_for_autoload_pmds(args[0]))
610
611     ldlibpath = os.environ.get('LD_LIBRARY_PATH')
612     if (ldlibpath is None):
613         ldlibpath = ""
614
615     if (os.path.exists(args[0]) is True):
616         myelffile = args[0]
617     else:
618         myelffile = search_file(
619             args[0], ldlibpath + ":/usr/lib64:/lib64:/usr/lib:/lib")
620
621     if (myelffile is None):
622         print("File not found")
623         sys.exit(1)
624
625     with io.open(myelffile, 'rb') as file:
626         try:
627             readelf = ReadElf(file, sys.stdout)
628             readelf.process_dt_needed_entries()
629             readelf.display_pmd_info_strings(".rodata")
630             sys.exit(0)
631
632         except ELFError as ex:
633             sys.stderr.write('ELF error: %s\n' % ex)
634             sys.exit(1)
635
636
637 # -------------------------------------------------------------------------
638 if __name__ == '__main__':
639     main()