2 # SPDX-License-Identifier: BSD-3-Clause
3 # Copyright(c) 2016 Neil Horman <nhorman@tuxdriver.com>
5 # -------------------------------------------------------------------------
7 # Utility to dump PMD_INFO_STRING support from an object file
9 # -------------------------------------------------------------------------
10 from __future__ import print_function
16 from elftools.common.exceptions import ELFError
17 from elftools.common.py3compat import (byte2int, bytes2str, str2bytes)
18 from elftools.elf.elffile import ELFFile
19 from optparse import OptionParser
21 # For running from development directory. It should take precedence over the
22 # installed pyelftools.
23 sys.path.insert(0, '.')
28 # ===========================================
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.
39 def __init__(self, vendorStr):
41 Class initializes with the raw line from pci.ids
42 Parsing takes place inside __init__
44 self.ID = vendorStr.split()[0]
45 self.name = vendorStr.replace("%s " % self.ID, "").rstrip()
48 def addDevice(self, deviceStr):
50 Adds a device to self.devices
51 takes the raw line from pci.ids
55 if devID in self.devices:
58 self.devices[devID] = Device(deviceStr)
61 print(self.ID, self.name)
62 for id, dev in self.devices.items():
65 def find_device(self, devid):
66 # convert to a hex string and remove 0x
67 devid = hex(devid)[2:]
69 return self.devices[devid]
71 return Device("%s Unknown Device" % devid)
76 def __init__(self, deviceStr):
78 Class for each device.
79 Each vendor has its own devices dictionary.
82 self.ID = s.split()[0]
83 self.name = s.replace("%s " % self.ID, "")
87 print("\t%s\t%s" % (self.ID, self.name))
88 for subID, subdev in self.subdevices.items():
91 def addSubDevice(self, subDeviceStr):
93 Adds a subvendor, subdevice to device.
94 Uses raw line from pci.ids
96 s = subDeviceStr.strip()
100 subDeviceName = s.split(" ")[-1]
101 devID = "%s:%s" % (subVendorID, subDeviceID)
102 self.subdevices[devID] = SubDevice(
103 subVendorID, subDeviceID, subDeviceName)
105 def find_subid(self, subven, subdev):
106 subven = hex(subven)[2:]
107 subdev = hex(subdev)[2:]
108 devid = "%s:%s" % (subven, subdev)
111 return self.subdevices[devid]
113 if (subven == "ffff" and subdev == "ffff"):
114 return SubDevice("ffff", "ffff", "(All Subdevices)")
116 return SubDevice(subven, subdev, "(Unknown Subdevice)")
121 Class for subdevices.
124 def __init__(self, vendor, device, name):
126 Class initializes with vendorid, deviceid and name
128 self.vendorID = vendor
129 self.deviceID = device
133 print("\t\t%s\t%s\t%s" % (self.vendorID, self.deviceID, self.name))
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"
144 def __init__(self, filename):
146 Prepares the directories.
147 Checks local data file.
148 Tries to load from local, if not found, downloads from web
154 self.readLocal(filename)
157 def reportVendors(self):
158 """Reports the vendors
160 for vid, v in self.vendors.items():
163 def report(self, vendor=None):
165 Reports everything for all vendors or a specific vendor
166 PCIIds.report() reports everything
167 PCIIDs.report("0e11") reports only "Compaq Computer Corporation"
169 if vendor is not None:
170 self.vendors[vendor].report()
172 for vID, v in self.vendors.items():
175 def find_vendor(self, vid):
176 # convert vid to a hex string and remove the 0x
180 return self.vendors[vid]
182 return Vendor("%s Unknown Vendor" % (vid))
184 def findDate(self, content):
186 if l.find("Date:") > -1:
187 return l.split()[-2].replace("-", "")
191 if len(self.contents) < 1:
192 print("data/%s-pci.ids not found" % self.date)
196 for l in self.contents:
199 elif len(l.strip()) == 0:
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)
209 vendorID = l.split()[0]
210 self.vendors[vendorID] = Vendor(l)
212 def readLocal(self, filename):
216 self.contents = open(filename).readlines()
217 self.date = self.findDate(self.contents)
221 Loads database from local. If there is no file,
222 it creates a new one from web
224 self.date = idsfile[0].split("/")[1].split("-")[0]
228 # =======================================
230 def 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)
239 class ReadElf(object):
240 """ display_* methods are used to emit output into the output stream
243 def __init__(self, file, output):
245 stream object with the ELF file to read
248 output stream to write to
250 self.elffile = ELFFile(file)
253 # Lazily initialized if a debug dump is requested
254 self._dwarfinfo = None
256 self._versioninfo = None
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.
264 if num < self.elffile.num_sections():
265 return self.elffile.get_section(num)
269 # Not a number. Must be a name then
270 return self.elffile.get_section_by_name(str2bytes(spec))
272 def pretty_print_pmdinfo(self, pmdinfo):
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))
283 def parse_pmd_info_string(self, mystring):
287 optional_pmd_info = [
288 {'id': 'params', 'tag': 'PMD PARAMETERS'},
289 {'id': 'kmod', 'tag': 'PMD KMOD DEPENDENCIES'}
292 i = mystring.index("=")
293 mystring = mystring[i + 2:]
294 pmdinfo = json.loads(mystring)
297 print(json.dumps(pmdinfo))
300 print("PMD NAME: " + pmdinfo["name"])
301 for i in optional_pmd_info:
303 print("%s: %s" % (i['tag'], pmdinfo[i['id']]))
307 if (len(pmdinfo["pci_ids"]) != 0):
308 print("PMD HW SUPPORT:")
309 if pcidb is not None:
310 self.pretty_print_pmdinfo(pmdinfo)
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]))
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.
323 section = self._section_from_spec(section_spec)
327 data = section.data()
330 while dataptr < len(data):
331 while (dataptr < len(data) and
332 not (32 <= byte2int(data[dataptr]) <= 127)):
335 if dataptr >= len(data):
339 while endptr < len(data) and byte2int(data[endptr]) != 0:
342 mystring = bytes2str(data[dataptr:endptr])
343 rc = mystring.find("PMD_INFO_STRING")
345 self.parse_pmd_info_string(mystring)
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:
356 def search_for_autoload_path(self):
361 section = self._section_from_spec(".dynamic")
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:
368 dtr = self.get_dt_runpath(section)
369 library = search_file(eallib,
370 dtr + ":" + ldlibpath +
371 ":/usr/lib64:/lib64:/usr/lib:/lib")
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
385 section = scanelf._section_from_spec(".rodata")
387 if scanfile is not None:
391 data = section.data()
394 while dataptr < len(data):
395 while (dataptr < len(data) and
396 not (32 <= byte2int(data[dataptr]) <= 127)):
399 if dataptr >= len(data):
403 while endptr < len(data) and byte2int(data[endptr]) != 0:
406 mystring = bytes2str(data[dataptr:endptr])
407 rc = mystring.find("DPDK_PLUGIN_PATH")
409 rc = mystring.find("=")
410 return (mystring[rc + 1:], library)
413 if scanfile is not None:
417 def get_dt_runpath(self, dynsec):
418 for tag in dynsec.iter_tags():
419 if tag.entry.d_tag == 'DT_RUNPATH':
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
429 ldlibpath = os.environ.get('LD_LIBRARY_PATH')
430 if ldlibpath is None:
433 dynsec = self._section_from_spec(".dynamic")
435 runpath = self.get_dt_runpath(dynsec)
436 except AttributeError:
437 # dynsec is None, just return
440 for tag in dynsec.iter_tags():
441 if tag.entry.d_tag == 'DT_NEEDED':
442 rc = tag.needed.find(b"librte_pmd")
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:
452 libelf = ReadElf(file, sys.stdout)
454 print("%s is no an ELF file" % library)
456 libelf.process_dt_needed_entries()
457 libelf.display_pmd_info_strings(".rodata")
461 def scan_autoload_path(autoload_path):
464 if os.path.exists(autoload_path) is False:
468 dirs = os.listdir(autoload_path)
470 # Couldn't read the directory, give up
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):
479 file = open(dpath, 'rb')
480 readelf = ReadElf(file, sys.stdout)
482 # this is likely not an elf file, skip it
485 # No permission to read the file, skip it
488 if raw_output is False:
489 print("Hw Support for library %s" % d)
490 readelf.display_pmd_info_strings(".rodata")
494 def scan_for_autoload_pmds(dpdk_path):
496 search the specified application or path for a pmd autoload path
497 then scan said path for pmds and report hw support
501 if (os.path.isfile(dpdk_path) is False):
502 if raw_output is False:
503 print("Must specify a file name")
506 file = open(dpdk_path, 'rb')
508 readelf = ReadElf(file, sys.stdout)
510 if raw_output is False:
511 print("Unable to parse %s" % file)
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)
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))
525 if (raw_output is False):
526 print("Discovered Autoload HW Support:")
527 scan_autoload_path(autoload_path)
531 def main(stream=None):
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"
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 "
558 optparser.add_option("-p", "--plugindir", dest="pdir",
559 help="scan dpdk for autoload plugins",
562 options, args = optparser.parse_args()
564 if options.raw_output:
568 pcidb = PCIIds(options.pcifile)
570 print("Pci DB file not found")
574 options.pcifile = None
578 optparser.print_usage()
581 if options.pdir is True:
582 exit(scan_for_autoload_pmds(args[0]))
584 ldlibpath = os.environ.get('LD_LIBRARY_PATH')
585 if (ldlibpath is None):
588 if (os.path.exists(args[0]) is True):
591 myelffile = search_file(
592 args[0], ldlibpath + ":/usr/lib64:/lib64:/usr/lib:/lib")
594 if (myelffile is None):
595 print("File not found")
598 with open(myelffile, 'rb') as file:
600 readelf = ReadElf(file, sys.stdout)
601 readelf.process_dt_needed_entries()
602 readelf.display_pmd_info_strings(".rodata")
605 except ELFError as ex:
606 sys.stderr.write('ELF error: %s\n' % ex)
610 # -------------------------------------------------------------------------
611 if __name__ == '__main__':