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