[dpdk-dev,PATCHv4,5/5] pmdinfo.py: Add tool to query binaries for hw and other support information

Message ID 1464118912-19658-6-git-send-email-nhorman@tuxdriver.com (mailing list archive)
State Superseded, archived
Delegated to: Thomas Monjalon
Headers

Commit Message

Neil Horman May 24, 2016, 7:41 p.m. UTC
  This tool searches for the primer sting PMD_DRIVER_INFO= in any ELF binary,
and, if found parses the remainder of the string as a json encoded string,
outputting the results in either a human readable or raw, script parseable
format

Note that, in the case of dynamically linked applications, pmdinfo.py will scan
for implicitly linked PMDs by searching the specified binaries .dynamic section
for DT_NEEDED entries that contain the substring librte_pmd.  The DT_RUNPATH,
LD_LIBRARY_PATH, /usr/lib and /lib are searched for these libraries, in that
order

If a file is specified with no path, it is assumed to be a PMD DSO, and the
LD_LIBRARY_PATH, /usr/lib[64]/ and /lib[64] is searched for it

Currently the tool can output data in 3 formats:

a) raw, suitable for scripting, where the raw JSON strings are dumped out
b) table format (default) where hex pci ids are dumped in a table format
c) pretty, where a user supplied pci.ids file is used to print out vendor and
device strings

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
CC: Bruce Richardson <bruce.richardson@intel.com>
CC: Thomas Monjalon <thomas.monjalon@6wind.com>
CC: Stephen Hemminger <stephen@networkplumber.org>
CC: Panu Matilainen <pmatilai@redhat.com>
---
 mk/rte.sdkinstall.mk |   2 +
 tools/pmdinfo.py     | 618 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 620 insertions(+)
 create mode 100755 tools/pmdinfo.py
  

Comments

Thomas Monjalon May 25, 2016, 5:22 p.m. UTC | #1
2016-05-24 15:41, Neil Horman:
> Note that, in the case of dynamically linked applications, pmdinfo.py will scan
> for implicitly linked PMDs by searching the specified binaries .dynamic section
> for DT_NEEDED entries that contain the substring librte_pmd.

I don't know any DPDK app dynamically linked with a PMD (with DT_NEEDED).
However it is a good idea to handle this case.
But relying on the name assumption "librte_pmd" is really weak.

> +	$(Q)$(call rte_symlink,    $(DESTDIR)$(datadir)/tools/pmdinfo.py, \
> +				   $(DESTDIR)$(bindir)/pmdinfo)

I think we must prefix the tool name with dpdk.
What about dpdk-objinfo or dpdk-pmdinfo?

> +from elftools.elf.elffile import ELFFile
> +from elftools.elf.dynamic import DynamicSection, DynamicSegment
> +from elftools.elf.enums import ENUM_D_TAG
> +from elftools.elf.segments import InterpSegment
> +from elftools.elf.sections import SymbolTableSection

Should it be possible to implement pmdinfogen with this
Python library?

I'll probably comment on the pmdinfo script details later.
Just knowing you did a tool is enough to assert that it is a good step :)
Thanks
  
Neil Horman May 25, 2016, 5:47 p.m. UTC | #2
On Wed, May 25, 2016 at 07:22:39PM +0200, Thomas Monjalon wrote:
> 2016-05-24 15:41, Neil Horman:
> > Note that, in the case of dynamically linked applications, pmdinfo.py will scan
> > for implicitly linked PMDs by searching the specified binaries .dynamic section
> > for DT_NEEDED entries that contain the substring librte_pmd.
> 
> I don't know any DPDK app dynamically linked with a PMD (with DT_NEEDED).
I know lots of them, they're all in the dpdk.  everything under app that uses a
virutal device links at link time to librte_pmd_bonding and librte_pmd_pipe (and
a few others), because they have additional apis that they need to resolve at
load time.

> However it is a good idea to handle this case.
> But relying on the name assumption "librte_pmd" is really weak.
> 
> > +	$(Q)$(call rte_symlink,    $(DESTDIR)$(datadir)/tools/pmdinfo.py, \
> > +				   $(DESTDIR)$(bindir)/pmdinfo)
> 
> I think we must prefix the tool name with dpdk.
> What about dpdk-objinfo or dpdk-pmdinfo?
> 
> > +from elftools.elf.elffile import ELFFile
> > +from elftools.elf.dynamic import DynamicSection, DynamicSegment
> > +from elftools.elf.enums import ENUM_D_TAG
> > +from elftools.elf.segments import InterpSegment
> > +from elftools.elf.sections import SymbolTableSection
> 
> Should it be possible to implement pmdinfogen with this
> Python library?
> 
Sure, but that really doesn't buy us anything, as its already implemented in C.
In fact, I would assert its harmful, because it implies that the build
environment needs to have python installed, as well as the pyelftools library,
which we don't need if we build from C.

