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