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 11import json 12import os 13import platform 14import string 15import sys 16from elftools.common.exceptions import ELFError 17from elftools.common.py3compat import (byte2int, bytes2str, str2bytes) 18from elftools.elf.elffile import ELFFile 19from optparse import OptionParser 20 21# For running from development directory. It should take precedence over the 22# installed pyelftools. 23sys.path.insert(0, '.') 24 25raw_output = False 26pcidb = None 27 28# =========================================== 29 30 31class Vendor: 32 """ 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. 37 """ 38 39 def __init__(self, vendorStr): 40 """ 41 Class initializes with the raw line from pci.ids 42 Parsing takes place inside __init__ 43 """ 44 self.ID = vendorStr.split()[0] 45 self.name = vendorStr.replace("%s " % self.ID, "").rstrip() 46 self.devices = {} 47 48 def addDevice(self, deviceStr): 49 """ 50 Adds a device to self.devices 51 takes the raw line from pci.ids 52 """ 53 s = deviceStr.strip() 54 devID = s.split()[0] 55 if devID in self.devices: 56 pass 57 else: 58 self.devices[devID] = Device(deviceStr) 59 60 def report(self): 61 print(self.ID, self.name) 62 for id, dev in self.devices.items(): 63 dev.report() 64 65 def find_device(self, devid): 66 # convert to a hex string and remove 0x 67 devid = hex(devid)[2:] 68 try: 69 return self.devices[devid] 70 except: 71 return Device("%s Unknown Device" % devid) 72 73 74class Device: 75 76 def __init__(self, deviceStr): 77 """ 78 Class for each device. 79 Each vendor has its own devices dictionary. 80 """ 81 s = deviceStr.strip() 82 self.ID = s.split()[0] 83 self.name = s.replace("%s " % self.ID, "") 84 self.subdevices = {} 85 86 def report(self): 87 print("\t%s\t%s" % (self.ID, self.name)) 88 for subID, subdev in self.subdevices.items(): 89 subdev.report() 90 91 def addSubDevice(self, subDeviceStr): 92 """ 93 Adds a subvendor, subdevice to device. 94 Uses raw line from pci.ids 95 """ 96 s = subDeviceStr.strip() 97 spl = s.split() 98 subVendorID = spl[0] 99 subDeviceID = spl[1] 100 subDeviceName = s.split(" ")[-1] 101 devID = "%s:%s" % (subVendorID, subDeviceID) 102 self.subdevices[devID] = SubDevice( 103 subVendorID, subDeviceID, subDeviceName) 104 105 def find_subid(self, subven, subdev): 106 subven = hex(subven)[2:] 107 subdev = hex(subdev)[2:] 108 devid = "%s:%s" % (subven, subdev) 109 110 try: 111 return self.subdevices[devid] 112 except: 113 if (subven == "ffff" and subdev == "ffff"): 114 return SubDevice("ffff", "ffff", "(All Subdevices)") 115 else: 116 return SubDevice(subven, subdev, "(Unknown Subdevice)") 117 118 119class SubDevice: 120 """ 121 Class for subdevices. 122 """ 123 124 def __init__(self, vendor, device, name): 125 """ 126 Class initializes with vendorid, deviceid and name 127 """ 128 self.vendorID = vendor 129 self.deviceID = device 130 self.name = name 131 132 def report(self): 133 print("\t\t%s\t%s\t%s" % (self.vendorID, self.deviceID, self.name)) 134 135 136class PCIIds: 137 """ 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" 142 """ 143 144 def __init__(self, filename): 145 """ 146 Prepares the directories. 147 Checks local data file. 148 Tries to load from local, if not found, downloads from web 149 """ 150 self.version = "" 151 self.date = "" 152 self.vendors = {} 153 self.contents = None 154 self.readLocal(filename) 155 self.parse() 156 157 def reportVendors(self): 158 """Reports the vendors 159 """ 160 for vid, v in self.vendors.items(): 161 print(v.ID, v.name) 162 163 def report(self, vendor=None): 164 """ 165 Reports everything for all vendors or a specific vendor 166 PCIIds.report() reports everything 167 PCIIDs.report("0e11") reports only "Compaq Computer Corporation" 168 """ 169 if vendor is not None: 170 self.vendors[vendor].report() 171 else: 172 for vID, v in self.vendors.items(): 173 v.report() 174 175 def find_vendor(self, vid): 176 # convert vid to a hex string and remove the 0x 177 vid = hex(vid)[2:] 178 179 try: 180 return self.vendors[vid] 181 except: 182 return Vendor("%s Unknown Vendor" % (vid)) 183 184 def findDate(self, content): 185 for l in content: 186 if l.find("Date:") > -1: 187 return l.split()[-2].replace("-", "") 188 return None 189 190 def parse(self): 191 if len(self.contents) < 1: 192 print("data/%s-pci.ids not found" % self.date) 193 else: 194 vendorID = "" 195 deviceID = "" 196 for l in self.contents: 197 if l[0] == "#": 198 continue 199 elif len(l.strip()) == 0: 200 continue 201 else: 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) 208 else: 209 vendorID = l.split()[0] 210 self.vendors[vendorID] = Vendor(l) 211 212 def readLocal(self, filename): 213 """ 214 Reads the local file 215 """ 216 self.contents = open(filename).readlines() 217 self.date = self.findDate(self.contents) 218 219 def loadLocal(self): 220 """ 221 Loads database from local. If there is no file, 222 it creates a new one from web 223 """ 224 self.date = idsfile[0].split("/")[1].split("-")[0] 225 self.readLocal() 226 227 228# ======================================= 229 230def 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) 236 return None 237 238 239class ReadElf(object): 240 """ display_* methods are used to emit output into the output stream 241 """ 242 243 def __init__(self, file, output): 244 """ file: 245 stream object with the ELF file to read 246 247 output: 248 output stream to write to 249 """ 250 self.elffile = ELFFile(file) 251 self.output = output 252 253 # Lazily initialized if a debug dump is requested 254 self._dwarfinfo = None 255 256 self._versioninfo = None 257 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. 261 """ 262 try: 263 num = int(spec) 264 if num < self.elffile.num_sections(): 265 return self.elffile.get_section(num) 266 else: 267 return None 268 except ValueError: 269 # Not a number. Must be a name then 270 return self.elffile.get_section_by_name(str2bytes(spec)) 271 272 def pretty_print_pmdinfo(self, pmdinfo): 273 global pcidb 274 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)) 282 283 def parse_pmd_info_string(self, mystring): 284 global raw_output 285 global pcidb 286 287 optional_pmd_info = [ 288 {'id': 'params', 'tag': 'PMD PARAMETERS'}, 289 {'id': 'kmod', 'tag': 'PMD KMOD DEPENDENCIES'} 290 ] 291 292 i = mystring.index("=") 293 mystring = mystring[i + 2:] 294 pmdinfo = json.loads(mystring) 295 296 if raw_output: 297 print(json.dumps(pmdinfo)) 298 return 299 300 print("PMD NAME: " + pmdinfo["name"]) 301 for i in optional_pmd_info: 302 try: 303 print("%s: %s" % (i['tag'], pmdinfo[i['id']])) 304 except KeyError: 305 continue 306 307 if (len(pmdinfo["pci_ids"]) != 0): 308 print("PMD HW SUPPORT:") 309 if pcidb is not None: 310 self.pretty_print_pmdinfo(pmdinfo) 311 else: 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])) 316 317 print("") 318 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. 322 """ 323 section = self._section_from_spec(section_spec) 324 if section is None: 325 return 326 327 data = section.data() 328 dataptr = 0 329 330 while dataptr < len(data): 331 while (dataptr < len(data) and 332 not (32 <= byte2int(data[dataptr]) <= 127)): 333 dataptr += 1 334 335 if dataptr >= len(data): 336 break 337 338 endptr = dataptr 339 while endptr < len(data) and byte2int(data[endptr]) != 0: 340 endptr += 1 341 342 mystring = bytes2str(data[dataptr:endptr]) 343 rc = mystring.find("PMD_INFO_STRING") 344 if (rc != -1): 345 self.parse_pmd_info_string(mystring) 346 347 dataptr = endptr 348 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: 353 return tag.needed 354 return None 355 356 def search_for_autoload_path(self): 357 scanelf = self 358 scanfile = None 359 library = None 360 361 section = self._section_from_spec(".dynamic") 362 try: 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: 367 ldlibpath = "" 368 dtr = self.get_dt_runpath(section) 369 library = search_file(eallib, 370 dtr + ":" + ldlibpath + 371 ":/usr/lib64:/lib64:/usr/lib:/lib") 372 if library is None: 373 return (None, None) 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 380 pass 381 except ELFError: 382 scanfile.close() 383 return (None, None) 384 385 section = scanelf._section_from_spec(".rodata") 386 if section is None: 387 if scanfile is not None: 388 scanfile.close() 389 return (None, None) 390 391 data = section.data() 392 dataptr = 0 393 394 while dataptr < len(data): 395 while (dataptr < len(data) and 396 not (32 <= byte2int(data[dataptr]) <= 127)): 397 dataptr += 1 398 399 if dataptr >= len(data): 400 break 401 402 endptr = dataptr 403 while endptr < len(data) and byte2int(data[endptr]) != 0: 404 endptr += 1 405 406 mystring = bytes2str(data[dataptr:endptr]) 407 rc = mystring.find("DPDK_PLUGIN_PATH") 408 if (rc != -1): 409 rc = mystring.find("=") 410 return (mystring[rc + 1:], library) 411 412 dataptr = endptr 413 if scanfile is not None: 414 scanfile.close() 415 return (None, None) 416 417 def get_dt_runpath(self, dynsec): 418 for tag in dynsec.iter_tags(): 419 if tag.entry.d_tag == 'DT_RUNPATH': 420 return tag.runpath 421 return "" 422 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 426 """ 427 global raw_output 428 runpath = "" 429 ldlibpath = os.environ.get('LD_LIBRARY_PATH') 430 if ldlibpath is None: 431 ldlibpath = "" 432 433 dynsec = self._section_from_spec(".dynamic") 434 try: 435 runpath = self.get_dt_runpath(dynsec) 436 except AttributeError: 437 # dynsec is None, just return 438 return 439 440 for tag in dynsec.iter_tags(): 441 if tag.entry.d_tag == 'DT_NEEDED': 442 rc = tag.needed.find(b"librte_pmd") 443 if (rc != -1): 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: 451 try: 452 libelf = ReadElf(file, sys.stdout) 453 except ELFError: 454 print("%s is no an ELF file" % library) 455 continue 456 libelf.process_dt_needed_entries() 457 libelf.display_pmd_info_strings(".rodata") 458 file.close() 459 460 461def scan_autoload_path(autoload_path): 462 global raw_output 463 464 if os.path.exists(autoload_path) is False: 465 return 466 467 try: 468 dirs = os.listdir(autoload_path) 469 except OSError: 470 # Couldn't read the directory, give up 471 return 472 473 for d in dirs: 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): 478 try: 479 file = open(dpath, 'rb') 480 readelf = ReadElf(file, sys.stdout) 481 except ELFError: 482 # this is likely not an elf file, skip it 483 continue 484 except IOError: 485 # No permission to read the file, skip it 486 continue 487 488 if raw_output is False: 489 print("Hw Support for library %s" % d) 490 readelf.display_pmd_info_strings(".rodata") 491 file.close() 492 493 494def scan_for_autoload_pmds(dpdk_path): 495 """ 496 search the specified application or path for a pmd autoload path 497 then scan said path for pmds and report hw support 498 """ 499 global raw_output 500 501 if (os.path.isfile(dpdk_path) is False): 502 if raw_output is False: 503 print("Must specify a file name") 504 return 505 506 file = open(dpdk_path, 'rb') 507 try: 508 readelf = ReadElf(file, sys.stdout) 509 except ElfError: 510 if raw_output is False: 511 print("Unable to parse %s" % file) 512 return 513 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) 518 return 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)) 523 524 file.close() 525 if (raw_output is False): 526 print("Discovered Autoload HW Support:") 527 scan_autoload_path(autoload_path) 528 return 529 530 531def main(stream=None): 532 global raw_output 533 global pcidb 534 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" 542 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 " 556 "hex table", 557 action='store_true') 558 optparser.add_option("-p", "--plugindir", dest="pdir", 559 help="scan dpdk for autoload plugins", 560 action='store_true') 561 562 options, args = optparser.parse_args() 563 564 if options.raw_output: 565 raw_output = True 566 567 if options.pcifile: 568 pcidb = PCIIds(options.pcifile) 569 if pcidb is None: 570 print("Pci DB file not found") 571 exit(1) 572 573 if options.tblout: 574 options.pcifile = None 575 pcidb = None 576 577 if (len(args) == 0): 578 optparser.print_usage() 579 exit(1) 580 581 if options.pdir is True: 582 exit(scan_for_autoload_pmds(args[0])) 583 584 ldlibpath = os.environ.get('LD_LIBRARY_PATH') 585 if (ldlibpath is None): 586 ldlibpath = "" 587 588 if (os.path.exists(args[0]) is True): 589 myelffile = args[0] 590 else: 591 myelffile = search_file( 592 args[0], ldlibpath + ":/usr/lib64:/lib64:/usr/lib:/lib") 593 594 if (myelffile is None): 595 print("File not found") 596 sys.exit(1) 597 598 with open(myelffile, 'rb') as file: 599 try: 600 readelf = ReadElf(file, sys.stdout) 601 readelf.process_dt_needed_entries() 602 readelf.display_pmd_info_strings(".rodata") 603 sys.exit(0) 604 605 except ELFError as ex: 606 sys.stderr.write('ELF error: %s\n' % ex) 607 sys.exit(1) 608 609 610# ------------------------------------------------------------------------- 611if __name__ == '__main__': 612 main() 613