> I'll probably comment on the pmdinfo script details later.
> Just knowing you did a tool is enough to assert that it is a good step :)
> Thanks
>
  
Thomas Monjalon May 25, 2016, 6:58 p.m. UTC | #3
2016-05-25 13:47, Neil Horman:
> On Wed, May 25, 2016 at 07:22:39PM +0200, Thomas Monjalon wrote:
> > 2016-05-24 15:41, Neil Horman:
> > > Note that, in the case of dynamically linked applications, pmdinfo.py will scan
> > > for implicitly linked PMDs by searching the specified binaries .dynamic section
> > > for DT_NEEDED entries that contain the substring librte_pmd.
> > 
> > I don't know any DPDK app dynamically linked with a PMD (with DT_NEEDED).
> I know lots of them, they're all in the dpdk.  everything under app that uses a
> virutal device links at link time to librte_pmd_bonding and librte_pmd_pipe (and
> a few others), because they have additional apis that they need to resolve at
> load time.

Oh yes! you are right.

> > However it is a good idea to handle this case.
> > But relying on the name assumption "librte_pmd" is really weak.
> > 
> > > +	$(Q)$(call rte_symlink,    $(DESTDIR)$(datadir)/tools/pmdinfo.py, \
> > > +				   $(DESTDIR)$(bindir)/pmdinfo)
> > 
> > I think we must prefix the tool name with dpdk.
> > What about dpdk-objinfo or dpdk-pmdinfo?
> > 
> > > +from elftools.elf.elffile import ELFFile
> > > +from elftools.elf.dynamic import DynamicSection, DynamicSegment
> > > +from elftools.elf.enums import ENUM_D_TAG
> > > +from elftools.elf.segments import InterpSegment
> > > +from elftools.elf.sections import SymbolTableSection
> > 
> > Should it be possible to implement pmdinfogen with this
> > Python library?
> > 
> Sure, but that really doesn't buy us anything, as its already implemented in C.
> In fact, I would assert its harmful, because it implies that the build
> environment needs to have python installed, as well as the pyelftools library,
> which we don't need if we build from C.

Right
  
Panu Matilainen May 27, 2016, 9:16 a.m. UTC | #4
On 05/25/2016 09:58 PM, Thomas Monjalon wrote:
> 2016-05-25 13:47, Neil Horman:
>> On Wed, May 25, 2016 at 07:22:39PM +0200, Thomas Monjalon wrote:
>>> 2016-05-24 15:41, Neil Horman:
>>>> Note that, in the case of dynamically linked applications, pmdinfo.py will scan
>>>> for implicitly linked PMDs by searching the specified binaries .dynamic section
>>>> for DT_NEEDED entries that contain the substring librte_pmd.
>>>
>>> I don't know any DPDK app dynamically linked with a PMD (with DT_NEEDED).
>> I know lots of them, they're all in the dpdk.  everything under app that uses a
>> virutal device links at link time to librte_pmd_bonding and librte_pmd_pipe (and
>> a few others), because they have additional apis that they need to resolve at
>> load time.
>
> Oh yes! you are right.

Also everything linking against the linker script (in its current form) 
such as OVS will pull in absolutely everything, including the pmds:

[pmatilai@sopuli ~]$ ldd /usr/sbin/ovs-vswitchd | awk '/librte_pmd/ 
{print $1}'
librte_pmd_bnx2x.so.1
librte_pmd_virtio.so.1
librte_pmd_cxgbe.so.1
librte_pmd_ring.so.2
librte_pmd_af_packet.so.1
librte_pmd_vhost.so.1
librte_pmd_ixgbe.so.1
librte_pmd_vmxnet3_uio.so.1
librte_pmd_fm10k.so.1
librte_pmd_i40e.so.1
librte_pmd_null.so.1
librte_pmd_pcap.so.1
librte_pmd_null_crypto.so.1
librte_pmd_ena.so.1
librte_pmd_e1000.so.1
librte_pmd_qede.so.1
librte_pmd_bond.so.1
librte_pmd_enic.so.1
[pmatilai@sopuli ~]$

>
>>> However it is a good idea to handle this case.

Yup. But there's also a bit of a gotcha involved with the virtual 
devices with added api. This is how eg testpmd looks when built in 
shared library config:

[pmatilai@sopuli ~]$ pmdinfo /usr/bin/testpmd
Scanning /usr/lib64/librte_pmd_bond.so.1 for pmd information
PMD NAME: bonding
PMD TYPE: PMD_VDEV
PMD PARAMETERS: slave=<ifc> primary=<ifc> mode=[0-4] xmit_policy=[l2 | 
l23 | l34] socket_id=<int> mac=<mac addr> lsc_poll_period_ms=<int> 
up_delay=<int> down_delay=<int>

[pmatilai@sopuli ~]$

