xref: /dpdk/usertools/dpdk-pmdinfo.py (revision c6dab2a873f65c5a4ea9735aa24d9539426adba4)
1#!/usr/bin/env python
2
3# -------------------------------------------------------------------------
4#
5# Utility to dump PMD_INFO_STRING support from an object file
6#
7# -------------------------------------------------------------------------
8from __future__ import print_function
9import json
10import os
11import platform
12import string
13import sys
14from elftools.common.exceptions import ELFError
15from elftools.common.py3compat import (byte2int, bytes2str, str2bytes)
16from elftools.elf.elffile import ELFFile
17from optparse import OptionParser
18
19# For running from development directory. It should take precedence over the
20# installed pyelftools.
21sys.path.insert(0, '.')
22
23raw_output = False
24pcidb = None
25
26# ===========================================
27
28
29class 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
72class 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
117class 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
134class 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
228def 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
237class 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
459def 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
492def 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
529def 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# -------------------------------------------------------------------------
609if __name__ == '__main__':
610    main()
611