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