Having support for bonding driver but nothing else will seem pretty damn 
confusing unless you happen to be a dpdk developer knowing this fact 
about some pmds also providing API and being linked directly etc.

Might make sense to have extra cli switch to control which kind of pmds 
are shown, and maybe default to skipping vdevs since they dont provide 
any actual hardware support anyway. Showing nothing at all in the above 
case would likely be a little less confusing. Maybe :)

Anyway, this is "refine later if needed once we gain more experience" 
material to me.

	- Panu -
  
Neil Horman May 27, 2016, 11:35 a.m. UTC | #5
On Fri, May 27, 2016 at 12:16:03PM +0300, Panu Matilainen wrote:
> On 05/25/2016 09:58 PM, Thomas Monjalon wrote:
> > 2016-05-25 13:47, Neil Horman:
> > > On Wed, May 25, 2016 at 07:22:39PM +0200, Thomas Monjalon wrote:
> > > > 2016-05-24 15:41, Neil Horman:
> > > > > Note that, in the case of dynamically linked applications, pmdinfo.py will scan
> > > > > for implicitly linked PMDs by searching the specified binaries .dynamic section
> > > > > for DT_NEEDED entries that contain the substring librte_pmd.
> > > > 
> > > > I don't know any DPDK app dynamically linked with a PMD (with DT_NEEDED).
> > > I know lots of them, they're all in the dpdk.  everything under app that uses a
> > > virutal device links at link time to librte_pmd_bonding and librte_pmd_pipe (and
> > > a few others), because they have additional apis that they need to resolve at
> > > load time.
> > 
> > Oh yes! you are right.
> 
> Also everything linking against the linker script (in its current form) such
> as OVS will pull in absolutely everything, including the pmds:
> 
> [pmatilai@sopuli ~]$ ldd /usr/sbin/ovs-vswitchd | awk '/librte_pmd/ {print
> $1}'
> librte_pmd_bnx2x.so.1
> librte_pmd_virtio.so.1
> librte_pmd_cxgbe.so.1
> librte_pmd_ring.so.2
> librte_pmd_af_packet.so.1
> librte_pmd_vhost.so.1
> librte_pmd_ixgbe.so.1
> librte_pmd_vmxnet3_uio.so.1
> librte_pmd_fm10k.so.1
> librte_pmd_i40e.so.1
> librte_pmd_null.so.1
> librte_pmd_pcap.so.1
> librte_pmd_null_crypto.so.1
> librte_pmd_ena.so.1
> librte_pmd_e1000.so.1
> librte_pmd_qede.so.1
> librte_pmd_bond.so.1
> librte_pmd_enic.so.1
> [pmatilai@sopuli ~]$
> 
> > 
> > > > However it is a good idea to handle this case.
> 
> Yup. But there's also a bit of a gotcha involved with the virtual devices
> with added api. This is how eg testpmd looks when built in shared library
> config:
> 
> [pmatilai@sopuli ~]$ pmdinfo /usr/bin/testpmd
> Scanning /usr/lib64/librte_pmd_bond.so.1 for pmd information
> PMD NAME: bonding
> PMD TYPE: PMD_VDEV
> PMD PARAMETERS: slave=<ifc> primary=<ifc> mode=[0-4] xmit_policy=[l2 | l23 |
> l34] socket_id=<int> mac=<mac addr> lsc_poll_period_ms=<int> up_delay=<int>
> down_delay=<int>
> 
> [pmatilai@sopuli ~]$
> 
> Having support for bonding driver but nothing else will seem pretty damn
> confusing unless you happen to be a dpdk developer knowing this fact about
> some pmds also providing API and being linked directly etc.
> 
Possibly, but I think its important to remember that we aren't really dealing
with end users buying this software at best buy.  The target user is more likely
a sysadmin, that I would like to think has the wherewithal to understand that
hardware support might be either (a) built-in, getting loaded at runtime by
virtue of the program itself, or a library that it is explicitly linked to, or
(b) configured-in, getting resolved an loaded at run time by virtue of a
configuration file or other site specific element.  They should be able to
figure out that the latter won't be discoverable by pmdinfo without help.

> Might make sense to have extra cli switch to control which kind of pmds are
> shown, and maybe default to skipping vdevs since they dont provide any
> actual hardware support anyway. Showing nothing at all in the above case
> would likely be a little less confusing. Maybe :)
> 
So, Thomas and I just finished arguing about the VDEV/PDEV issue.  Since he
asserted that the ennumeration for device types was being removed I removed any
notion of differentiation from the tool.  Unless there are plans to keep that
differentiation, I don't think adding a filter for device types is really
advisable

What might be useful is a filter on vendor/device id's.  That is to say only
show pmd information that matches the specified vendor/device tuple, so that a
user can search to see if the application supports the hardware they have.

> Anyway, this is "refine later if needed once we gain more experience"
> material to me.
Agreed, this isn't an addition that needs to happen now, walk before we run.

