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