net/sfc: support multicast addresses list controls
[dpdk.git] / usertools / dpdk-pmdinfo.py
1 #!/usr/bin/env python
2
3 # -------------------------------------------------------------------------
4 #
5 # Utility to dump PMD_INFO_STRING support from an object file
6 #
7 # -------------------------------------------------------------------------
8 from __future__ import print_function
9 import json
10 import os
11 import platform
12 import string
13 import sys
14 from elftools.common.exceptions import ELFError
15 from elftools.common.py3compat import (byte2int, bytes2str, str2bytes)
16 from elftools.elf.elffile import ELFFile
17 from optparse import OptionParser
18
19 # For running from development directory. It should take precedence over the
20 # installed pyelftools.
21 sys.path.insert(0, '.')
22
23 raw_output = False
24 pcidb = None
25
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         self.contents = open(filename).readlines()
215         self.date = self.findDate(self.contents)
216
217     def loadLocal(self):
218         """
219         Loads database from local. If there is no file,
220         it creates a new one from web
221         """
222         self.date = idsfile[0].split("/")[1].split("-")[0]
223         self.readLocal()
224
225
226 # =======================================
227
228 def search_file(filename, search_path):
229     """ Given a search path, find file with requested name """
230     for path in string.split(search_path, ":"):
231         candidate = os.path.join(path, filename)
232         if os.path.exists(candidate):
233             return os.path.abspath(candidate)
234     return None
235
236
237 class ReadElf(object):
238     """ display_* methods are used to emit output into the output stream
239     """
240
241     def __init__(self, file, output):
242         """ file:
243                 stream object with the ELF file to read
244
245             output:
246                 output stream to write to
247         """
248         self.elffile = ELFFile(file)
249         self.output = output
250
251         # Lazily initialized if a debug dump is requested
252         self._dwarfinfo = None
253
254         self._versioninfo = None
255
256     def _section_from_spec(self, spec):
257         """ Retrieve a section given a "spec" (either number or name).
258             Return None if no such section exists in the file.
259         """
260         try:
261             num = int(spec)
262             if num < self.elffile.num_sections():
263                 return self.elffile.get_section(num)
264             else:
265                 return None
266         except ValueError:
267             # Not a number. Must be a name then
268             return self.elffile.get_section_by_name(str2bytes(spec))
269
270     def pretty_print_pmdinfo(self, pmdinfo):
271         global pcidb
272
273         for i in pmdinfo["pci_ids"]:
274             vendor = pcidb.find_vendor(i[0])
275             device = vendor.find_device(i[1])
276             subdev = device.find_subid(i[2], i[3])
277             print("%s (%s) : %s (%s) %s" %
278                   (vendor.name, vendor.ID, device.name,
279                    device.ID, subdev.name))
280
281     def parse_pmd_info_string(self, mystring):
282         global raw_output
283         global pcidb
284
285         optional_pmd_info = [
286             {'id': 'params', 'tag': 'PMD PARAMETERS'},
287             {'id': 'kmod', 'tag': 'PMD KMOD DEPENDENCIES'}
288         ]
289
290         i = mystring.index("=")
291         mystring = mystring[i + 2:]
292         pmdinfo = json.loads(mystring)
293
294         if raw_output:
295             print(json.dumps(pmdinfo))
296             return
297
298         print("PMD NAME: " + pmdinfo["name"])
299         for i in optional_pmd_info:
300             try:
301                 print("%s: %s" % (i['tag'], pmdinfo[i['id']]))
302             except KeyError:
303                 continue
304
305         if (len(pmdinfo["pci_ids"]) != 0):
306             print("PMD HW SUPPORT:")
307             if pcidb is not None:
308                 self.pretty_print_pmdinfo(pmdinfo)
309             else:
310                 print("VENDOR\t DEVICE\t SUBVENDOR\t SUBDEVICE")
311                 for i in pmdinfo["pci_ids"]:
312                     print("0x%04x\t 0x%04x\t 0x%04x\t\t 0x%04x" %
313                           (i[0], i[1], i[2], i[3]))
314
315         print("")
316
317     def display_pmd_info_strings(self, section_spec):
318         """ Display a strings dump of a section. section_spec is either a
319             section number or a name.
320         """
321         section = self._section_from_spec(section_spec)
322         if section is None:
323             return
324
325         data = section.data()
326         dataptr = 0
327
328         while dataptr < len(data):
329             while (dataptr < len(data) and
330                     not (32 <= byte2int(data[dataptr]) <= 127)):
331                 dataptr += 1
332
333             if dataptr >= len(data):
334                 break
335
336             endptr = dataptr
337             while endptr < len(data) and byte2int(data[endptr]) != 0:
338                 endptr += 1
339
340             mystring = bytes2str(data[dataptr:endptr])
341             rc = mystring.find("PMD_INFO_STRING")
342             if (rc != -1):
343                 self.parse_pmd_info_string(mystring)
344
345             dataptr = endptr
346
347     def find_librte_eal(self, section):
348         for tag in section.iter_tags():
349             if tag.entry.d_tag == 'DT_NEEDED':
350                 if "librte_eal" in tag.needed:
351                     return tag.needed
352         return None
353
354     def search_for_autoload_path(self):
355         scanelf = self
356         scanfile = None
357         library = None
358
359         section = self._section_from_spec(".dynamic")
360         try:
361             eallib = self.find_librte_eal(section)
362             if eallib is not None:
363                 ldlibpath = os.environ.get('LD_LIBRARY_PATH')
364                 if ldlibpath is None:
365                     ldlibpath = ""
366                 dtr = self.get_dt_runpath(section)
367                 library = search_file(eallib,
368                                       dtr + ":" + ldlibpath +
369                                       ":/usr/lib64:/lib64:/usr/lib:/lib")
370                 if library is None:
371                     return (None, None)
372                 if raw_output is False:
373                     print("Scanning for autoload path in %s" % library)
374                 scanfile = open(library, 'rb')
375                 scanelf = ReadElf(scanfile, sys.stdout)
376         except AttributeError:
377             # Not a dynamic binary
378             pass
379         except ELFError:
380             scanfile.close()
381             return (None, None)
382
383         section = scanelf._section_from_spec(".rodata")
384         if section is None:
385             if scanfile is not None:
386                 scanfile.close()
387             return (None, None)
388
389         data = section.data()
390         dataptr = 0
391
392         while dataptr < len(data):
393             while (dataptr < len(data) and
394                     not (32 <= byte2int(data[dataptr]) <= 127)):
395                 dataptr += 1
396
397             if dataptr >= len(data):
398                 break
399
400             endptr = dataptr
401             while endptr < len(data) and byte2int(data[endptr]) != 0:
402                 endptr += 1
403
404             mystring = bytes2str(data[dataptr:endptr])
405             rc = mystring.find("DPDK_PLUGIN_PATH")
406             if (rc != -1):
407                 rc = mystring.find("=")
408                 return (mystring[rc + 1:], library)
409
410             dataptr = endptr
411         if scanfile is not None:
412             scanfile.close()
413         return (None, None)
414
415     def get_dt_runpath(self, dynsec):
416         for tag in dynsec.iter_tags():
417             if tag.entry.d_tag == 'DT_RUNPATH':
418                 return tag.runpath
419         return ""
420
421     def process_dt_needed_entries(self):
422         """ Look to see if there are any DT_NEEDED entries in the binary
423             And process those if there are
424         """
425         global raw_output
426         runpath = ""
427         ldlibpath = os.environ.get('LD_LIBRARY_PATH')
428         if ldlibpath is None:
429             ldlibpath = ""
430
431         dynsec = self._section_from_spec(".dynamic")
432         try:
433             runpath = self.get_dt_runpath(dynsec)
434         except AttributeError:
435             # dynsec is None, just return
436             return
437
438         for tag in dynsec.iter_tags():
439             if tag.entry.d_tag == 'DT_NEEDED':
440                 rc = tag.needed.find(b"librte_pmd")
441                 if (rc != -1):
442                     library = search_file(tag.needed,
443                                           runpath + ":" + ldlibpath +
444                                           ":/usr/lib64:/lib64:/usr/lib:/lib")
445                     if library is not None:
446                         if raw_output is False:
447                             print("Scanning %s for pmd information" % library)
448                         with open(library, 'rb') as file:
449                             try:
450                                 libelf = ReadElf(file, sys.stdout)
451                             except ELFError:
452                                 print("%s is no an ELF file" % library)
453                                 continue
454                             libelf.process_dt_needed_entries()
455                             libelf.display_pmd_info_strings(".rodata")
456                             file.close()
457
458
459 def scan_autoload_path(autoload_path):
460     global raw_output
461
462     if os.path.exists(autoload_path) is False:
463         return
464
465     try:
466         dirs = os.listdir(autoload_path)
467     except OSError:
468         # Couldn't read the directory, give up
469         return
470
471     for d in dirs:
472         dpath = os.path.join(autoload_path, d)
473         if os.path.isdir(dpath):
474             scan_autoload_path(dpath)
475         if os.path.isfile(dpath):
476             try:
477                 file = open(dpath, 'rb')
478                 readelf = ReadElf(file, sys.stdout)
479             except ELFError:
480                 # this is likely not an elf file, skip it
481                 continue
482             except IOError:
483                 # No permission to read the file, skip it
484                 continue
485
486             if raw_output is False:
487                 print("Hw Support for library %s" % d)
488             readelf.display_pmd_info_strings(".rodata")
489             file.close()
490
491
492 def scan_for_autoload_pmds(dpdk_path):
493     """
494     search the specified application or path for a pmd autoload path
495     then scan said path for pmds and report hw support
496     """
497     global raw_output
498
499     if (os.path.isfile(dpdk_path) is False):
500         if raw_output is False:
501             print("Must specify a file name")
502         return
503
504     file = open(dpdk_path, 'rb')
505     try:
506         readelf = ReadElf(file, sys.stdout)
507     except ElfError:
508         if raw_output is False:
509             print("Unable to parse %s" % file)
510         return
511
512     (autoload_path, scannedfile) = readelf.search_for_autoload_path()
513     if (autoload_path is None or autoload_path is ""):
514         if (raw_output is False):
515             print("No autoload path configured in %s" % dpdk_path)
516         return
517     if (raw_output is False):
518         if (scannedfile is None):
519             scannedfile = dpdk_path
520         print("Found autoload path %s in %s" % (autoload_path, scannedfile))
521
522     file.close()
523     if (raw_output is False):
524         print("Discovered Autoload HW Support:")
525     scan_autoload_path(autoload_path)
526     return
527
528
529 def main(stream=None):
530     global raw_output
531     global pcidb
532
533     pcifile_default = "./pci.ids"  # For unknown OS's assume local file
534     if platform.system() == 'Linux':
535         pcifile_default = "/usr/share/hwdata/pci.ids"
536     elif platform.system() == 'FreeBSD':
537         pcifile_default = "/usr/local/share/pciids/pci.ids"
538         if not os.path.exists(pcifile_default):
539             pcifile_default = "/usr/share/misc/pci_vendors"
540
541     optparser = OptionParser(
542         usage='usage: %prog [-hrtp] [-d <pci id file] <elf-file>',
543         description="Dump pmd hardware support info",
544         add_help_option=True)
545     optparser.add_option('-r', '--raw',
546                          action='store_true', dest='raw_output',
547                          help='Dump raw json strings')
548     optparser.add_option("-d", "--pcidb", dest="pcifile",
549                          help="specify a pci database "
550                               "to get vendor names from",
551                          default=pcifile_default, metavar="FILE")
552     optparser.add_option("-t", "--table", dest="tblout",
553                          help="output information on hw support as a "
554                               "hex table",
555                          action='store_true')
556     optparser.add_option("-p", "--plugindir", dest="pdir",
557                          help="scan dpdk for autoload plugins",
558                          action='store_true')
559
560     options, args = optparser.parse_args()
561
562     if options.raw_output:
563         raw_output = True
564
565     if options.pcifile:
566         pcidb = PCIIds(options.pcifile)
567         if pcidb is None:
568             print("Pci DB file not found")
569             exit(1)
570
571     if options.tblout:
572         options.pcifile = None
573         pcidb = None
574
575     if (len(args) == 0):
576         optparser.print_usage()
577         exit(1)
578
579     if options.pdir is True:
580         exit(scan_for_autoload_pmds(args[0]))
581
582     ldlibpath = os.environ.get('LD_LIBRARY_PATH')
583     if (ldlibpath is None):
584         ldlibpath = ""
585
586     if (os.path.exists(args[0]) is True):
587         myelffile = args[0]
588     else:
589         myelffile = search_file(
590             args[0], ldlibpath + ":/usr/lib64:/lib64:/usr/lib:/lib")
591
592     if (myelffile is None):
593         print("File not found")
594         sys.exit(1)
595
596     with open(myelffile, 'rb') as file:
597         try:
598             readelf = ReadElf(file, sys.stdout)
599             readelf.process_dt_needed_entries()
600             readelf.display_pmd_info_strings(".rodata")
601             sys.exit(0)
602
603         except ELFError as ex:
604             sys.stderr.write('ELF error: %s\n' % ex)
605             sys.exit(1)
606
607
608 # -------------------------------------------------------------------------
609 if __name__ == '__main__':
610     main()