Neil

> 
> 	- Panu -
> 
>
  
Panu Matilainen May 27, 2016, 12:46 p.m. UTC | #6
On 05/27/2016 02:35 PM, Neil Horman wrote:
> On Fri, May 27, 2016 at 12:16:03PM +0300, Panu Matilainen wrote:
>
>> Yup. But there's also a bit of a gotcha involved with the virtual devices
>> with added api. This is how eg testpmd looks when built in shared library
>> config:
>>
>> [pmatilai@sopuli ~]$ pmdinfo /usr/bin/testpmd
>> Scanning /usr/lib64/librte_pmd_bond.so.1 for pmd information
>> PMD NAME: bonding
>> PMD TYPE: PMD_VDEV
>> PMD PARAMETERS: slave=<ifc> primary=<ifc> mode=[0-4] xmit_policy=[l2 | l23 |
>> l34] socket_id=<int> mac=<mac addr> lsc_poll_period_ms=<int> up_delay=<int>
>> down_delay=<int>
>>
>> [pmatilai@sopuli ~]$
>>
>> Having support for bonding driver but nothing else will seem pretty damn
>> confusing unless you happen to be a dpdk developer knowing this fact about
>> some pmds also providing API and being linked directly etc.
>>
> Possibly, but I think its important to remember that we aren't really dealing
> with end users buying this software at best buy.  The target user is more likely
> a sysadmin, that I would like to think has the wherewithal to understand that
> hardware support might be either (a) built-in, getting loaded at runtime by
> virtue of the program itself, or a library that it is explicitly linked to, or
> (b) configured-in, getting resolved an loaded at run time by virtue of a
> configuration file or other site specific element.  They should be able to
> figure out that the latter won't be discoverable by pmdinfo without help.

Sure, but ability to figure something out doesn't necessarily mean they 
should have to, much less want to, do so. Somebody setting up DPDK is 
interested in moving network packets, and the details of how a 
particular piece of software was built and linked together couldn't be 
much more further from away from that task.

I do remember the days when you had to manually load device drivers by 
kernel module names during Linux installation and while I certainly 
could do it, I dont really miss that part a single bit :)

I also realize I'm probably way ahead of things with my these usability 
ramblings when you're only trying to get the feature in, and that's 
clearly been causing some unintended friction here. Sorry about that, 
I'm really just excited about the possibilities this feature opens.

>> Might make sense to have extra cli switch to control which kind of pmds are
>> shown, and maybe default to skipping vdevs since they dont provide any
>> actual hardware support anyway. Showing nothing at all in the above case
>> would likely be a little less confusing. Maybe :)
>>
> So, Thomas and I just finished arguing about the VDEV/PDEV issue.  Since he
> asserted that the ennumeration for device types was being removed I removed any
> notion of differentiation from the tool.  Unless there are plans to keep that
> differentiation, I don't think adding a filter for device types is really
> advisable

Ah yes, sorry. I did see the discussion but the actual message 
apparently failed to register :)

>
> What might be useful is a filter on vendor/device id's.  That is to say only
> show pmd information that matches the specified vendor/device tuple, so that a
> user can search to see if the application supports the hardware they have.

Yeah, makes sense.

>> Anyway, this is "refine later if needed once we gain more experience"
>> material to me.
> Agreed, this isn't an addition that needs to happen now, walk before we run.

Nod.

	- Panu -

> Neil
>
>>
>> 	- Panu -
>>
>>
  

Patch

diff --git a/mk/rte.sdkinstall.mk b/mk/rte.sdkinstall.mk
index 68e56b6..119129a 100644
--- a/mk/rte.sdkinstall.mk
+++ b/mk/rte.sdkinstall.mk
@@ -126,6 +126,8 @@  install-runtime:
 	$(Q)$(call rte_mkdir,      $(DESTDIR)$(sbindir))
 	$(Q)$(call rte_symlink,    $(DESTDIR)$(datadir)/tools/dpdk_nic_bind.py, \
 	                           $(DESTDIR)$(sbindir)/dpdk_nic_bind)
