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