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