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