dpdk/usertools/dpdk-pmdinfo.py
<<
>>
Prefs
   1#!/usr/bin/env python3
   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# -------------------------------------------------------------------------
  10import json
  11import os
  12import platform
  13import sys
  14import argparse
  15from elftools.common.exceptions import ELFError
  16from elftools.common.py3compat import byte2int
  17from elftools.elf.elffile import ELFFile
  18
  19
  20# For running from development directory. It should take precedence over the
  21# installed pyelftools.
  22sys.path.insert(0, '.')
  23
  24raw_output = False
  25pcidb = None
  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            return SubDevice(subven, subdev, "(Unknown Subdevice)")
 114
 115
 116class SubDevice:
 117    """
 118    Class for subdevices.
 119    """
 120
 121    def __init__(self, vendor, device, name):
 122        """
 123        Class initializes with vendorid, deviceid and name
 124        """
 125        self.vendorID = vendor
 126        self.deviceID = device
 127        self.name = name
 128
 129    def report(self):
 130        print("\t\t%s\t%s\t%s" % (self.vendorID, self.deviceID, self.name))
 131
 132
 133class PCIIds:
 134    """
 135    Top class for all pci.ids entries.
 136    All queries will be asked to this class.
 137    PCIIds.vendors["0e11"].devices["0046"].\
 138    subdevices["0e11:4091"].name  =  "Smart Array 6i"
 139    """
 140
 141    def __init__(self, filename):
 142        """
 143        Prepares the directories.
 144        Checks local data file.
 145        Tries to load from local, if not found, downloads from web
 146        """
 147        self.version = ""
 148        self.date = ""
 149        self.vendors = {}
 150        self.contents = None
 151        self.readLocal(filename)
 152        self.parse()
 153
 154    def reportVendors(self):
 155        """Reports the vendors
 156        """
 157        for vid, v in self.vendors.items():
 158            print(v.ID, v.name)
 159
 160    def report(self, vendor=None):
 161        """
 162        Reports everything for all vendors or a specific vendor
 163        PCIIds.report()  reports everything
 164        PCIIDs.report("0e11") reports only "Compaq Computer Corporation"
 165        """
 166        if vendor is not None:
 167            self.vendors[vendor].report()
 168        else:
 169            for vID, v in self.vendors.items():
 170                v.report()
 171
 172    def find_vendor(self, vid):
 173        # convert vid to a hex string and remove the 0x
 174        vid = hex(vid)[2:]
 175
 176        try:
 177            return self.vendors[vid]
 178        except:
 179            return Vendor("%s Unknown Vendor" % (vid))
 180
 181    def findDate(self, content):
 182        for l in content:
 183            if l.find("Date:") > -1:
 184                return l.split()[-2].replace("-", "")
 185        return None
 186
 187    def parse(self):
 188        if not self.contents:
 189            print("data/%s-pci.ids not found" % self.date)
 190        else:
 191            vendorID = ""
 192            deviceID = ""
 193            for l in self.contents:
 194                if l[0] == "#":
 195                    continue
 196                elif not l.strip():
 197                    continue
 198                else:
 199                    if l.find("\t\t") == 0:
 200                        self.vendors[vendorID].devices[
 201                            deviceID].addSubDevice(l)
 202                    elif l.find("\t") == 0:
 203                        deviceID = l.strip().split()[0]
 204                        self.vendors[vendorID].addDevice(l)
 205                    else:
 206                        vendorID = l.split()[0]
 207                        self.vendors[vendorID] = Vendor(l)
 208
 209    def readLocal(self, filename):
 210        """
 211        Reads the local file
 212        """
 213        with open(filename, 'r', encoding='utf-8') as f:
 214            self.contents = f.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 search_path.split(':'):
 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            return None
 265        except ValueError:
 266            # Not a number. Must be a name then
 267            section = self.elffile.get_section_by_name(force_unicode(spec))
 268            if section is None:
 269                # No match with a unicode name.
 270                # Some versions of pyelftools (<= 0.23) store internal strings
 271                # as bytes. Try again with the name encoded as bytes.
 272                section = self.elffile.get_section_by_name(force_bytes(spec))
 273            return section
 274
 275    def pretty_print_pmdinfo(self, pmdinfo):
 276        global pcidb
 277
 278        for i in pmdinfo["pci_ids"]:
 279            vendor = pcidb.find_vendor(i[0])
 280            device = vendor.find_device(i[1])
 281            subdev = device.find_subid(i[2], i[3])
 282            print("%s (%s) : %s (%s) %s" %
 283                  (vendor.name, vendor.ID, device.name,
 284                   device.ID, subdev.name))
 285
 286    def parse_pmd_info_string(self, mystring):
 287        global raw_output
 288        global pcidb
 289
 290        optional_pmd_info = [
 291            {'id': 'params', 'tag': 'PMD PARAMETERS'},
 292            {'id': 'kmod', 'tag': 'PMD KMOD DEPENDENCIES'}
 293        ]
 294
 295        i = mystring.index("=")
 296        mystring = mystring[i + 2:]
 297        pmdinfo = json.loads(mystring)
 298
 299        if raw_output:
 300            print(json.dumps(pmdinfo))
 301            return
 302
 303        print("PMD NAME: " + pmdinfo["name"])
 304        for i in optional_pmd_info:
 305            try:
 306                print("%s: %s" % (i['tag'], pmdinfo[i['id']]))
 307            except KeyError:
 308                continue
 309
 310        if pmdinfo["pci_ids"]:
 311            print("PMD HW SUPPORT:")
 312            if pcidb is not None:
 313                self.pretty_print_pmdinfo(pmdinfo)
 314            else:
 315                print("VENDOR\t DEVICE\t SUBVENDOR\t SUBDEVICE")
 316                for i in pmdinfo["pci_ids"]:
 317                    print("0x%04x\t 0x%04x\t 0x%04x\t\t 0x%04x" %
 318                          (i[0], i[1], i[2], i[3]))
 319
 320        print("")
 321
 322    def display_pmd_info_strings(self, section_spec):
 323        """ Display a strings dump of a section. section_spec is either a
 324            section number or a name.
 325        """
 326        section = self._section_from_spec(section_spec)
 327        if section is None:
 328            return
 329
 330        data = section.data()
 331        dataptr = 0
 332
 333        while dataptr < len(data):
 334            while (dataptr < len(data) and
 335                   not 32 <= byte2int(data[dataptr]) <= 127):
 336                dataptr += 1
 337
 338            if dataptr >= len(data):
 339                break
 340
 341            endptr = dataptr
 342            while endptr < len(data) and byte2int(data[endptr]) != 0:
 343                endptr += 1
 344
 345            # pyelftools may return byte-strings, force decode them
 346            mystring = force_unicode(data[dataptr:endptr])
 347            rc = mystring.find("PMD_INFO_STRING")
 348            if rc != -1:
 349                self.parse_pmd_info_string(mystring[rc:])
 350
 351            dataptr = endptr
 352
 353    def find_librte_eal(self, section):
 354        for tag in section.iter_tags():
 355            # pyelftools may return byte-strings, force decode them
 356            if force_unicode(tag.entry.d_tag) == 'DT_NEEDED':
 357                if "librte_eal" in force_unicode(tag.needed):
 358                    return force_unicode(tag.needed)
 359        return None
 360
 361    def search_for_autoload_path(self):
 362        scanelf = self
 363        scanfile = None
 364        library = None
 365
 366        section = self._section_from_spec(".dynamic")
 367        try:
 368            eallib = self.find_librte_eal(section)
 369            if eallib is not None:
 370                ldlibpath = os.environ.get('LD_LIBRARY_PATH')
 371                if ldlibpath is None:
 372                    ldlibpath = ""
 373                dtr = self.get_dt_runpath(section)
 374                library = search_file(eallib,
 375                                      dtr + ":" + ldlibpath +
 376                                      ":/usr/lib64:/lib64:/usr/lib:/lib")
 377                if library is None:
 378                    return (None, None)
 379                if not raw_output:
 380                    print("Scanning for autoload path in %s" % library)
 381                scanfile = open(library, 'rb')
 382                scanelf = ReadElf(scanfile, sys.stdout)
 383        except AttributeError:
 384            # Not a dynamic binary
 385            pass
 386        except ELFError:
 387            scanfile.close()
 388            return (None, None)
 389
 390        section = scanelf._section_from_spec(".rodata")
 391        if section is None:
 392            if scanfile is not None:
 393                scanfile.close()
 394            return (None, None)
 395
 396        data = section.data()
 397        dataptr = 0
 398
 399        while dataptr < len(data):
 400            while (dataptr < len(data) and
 401                   not 32 <= byte2int(data[dataptr]) <= 127):
 402                dataptr += 1
 403
 404            if dataptr >= len(data):
 405                break
 406
 407            endptr = dataptr
 408            while endptr < len(data) and byte2int(data[endptr]) != 0:
 409                endptr += 1
 410
 411            # pyelftools may return byte-strings, force decode them
 412            mystring = force_unicode(data[dataptr:endptr])
 413            rc = mystring.find("DPDK_PLUGIN_PATH")
 414            if rc != -1:
 415                rc = mystring.find("=")
 416                return (mystring[rc + 1:], library)
 417
 418            dataptr = endptr
 419        if scanfile is not None:
 420            scanfile.close()
 421        return (None, None)
 422
 423    def get_dt_runpath(self, dynsec):
 424        for tag in dynsec.iter_tags():
 425            # pyelftools may return byte-strings, force decode them
 426            if force_unicode(tag.entry.d_tag) == 'DT_RUNPATH':
 427                return force_unicode(tag.runpath)
 428        return ""
 429
 430    def process_dt_needed_entries(self):
 431        """ Look to see if there are any DT_NEEDED entries in the binary
 432            And process those if there are
 433        """
 434        runpath = ""
 435        ldlibpath = os.environ.get('LD_LIBRARY_PATH')
 436        if ldlibpath is None:
 437            ldlibpath = ""
 438
 439        dynsec = self._section_from_spec(".dynamic")
 440        try:
 441            runpath = self.get_dt_runpath(dynsec)
 442        except AttributeError:
 443            # dynsec is None, just return
 444            return
 445
 446        for tag in dynsec.iter_tags():
 447            # pyelftools may return byte-strings, force decode them
 448            if force_unicode(tag.entry.d_tag) == 'DT_NEEDED':
 449                if 'librte_' in force_unicode(tag.needed):
 450                    library = search_file(force_unicode(tag.needed),
 451                                          runpath + ":" + ldlibpath +
 452                                          ":/usr/lib64:/lib64:/usr/lib:/lib")
 453                    if library is not None:
 454                        with open(library, 'rb') as file:
 455                            try:
 456                                libelf = ReadElf(file, sys.stdout)
 457                            except ELFError:
 458                                print("%s is no an ELF file" % library)
 459                                continue
 460                            libelf.process_dt_needed_entries()
 461                            libelf.display_pmd_info_strings(".rodata")
 462                            file.close()
 463
 464
 465# compat: remove force_unicode & force_bytes when pyelftools<=0.23 support is
 466# dropped.
 467def force_unicode(s):
 468    if hasattr(s, 'decode') and callable(s.decode):
 469        s = s.decode('latin-1')  # same encoding used in pyelftools py3compat
 470    return s
 471
 472
 473def force_bytes(s):
 474    if hasattr(s, 'encode') and callable(s.encode):
 475        s = s.encode('latin-1')  # same encoding used in pyelftools py3compat
 476    return s
 477
 478
 479def scan_autoload_path(autoload_path):
 480    global raw_output
 481
 482    if not os.path.exists(autoload_path):
 483        return
 484
 485    try:
 486        dirs = os.listdir(autoload_path)
 487    except OSError:
 488        # Couldn't read the directory, give up
 489        return
 490
 491    for d in dirs:
 492        dpath = os.path.join(autoload_path, d)
 493        if os.path.isdir(dpath):
 494            scan_autoload_path(dpath)
 495        if os.path.isfile(dpath):
 496            try:
 497                file = open(dpath, 'rb')
 498                readelf = ReadElf(file, sys.stdout)
 499            except ELFError:
 500                # this is likely not an elf file, skip it
 501                continue
 502            except IOError:
 503                # No permission to read the file, skip it
 504                continue
 505
 506            if not raw_output:
 507                print("Hw Support for library %s" % d)
 508            readelf.display_pmd_info_strings(".rodata")
 509            file.close()
 510
 511
 512def scan_for_autoload_pmds(dpdk_path):
 513    """
 514    search the specified application or path for a pmd autoload path
 515    then scan said path for pmds and report hw support
 516    """
 517    global raw_output
 518
 519    if not os.path.isfile(dpdk_path):
 520        if not raw_output:
 521            print("Must specify a file name")
 522        return
 523
 524    file = open(dpdk_path, 'rb')
 525    try:
 526        readelf = ReadElf(file, sys.stdout)
 527    except ElfError:
 528        if not raw_output:
 529            print("Unable to parse %s" % file)
 530        return
 531
 532    (autoload_path, scannedfile) = readelf.search_for_autoload_path()
 533    if not autoload_path:
 534        if not raw_output:
 535            print("No autoload path configured in %s" % dpdk_path)
 536        return
 537    if not raw_output:
 538        if scannedfile is None:
 539            scannedfile = dpdk_path
 540        print("Found autoload path %s in %s" % (autoload_path, scannedfile))
 541
 542    file.close()
 543    if not raw_output:
 544        print("Discovered Autoload HW Support:")
 545    scan_autoload_path(autoload_path)
 546    return
 547
 548
 549def main(stream=None):
 550    global raw_output
 551    global pcidb
 552
 553    pcifile_default = "./pci.ids"  # For unknown OS's assume local file
 554    if platform.system() == 'Linux':
 555        # hwdata is the legacy location, misc is supported going forward
 556        pcifile_default = "/usr/share/misc/pci.ids"
 557        if not os.path.exists(pcifile_default):
 558            pcifile_default = "/usr/share/hwdata/pci.ids"
 559    elif platform.system() == 'FreeBSD':
 560        pcifile_default = "/usr/local/share/pciids/pci.ids"
 561        if not os.path.exists(pcifile_default):
 562            pcifile_default = "/usr/share/misc/pci_vendors"
 563
 564    parser = argparse.ArgumentParser(
 565        usage='usage: %(prog)s [-hrtp] [-d <pci id file>] elf_file',
 566        description="Dump pmd hardware support info")
 567    group = parser.add_mutually_exclusive_group()
 568    group.add_argument('-r', '--raw',
 569                       action='store_true', dest='raw_output',
 570                       help='dump raw json strings')
 571    group.add_argument("-t", "--table", dest="tblout",
 572                       help="output information on hw support as a hex table",
 573                       action='store_true')
 574    parser.add_argument("-d", "--pcidb", dest="pcifile",
 575                        help="specify a pci database to get vendor names from",
 576                        default=pcifile_default, metavar="FILE")
 577    parser.add_argument("-p", "--plugindir", dest="pdir",
 578                        help="scan dpdk for autoload plugins",
 579                        action='store_true')
 580    parser.add_argument("elf_file", help="driver shared object file")
 581    args = parser.parse_args()
 582
 583    if args.raw_output:
 584        raw_output = True
 585
 586    if args.tblout:
 587        args.pcifile = None
 588
 589    if args.pcifile:
 590        pcidb = PCIIds(args.pcifile)
 591        if pcidb is None:
 592            print("Pci DB file not found")
 593            exit(1)
 594
 595    if args.pdir:
 596        exit(scan_for_autoload_pmds(args[0]))
 597
 598    ldlibpath = os.environ.get('LD_LIBRARY_PATH')
 599    if ldlibpath is None:
 600        ldlibpath = ""
 601
 602    if os.path.exists(args.elf_file):
 603        myelffile = args.elf_file
 604    else:
 605        myelffile = search_file(args.elf_file,
 606                                ldlibpath + ":/usr/lib64:/lib64:/usr/lib:/lib")
 607
 608    if myelffile is None:
 609        print("File not found")
 610        sys.exit(1)
 611
 612    with open(myelffile, 'rb') as file:
 613        try:
 614            readelf = ReadElf(file, sys.stdout)
 615            readelf.process_dt_needed_entries()
 616            readelf.display_pmd_info_strings(".rodata")
 617            sys.exit(0)
 618
 619        except ELFError as ex:
 620            sys.stderr.write('ELF error: %s\n' % ex)
 621            sys.exit(1)
 622
 623
 624# -------------------------------------------------------------------------
 625if __name__ == '__main__':
 626    main()
 627