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 # -------------------------------------------------------------------------
15 from elftools.common.exceptions import ELFError
16 from elftools.common.py3compat import byte2int
17 from elftools.elf.elffile import ELFFile
20 # For running from development directory. It should take precedence over the
21 # installed pyelftools.
22 sys.path.insert(0, '.')
27 # ===========================================
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.
37 def __init__(self, vendorStr):
39 Class initializes with the raw line from pci.ids
40 Parsing takes place inside __init__
42 self.ID = vendorStr.split()[0]
43 self.name = vendorStr.replace("%s " % self.ID, "").rstrip()
46 def addDevice(self, deviceStr):
48 Adds a device to self.devices
49 takes the raw line from pci.ids
53 if devID in self.devices:
56 self.devices[devID] = Device(deviceStr)
59 print(self.ID, self.name)
60 for id, dev in self.devices.items():
63 def find_device(self, devid):
64 # convert to a hex string and remove 0x
65 devid = hex(devid)[2:]
67 return self.devices[devid]
69 return Device("%s Unknown Device" % devid)
74 def __init__(self, deviceStr):
76 Class for each device.
77 Each vendor has its own devices dictionary.
80 self.ID = s.split()[0]
81 self.name = s.replace("%s " % self.ID, "")
85 print("\t%s\t%s" % (self.ID, self.name))
86 for subID, subdev in self.subdevices.items():
89 def addSubDevice(self, subDeviceStr):
91 Adds a subvendor, subdevice to device.
92 Uses raw line from pci.ids
94 s = subDeviceStr.strip()
98 subDeviceName = s.split(" ")[-1]
99 devID = "%s:%s" % (subVendorID, subDeviceID)
100 self.subdevices[devID] = SubDevice(
101 subVendorID, subDeviceID, subDeviceName)
103 def find_subid(self, subven, subdev):
104 subven = hex(subven)[2:]
105 subdev = hex(subdev)[2:]
106 devid = "%s:%s" % (subven, subdev)
109 return self.subdevices[devid]
111 if (subven == "ffff" and subdev == "ffff"):
112 return SubDevice("ffff", "ffff", "(All Subdevices)")
113 return SubDevice(subven, subdev, "(Unknown Subdevice)")
118 Class for subdevices.
121 def __init__(self, vendor, device, name):
123 Class initializes with vendorid, deviceid and name
125 self.vendorID = vendor
126 self.deviceID = device
130 print("\t\t%s\t%s\t%s" % (self.vendorID, self.deviceID, self.name))
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"
141 def __init__(self, filename):
143 Prepares the directories.
144 Checks local data file.
145 Tries to load from local, if not found, downloads from web
151 self.readLocal(filename)
154 def reportVendors(self):
155 """Reports the vendors
157 for vid, v in self.vendors.items():
160 def report(self, vendor=None):
162 Reports everything for all vendors or a specific vendor
163 PCIIds.report() reports everything
164 PCIIDs.report("0e11") reports only "Compaq Computer Corporation"
166 if vendor is not None:
167 self.vendors[vendor].report()
169 for vID, v in self.vendors.items():
172 def find_vendor(self, vid):
173 # convert vid to a hex string and remove the 0x
177 return self.vendors[vid]
179 return Vendor("%s Unknown Vendor" % (vid))
181 def findDate(self, content):
183 if l.find("Date:") > -1:
184 return l.split()[-2].replace("-", "")
188 if not self.contents:
189 print("data/%s-pci.ids not found" % self.date)
193 for l in self.contents:
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)
206 vendorID = l.split()[0]
207 self.vendors[vendorID] = Vendor(l)
209 def readLocal(self, filename):
213 with open(filename, 'r', encoding='utf-8') as f:
214 self.contents = f.readlines()
215 self.date = self.findDate(self.contents)
219 Loads database from local. If there is no file,
220 it creates a new one from web
222 self.date = idsfile[0].split("/")[1].split("-")[0]
226 # =======================================
228 def 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)
237 class ReadElf(object):
238 """ display_* methods are used to emit output into the output stream
241 def __init__(self, file, output):
243 stream object with the ELF file to read
246 output stream to write to
248 self.elffile = ELFFile(file)
251 # Lazily initialized if a debug dump is requested
252 self._dwarfinfo = None
254 self._versioninfo = None
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.
262 if num < self.elffile.num_sections():
263 return self.elffile.get_section(num)
266 # Not a number. Must be a name then
267 section = self.elffile.get_section_by_name(force_unicode(spec))
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))
275 def pretty_print_pmdinfo(self, pmdinfo):
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))
286 def parse_pmd_info_string(self, mystring):
290 optional_pmd_info = [
291 {'id': 'params', 'tag': 'PMD PARAMETERS'},
292 {'id': 'kmod', 'tag': 'PMD KMOD DEPENDENCIES'}
295 i = mystring.index("=")
296 mystring = mystring[i + 2:]
297 pmdinfo = json.loads(mystring)
300 print(json.dumps(pmdinfo))
303 print("PMD NAME: " + pmdinfo["name"])
304 for i in optional_pmd_info:
306 print("%s: %s" % (i['tag'], pmdinfo[i['id']]))
310 if pmdinfo["pci_ids"]:
311 print("PMD HW SUPPORT:")
312 if pcidb is not None:
313 self.pretty_print_pmdinfo(pmdinfo)
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]))
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.
326 section = self._section_from_spec(section_spec)
330 data = section.data()
333 while dataptr < len(data):
334 while (dataptr < len(data) and
335 not 32 <= byte2int(data[dataptr]) <= 127):
338 if dataptr >= len(data):
342 while endptr < len(data) and byte2int(data[endptr]) != 0:
345 # pyelftools may return byte-strings, force decode them
346 mystring = force_unicode(data[dataptr:endptr])
347 rc = mystring.find("PMD_INFO_STRING")
349 self.parse_pmd_info_string(mystring[rc:])
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)
361 def search_for_autoload_path(self):
366 section = self._section_from_spec(".dynamic")
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:
373 dtr = self.get_dt_runpath(section)
374 library = search_file(eallib,
375 dtr + ":" + ldlibpath +
376 ":/usr/lib64:/lib64:/usr/lib:/lib")
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
390 section = scanelf._section_from_spec(".rodata")
392 if scanfile is not None:
396 data = section.data()
399 while dataptr < len(data):
400 while (dataptr < len(data) and
401 not 32 <= byte2int(data[dataptr]) <= 127):
404 if dataptr >= len(data):
408 while endptr < len(data) and byte2int(data[endptr]) != 0:
411 # pyelftools may return byte-strings, force decode them
412 mystring = force_unicode(data[dataptr:endptr])
413 rc = mystring.find("DPDK_PLUGIN_PATH")
415 rc = mystring.find("=")
416 return (mystring[rc + 1:], library)
419 if scanfile is not None:
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)
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
435 ldlibpath = os.environ.get('LD_LIBRARY_PATH')
436 if ldlibpath is None:
439 dynsec = self._section_from_spec(".dynamic")
441 runpath = self.get_dt_runpath(dynsec)
442 except AttributeError:
443 # dynsec is None, just return
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:
456 libelf = ReadElf(file, sys.stdout)
458 print("%s is no an ELF file" % library)
460 libelf.process_dt_needed_entries()
461 libelf.display_pmd_info_strings(".rodata")
465 # compat: remove force_unicode & force_bytes when pyelftools<=0.23 support is
467 def force_unicode(s):
468 if hasattr(s, 'decode') and callable(s.decode):
469 s = s.decode('latin-1') # same encoding used in pyelftools py3compat
474 if hasattr(s, 'encode') and callable(s.encode):
475 s = s.encode('latin-1') # same encoding used in pyelftools py3compat
479 def scan_autoload_path(autoload_path):
482 if not os.path.exists(autoload_path):
486 dirs = os.listdir(autoload_path)
488 # Couldn't read the directory, give up
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):
497 file = open(dpath, 'rb')
498 readelf = ReadElf(file, sys.stdout)
500 # this is likely not an elf file, skip it
503 # No permission to read the file, skip it
507 print("Hw Support for library %s" % d)
508 readelf.display_pmd_info_strings(".rodata")
512 def scan_for_autoload_pmds(dpdk_path):
514 search the specified application or path for a pmd autoload path
515 then scan said path for pmds and report hw support
519 if not os.path.isfile(dpdk_path):
521 print("Must specify a file name")
524 file = open(dpdk_path, 'rb')
526 readelf = ReadElf(file, sys.stdout)
529 print("Unable to parse %s" % file)
532 (autoload_path, scannedfile) = readelf.search_for_autoload_path()
533 if not autoload_path:
535 print("No autoload path configured in %s" % dpdk_path)
538 if scannedfile is None:
539 scannedfile = dpdk_path
540 print("Found autoload path %s in %s" % (autoload_path, scannedfile))
544 print("Discovered Autoload HW Support:")
545 scan_autoload_path(autoload_path)
549 def main(stream=None):
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"
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",
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",
580 parser.add_argument("elf_file", help="driver shared object file")
581 args = parser.parse_args()
590 pcidb = PCIIds(args.pcifile)
592 print("Pci DB file not found")
596 exit(scan_for_autoload_pmds(args[0]))
598 ldlibpath = os.environ.get('LD_LIBRARY_PATH')
599 if ldlibpath is None:
602 if os.path.exists(args.elf_file):
603 myelffile = args.elf_file
605 myelffile = search_file(args.elf_file,
606 ldlibpath + ":/usr/lib64:/lib64:/usr/lib:/lib")
608 if myelffile is None:
609 print("File not found")
612 with open(myelffile, 'rb') as file:
614 readelf = ReadElf(file, sys.stdout)
615 readelf.process_dt_needed_entries()
616 readelf.display_pmd_info_strings(".rodata")
619 except ELFError as ex:
620 sys.stderr.write('ELF error: %s\n' % ex)
624 # -------------------------------------------------------------------------
625 if __name__ == '__main__':