+	$(Q)$(call rte_symlink,    $(DESTDIR)$(datadir)/tools/pmdinfo.py, \
+				   $(DESTDIR)$(bindir)/pmdinfo)
 
 install-kmod:
 ifneq ($(wildcard $O/kmod/*),)
diff --git a/tools/pmdinfo.py b/tools/pmdinfo.py
new file mode 100755
index 0000000..e2b410d
--- /dev/null
+++ b/tools/pmdinfo.py
@@ -0,0 +1,618 @@ 
+#!/usr/bin/python
+#-------------------------------------------------------------------------------
+# scripts/pmd_hw_support.py
+#
+# Utility to dump PMD_INFO_STRING support from an object file
+#
+#-------------------------------------------------------------------------------
+import os, sys
+from optparse import OptionParser
+import string
+import json
+
+# For running from development directory. It should take precedence over the
+# installed pyelftools.
+sys.path.insert(0, '.')
+
+
+from elftools import __version__
+from elftools.common.exceptions import ELFError
+from elftools.common.py3compat import (
+        ifilter, byte2int, bytes2str, itervalues, str2bytes)
+from elftools.elf.elffile import ELFFile
+from elftools.elf.dynamic import DynamicSection, DynamicSegment
+from elftools.elf.enums import ENUM_D_TAG
+from elftools.elf.segments import InterpSegment
+from elftools.elf.sections import SymbolTableSection
+from elftools.elf.gnuversions import (
+    GNUVerSymSection, GNUVerDefSection,
+    GNUVerNeedSection,
+    )
+from elftools.elf.relocation import RelocationSection
+from elftools.elf.descriptions import (
+    describe_ei_class, describe_ei_data, describe_ei_version,
+    describe_ei_osabi, describe_e_type, describe_e_machine,
+    describe_e_version_numeric, describe_p_type, describe_p_flags,
+    describe_sh_type, describe_sh_flags,
+    describe_symbol_type, describe_symbol_bind, describe_symbol_visibility,
+    describe_symbol_shndx, describe_reloc_type, describe_dyn_tag,
+    describe_ver_flags,
+    )
+from elftools.elf.constants import E_FLAGS
+from elftools.dwarf.dwarfinfo import DWARFInfo
+from elftools.dwarf.descriptions import (
+    describe_reg_name, describe_attr_value, set_global_machine_arch,
+    describe_CFI_instructions, describe_CFI_register_rule,
+    describe_CFI_CFA_rule,
+    )
+from elftools.dwarf.constants import (
+    DW_LNS_copy, DW_LNS_set_file, DW_LNE_define_file)
+from elftools.dwarf.callframe import CIE, FDE
+
+raw_output = False
+pcidb = None
+
+#===========================================
+
+class Vendor:
+    """
+    Class for vendors. This is the top level class
+    for the devices belong to a specific vendor.
+    self.devices is the device dictionary
+    subdevices are in each device.
+    """
+    def __init__(self, vendorStr):
+        """
+        Class initializes with the raw line from pci.ids
+        Parsing takes place inside __init__
+        """
+        self.ID = vendorStr.split()[0]
+        self.name = vendorStr.replace("%s " % self.ID,"").rstrip()
+        self.devices = {}
+
+    def addDevice(self, deviceStr):
+        """
+        Adds a device to self.devices
+        takes the raw line from pci.ids
+        """
+        s = deviceStr.strip()
+        devID = s.split()[0]
+        if devID in self.devices:
+            pass
+        else:
+            self.devices[devID] = Device(deviceStr)
+
+    def report(self):
+        print self.ID, self.name
+        for id, dev in self.devices.items():
+            dev.report()
+
+    def find_device(self, devid):
+        # convert to a hex string and remove 0x
+        devid = hex(devid)[2:]
+        try:
+            return self.devices[devid]
+        except:
+            return Device("%s  Unknown Device" % devid) 
+
+class Device:
+    def __init__(self, deviceStr):
+        """
+        Class for each device.
+        Each vendor has its own devices dictionary.
+        """
+        s = deviceStr.strip()
+        self.ID = s.split()[0]
+        self.name = s.replace("%s  " % self.ID,"")
+        self.subdevices = {}
+
+    def report(self):
+        print "\t%s\t%s" % (self.ID, self.name)
+        for subID, subdev in self.subdevices.items():
+            subdev.report()
+
+    def addSubDevice(self, subDeviceStr):
+        """
+        Adds a subvendor, subdevice to device.
+        Uses raw line from pci.ids
+        """
+        s = subDeviceStr.strip()
+        spl = s.split()
+        subVendorID  = spl[0]
+        subDeviceID  = spl[1]
+        subDeviceName = s.split("  ")[-1]
+        devID = "%s:%s" % (subVendorID,subDeviceID)
+        self.subdevices[devID] = SubDevice(subVendorID,subDeviceID,subDeviceName)
+
+    def find_subid(self, subven, subdev):
+        subven = hex(subven)[2:]
+        subdev = hex(subdev)[2:]
+        devid ="%s:%s" % (subven, subdev)
+
+        try:
+            return self.subdevices[devid]
+        except:
+            if (subven == "ffff" and subdev == "ffff"):
+                return SubDevice("ffff", "ffff", "(All Subdevices)");
+            else:
+                return SubDevice(subven, subdev, "(Unknown Subdevice)")
+        
+
+class SubDevice:
+    """
+    Class for subdevices.
+    """
+    def __init__(self, vendor, device, name):
+        """
+        Class initializes with vendorid, deviceid and name
+        """
+        self.vendorID = vendor
+        self.deviceID = device
+        self.name = name
+
+    def report(self):
+        print "\t\t%s\t%s\t%s" % (self.vendorID, self.deviceID,self.name)
+
+class PCIIds:
+    """
+    Top class for all pci.ids entries.
+    All queries will be asked to this class.
+    PCIIds.vendors["0e11"].devices["0046"].subdevices["0e11:4091"].name  =  "Smart Array 6i"
+    """
+    def __init__(self, filename):
+        """
+        Prepares the directories.
+        Checks local data file.
+        Tries to load from local, if not found, downloads from web
+        """
+        self.version = ""
+        self.date = ""
+        self.vendors = {}
+        self.contents = None
+        self.readLocal(filename)
+        self.parse()
+
+    def reportVendors(self):
+        """Reports the vendors
+        """
+        for vid, v in self.vendors.items():
+            print v.ID, v.name
+
+    def report(self, vendor = None):
+        """
+        Reports everything for all vendors or a specific vendor
+        PCIIds.report()  reports everything
+        PCIIDs.report("0e11") reports only "Compaq Computer Corporation"
+        """
+        if vendor != None:
+            self.vendors[vendor].report()
+        else:
+            for vID, v in self.vendors.items():
+                v.report()
+
+    def find_vendor(self, vid):
+        # convert vid to a hex string and remove the 0x
+        vid = hex(vid)[2:]
+
+        try:
+            return self.vendors[vid]
+        except: 
+            return Vendor("%s Unknown Vendor" % (vid))
+
+    def findDate(self, content):
+        for l in content:
+            if l.find("Date:") > -1:
+                return l.split()[-2].replace("-", "")
+        return None
+
+    def parse(self):
+        if len(self.contents) < 1:
+            print "data/%s-pci.ids not found" % self.date
+        else:
+            vendorID = ""
+            deviceID = ""
+            for l in self.contents:
+                if l[0] == "#":
+                    continue
+                elif len(l.strip()) == 0:
+                    continue
+                else:
+                    if l.find("\t\t") == 0:
+                        self.vendors[vendorID].devices[deviceID].addSubDevice(l)
+                    elif l.find("\t") == 0:
+                        deviceID = l.strip().split()[0]
+                        self.vendors[vendorID].addDevice(l)
+                    else:
+                        vendorID = l.split()[0]
+                        self.vendors[vendorID] = Vendor(l)
+
+    def readLocal(self, filename):
+        """
+        Reads the local file
+        """
+        self.contents = open(filename).readlines()
+        self.date = self.findDate(self.contents)
+
+    def loadLocal(self):
+        """
+        Loads database from local. If there is no file,
+        it creates a new one from web
+        """
+        self.date = idsfile[0].split("/")[1].split("-")[0]
+        self.readLocal()
+
+
+
+#=======================================
+
+def search_file(filename, search_path):
+    """ Given a search path, find file with requested name """
+    for path in string.split(search_path, ":"):
+        candidate = os.path.join(path, filename)
+        if os.path.exists(candidate): return os.path.abspath(candidate)
+    return None
+
+class ReadElf(object):
+    """ display_* methods are used to emit output into the output stream
+    """
+    def __init__(self, file, output):
+        """ file:
+                stream object with the ELF file to read
+
+            output:
+                output stream to write to
+        """
+        self.elffile = ELFFile(file)
+        self.output = output
+
+        # Lazily initialized if a debug dump is requested
+        self._dwarfinfo = None
+
+        self._versioninfo = None
+
+    def _section_from_spec(self, spec):
+        """ Retrieve a section given a "spec" (either number or name).
+            Return None if no such section exists in the file.
+        """
+        try:
+            num = int(spec)
+            if num < self.elffile.num_sections():
+                return self.elffile.get_section(num)
+            else:
+                return None
+        except ValueError:
+            # Not a number. Must be a name then
+            return self.elffile.get_section_by_name(str2bytes(spec))
+
+    def pretty_print_pmdinfo(self, pmdinfo):
+        global pcidb
+
+        for i in pmdinfo["pci_ids"]:
+            vendor = pcidb.find_vendor(i[0])
+            device = vendor.find_device(i[1])
+            subdev = device.find_subid(i[2], i[3])
+            print("%s (%s) : %s (%s) %s" % (vendor.name, vendor.ID, device.name, device.ID, subdev.name))
+   
+    def parse_pmd_info_string(self, mystring):
+        global raw_output
+        global pcidb
+
+        optional_pmd_info = [{'id': 'params', 'tag' : 'PMD PARAMETERS'}]
+
+        i = mystring.index("=");
+        mystring = mystring[i+2:]
+        pmdinfo = json.loads(mystring)
+
+        if raw_output:
+            print(pmdinfo)
+            return
+
+        print("PMD NAME: " + pmdinfo["name"])
+        print("PMD TYPE: " + pmdinfo["type"])
+        for i in optional_pmd_info:
+            try:
+                print("%s: %s" % (i['tag'], pmdinfo[i['id']]))
+            except KeyError as e:
+                continue
+
+        if (pmdinfo["type"] == "PMD_PDEV"):
+            print("PMD HW SUPPORT:")
+            if pcidb != None:
+                self.pretty_print_pmdinfo(pmdinfo)
+            else:
+                print("VENDOR\t DEVICE\t SUBVENDOR\t SUBDEVICE")
+                for i in pmdinfo["pci_ids"]:
+                    print("0x%04x\t 0x%04x\t 0x%04x\t\t 0x%04x" % (i[0], i[1], i[2], i[3]))
+
+        print("")
+
+
+    def display_pmd_info_strings(self, section_spec):
+        """ Display a strings dump of a section. section_spec is either a
+            section number or a name.
+        """
+        section = self._section_from_spec(section_spec)
+        if section is None:
+            return
+
+
+        data = section.data()
+        dataptr = 0
+
+        while dataptr < len(data):
+            while ( dataptr < len(data) and
+                    not (32 <= byte2int(data[dataptr]) <= 127)):
+                dataptr += 1
+
+            if dataptr >= len(data):
+                break
+
+            endptr = dataptr
+            while endptr < len(data) and byte2int(data[endptr]) != 0:
+                endptr += 1
+
+            mystring = bytes2str(data[dataptr:endptr])
+            rc = mystring.find("PMD_INFO_STRING")
+            if (rc != -1):
+                self.parse_pmd_info_string(mystring)
+
+            dataptr = endptr
+
+    def find_librte_eal(self, section):
+        for tag in section.iter_tags():
+            if tag.entry.d_tag == 'DT_NEEDED':
+                if "librte_eal" in tag.needed:
+                    return tag.needed
+        return None 
+
+        
+    def search_for_autoload_path(self):
+        scanelf = self
+        scanfile = None
+        library = None
+
+        section = self._section_from_spec(".dynamic")
+        try:
+            eallib = self.find_librte_eal(section)
+            if (eallib != None):
+                ldlibpath = os.environ.get('LD_LIBRARY_PATH')
+                if (ldlibpath == None):
+                    ldlibpath = ""
+                dtr = self.get_dt_runpath(section)
+                library = search_file(eallib,
+                                      dtr + ":" + ldlibpath +
+                                      ":/usr/lib64:/lib64:/usr/lib:/lib")
+                if (library == None):
+                    return (None, None)
+                if (raw_output == False):
+                    print("Scanning for autoload path in %s" % library)
+                scanfile = open(library, 'rb')
+                scanelf = ReadElf(scanfile, sys.stdout)
+        except AttributeError:
+            # Not a dynamic binary
+            pass
+        except ELFError:
+            scanfile.close()
+            return (None, None)
+
+        section = scanelf._section_from_spec(".rodata")
+        if section is None:
+            if (scanfile != None):
+                scanfile.close()
+            return (None, None)
+
+        data = section.data()
+        dataptr = 0
+
+        while dataptr < len(data):
+            while ( dataptr < len(data) and
+                    not (32 <= byte2int(data[dataptr]) <= 127)):
+                dataptr += 1
+
+            if dataptr >= len(data):
+                break
+
+            endptr = dataptr
+            while endptr < len(data) and byte2int(data[endptr]) != 0:
+                endptr += 1
+
+            mystring = bytes2str(data[dataptr:endptr])
+            rc = mystring.find("DPDK_PLUGIN_PATH")
+            if (rc != -1):
+                rc = mystring.find("=")
+                return (mystring[rc+1:], library)
+
+            dataptr = endptr
+        if (scanfile != None):
+            scanfile.close()
+        return (None, None)
+
+    def get_dt_runpath(self, dynsec):
+        for tag in dynsec.iter_tags():
+            if tag.entry.d_tag == 'DT_RUNPATH':
+                return tag.runpath
+        return ""
+
+
+    def process_dt_needed_entries(self):
+        """ Look to see if there are any DT_NEEDED entries in the binary
+            And process those if there are
+        """
+        global raw_output
+        runpath = ""
+        ldlibpath = os.environ.get('LD_LIBRARY_PATH')
+        if (ldlibpath == None):
+            ldlibpath = ""
+
+        dynsec = self._section_from_spec(".dynamic")
+        try:
+            runpath = self.get_dt_runpath(dynsec)
+        except AttributeError:
+            # dynsec is None, just return
+            return
+
+        for tag in dynsec.iter_tags():
+            if tag.entry.d_tag == 'DT_NEEDED':
+                rc = tag.needed.find("librte_pmd")
+                if (rc != -1):
+                    library = search_file(tag.needed,
+                            runpath + ":" + ldlibpath +
+                            ":/usr/lib64:/lib64:/usr/lib:/lib")
+                    if (library != None):
+                        if (raw_output == False):
+                            print("Scanning %s for pmd information" % library)
+                        with open(library, 'rb') as file:
+                            try:
+                                libelf = ReadElf(file, sys.stdout)
+                            except ELFError as e:
+                                print("%s is no an ELF file" % library)
+                                continue
+                            libelf.process_dt_needed_entries()
+                            libelf.display_pmd_info_strings(".rodata")
+                            file.close()
+
+def scan_autoload_path(autoload_path):
+    global raw_output
+
+    if os.path.exists(autoload_path) == False:
+        return
+
+    try:
+        dirs = os.listdir(autoload_path)
+    except OSError as e:
+        # Couldn't read the directory, give up
+        return
+
+    for d in dirs:
+        dpath = os.path.join(autoload_path, d)
+        if os.path.isdir(dpath):
+            scan_autoload_path(dpath)
+        if os.path.isfile(dpath):
+            try:
+                file = open(dpath, 'rb')
+                readelf = ReadElf(file, sys.stdout)
+            except ELFError as e:
+                # this is likely not an elf file, skip it
+               continue
+            except IOError as e:
+                # No permission to read the file, skip it
+                continue
+
+            if (raw_output == False):
+                print("Hw Support for library %s" % d)
+            readelf.display_pmd_info_strings(".rodata")
+            file.close()
+
+
+
+def scan_for_autoload_pmds(dpdk_path):
+    """
+    search the specified application or path for a pmd autoload path
+    then scan said path for pmds and report hw support
+    """
+    global raw_output
+
+    if (os.path.isfile(dpdk_path) == False):
+        if (raw_output == False):
+            print("Must specify a file name")
+        return
+        
+
+    file = open(dpdk_path, 'rb')
+    try:
+        readelf = ReadElf(file, sys.stdout)
+    except ElfError as e:
+        if (raw_output == False):
+            print("Unable to parse %s" % file)
+        return
+
+    (autoload_path, scannedfile) = readelf.search_for_autoload_path()
+    if (autoload_path == None or autoload_path == ""):
+        if (raw_output == False):
+            print("No autoload path configured in %s" % dpdk_path)
+        return
+    if (raw_output == False):
+        if (scannedfile == None):
+            scannedfile = dpdk_path
+        print("Found autoload path %s in %s" % (autoload_path, scannedfile))
+
+    file.close()
+    if (raw_output == False):
+        print("Discovered Autoload HW Support:") 
+    scan_autoload_path(autoload_path) 
+    return
+    
+    
+def main(stream=None):
+    global raw_output
+    global pcidb
+
+    optparser = OptionParser(
+            usage='usage: %prog [-hrtp] [-d <pci id file] <elf-file>',
+            description="Dump pmd hardware support info",
+            add_help_option=True,
+            prog='pmdinfo.py')
+    optparser.add_option('-r', '--raw',
+            action='store_true', dest='raw_output',
+            help='Dump raw json strings')
+    optparser.add_option("-d", "--pcidb", dest="pcifile",
+            help="specify a pci database to get vendor names from",
+            default="/usr/share/hwdata/pci.ids", metavar="FILE")
+    optparser.add_option("-t", "--table", dest="tblout",
+            help="output infomation on hw support as a hex table",
+            action='store_true')
+    optparser.add_option("-p", "--plugindir", dest="pdir",
+            help="scan dpdk for autoload plugins", action='store_true')
+
+    options, args = optparser.parse_args()
+
+    if options.raw_output:
+        raw_output = True
+
+    if options.pcifile:
+        pcidb = PCIIds(options.pcifile)
+        if pcidb == None:
+            print("Pci DB file not found")
+            exit(1)
+
+    if options.tblout:   
+        options.pcifile = None
+        pcidb = None
+
+    if (len(args) == 0):
+        optparser.print_usage()
+        exit(1)
+
+    if options.pdir == True:
+        exit (scan_for_autoload_pmds(args[0]))
+
+    ldlibpath = os.environ.get('LD_LIBRARY_PATH')
+    if (ldlibpath == None):
+        ldlibpath = ""
+
+    if (os.path.exists(args[0]) == True):
+        myelffile = args[0]
+    else:
+        myelffile = search_file(args[0], ldlibpath + ":/usr/lib64:/lib64:/usr/lib:/lib")
+
+    if (myelffile == None):
+        print("File not found")
+        sys.exit(1)
+
+    with open(myelffile, 'rb') as file:
+        try:
+            readelf = ReadElf(file, sys.stdout)
+            readelf.process_dt_needed_entries()
+            readelf.display_pmd_info_strings(".rodata") 
+            sys.exit(0)
+ 
+        except ELFError as ex:
+            sys.stderr.write('ELF error: %s\n' % ex)
+            sys.exit(1)
+
+
+#-------------------------------------------------------------------------------
+if __name__ == '__main__':
+    main()
+
+