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