tests: replace pycodestyle with black
[vpp.git] / extras / vpp_config / scripts / dpdk-devbind.py
1 #! /usr/bin/env python3
2 #
3 #   BSD LICENSE
4 #
5 #   Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
6 #   All rights reserved.
7 #
8 #   Redistribution and use in source and binary forms, with or without
9 #   modification, are permitted provided that the following conditions
10 #   are met:
11 #
12 #     * Redistributions of source code must retain the above copyright
13 #       notice, this list of conditions and the following disclaimer.
14 #     * Redistributions in binary form must reproduce the above copyright
15 #       notice, this list of conditions and the following disclaimer in
16 #       the documentation and/or other materials provided with the
17 #       distribution.
18 #     * Neither the name of Intel Corporation nor the names of its
19 #       contributors may be used to endorse or promote products derived
20 #       from this software without specific prior written permission.
21 #
22 #   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 #   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 #   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25 #   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26 #   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27 #   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28 #   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 #   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 #   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 #   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32 #   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 #
34
35 import sys
36 import os
37 import getopt
38 import subprocess
39 from os.path import exists, abspath, dirname, basename
40
41 # The PCI base class for NETWORK devices
42 NETWORK_BASE_CLASS = "02"
43 CRYPTO_BASE_CLASS = "0b"
44
45 # global dict ethernet devices present. Dictionary indexed by PCI address.
46 # Each device within this is itself a dictionary of device properties
47 devices = {}
48 # list of supported DPDK drivers
49 dpdk_drivers = ["igb_uio", "vfio-pci", "uio_pci_generic"]
50
51 # command-line arg flags
52 b_flag = None
53 status_flag = False
54 force_flag = False
55 args = []
56
57
58 def usage():
59     """Print usage information for the program"""
60     argv0 = basename(sys.argv[0])
61     print(
62         """
63 Usage:
64 ------
65
66      %(argv0)s [options] DEVICE1 DEVICE2 ....
67
68 where DEVICE1, DEVICE2 etc, are specified via PCI "domain:bus:slot.func" syntax
69 or "bus:slot.func" syntax. For devices bound to Linux kernel drivers, they may
70 also be referred to by Linux interface name e.g. eth0, eth1, em0, em1, etc.
71
72 Options:
73     --help, --usage:
74         Display usage information and quit
75
76     -s, --status:
77         Print the current status of all known network and crypto devices.
78         For each device, it displays the PCI domain, bus, slot and function,
79         along with a text description of the device. Depending upon whether the
80         device is being used by a kernel driver, the igb_uio driver, or no
81         driver, other relevant information will be displayed:
82         * the Linux interface name e.g. if=eth0
83         * the driver being used e.g. drv=igb_uio
84         * any suitable drivers not currently using that device
85             e.g. unused=igb_uio
86         NOTE: if this flag is passed along with a bind/unbind option, the
87         status display will always occur after the other operations have taken
88         place.
89
90     -b driver, --bind=driver:
91         Select the driver to use or \"none\" to unbind the device
92
93     -u, --unbind:
94         Unbind a device (Equivalent to \"-b none\")
95
96     --force:
97         By default, network devices which are used by Linux - as indicated by having
98         routes in the routing table - cannot be modified. Using the --force
99         flag overrides this behavior, allowing active links to be forcibly
100         unbound.
101         WARNING: This can lead to loss of network connection and should be used
102         with caution.
103
104 Examples:
105 ---------
106
107 To display current device status:
108         %(argv0)s --status
109
110 To bind eth1 from the current driver and move to use igb_uio
111         %(argv0)s --bind=igb_uio eth1
112
113 To unbind 0000:01:00.0 from using any driver
114         %(argv0)s -u 0000:01:00.0
115
116 To bind 0000:02:00.0 and 0000:02:00.1 to the ixgbe kernel driver
117         %(argv0)s -b ixgbe 02:00.0 02:00.1
118
119     """
120         % locals()
121     )  # replace items from local variables
122
123
124 # This is roughly compatible with check_output function in subprocess module
125 # which is only available in python 2.7.
126 def check_output(args, stderr=None):
127     """Run a command and capture its output"""
128     return subprocess.Popen(args, stdout=subprocess.PIPE, stderr=stderr).communicate()[
129         0
130     ]
131
132
133 def find_module(mod):
134     """find the .ko file for kernel module named mod.
135     Searches the $RTE_SDK/$RTE_TARGET directory, the kernel
136     modules directory and finally under the parent directory of
137     the script"""
138     # check $RTE_SDK/$RTE_TARGET directory
139     if "RTE_SDK" in os.environ and "RTE_TARGET" in os.environ:
140         path = "%s/%s/kmod/%s.ko" % (
141             os.environ["RTE_SDK"],
142             os.environ["RTE_TARGET"],
143             mod,
144         )
145         if exists(path):
146             return path
147
148     # check using depmod
149     try:
150         depmod_out = check_output(
151             ["modinfo", "-n", mod], stderr=subprocess.STDOUT
152         ).lower()
153         if "error" not in depmod_out:
154             path = depmod_out.strip()
155             if exists(path):
156                 return path
157     except:  # if modinfo can't find module, it fails, so continue
158         pass
159
160     # check for a copy based off current path
161     tools_dir = dirname(abspath(sys.argv[0]))
162     if tools_dir.endswith("tools"):
163         base_dir = dirname(tools_dir)
164         find_out = check_output(["find", base_dir, "-name", mod + ".ko"])
165         if len(find_out) > 0:  # something matched
166             path = find_out.splitlines()[0]
167             if exists(path):
168                 return path
169
170
171 def check_modules():
172     """Checks that igb_uio is loaded"""
173     global dpdk_drivers
174
175     # list of supported modules
176     mods = [{"Name": driver, "Found": False} for driver in dpdk_drivers]
177
178     # first check if module is loaded
179     try:
180         # Get list of sysfs modules (both built-in and dynamically loaded)
181         sysfs_path = "/sys/module/"
182
183         # Get the list of directories in sysfs_path
184         sysfs_mods = [
185             os.path.join(sysfs_path, o)
186             for o in os.listdir(sysfs_path)
187             if os.path.isdir(os.path.join(sysfs_path, o))
188         ]
189
190         # Extract the last element of '/sys/module/abc' in the array
191         sysfs_mods = [a.split("/")[-1] for a in sysfs_mods]
192
193         # special case for vfio_pci (module is named vfio-pci,
194         # but its .ko is named vfio_pci)
195         sysfs_mods = map(lambda a: a if a != "vfio_pci" else "vfio-pci", sysfs_mods)
196
197         for mod in mods:
198             if mod["Name"] in sysfs_mods:
199                 mod["Found"] = True
200     except:
201         pass
202
203     # check if we have at least one loaded module
204     if True not in [mod["Found"] for mod in mods] and b_flag is not None:
205         if b_flag in dpdk_drivers:
206             print("Error - no supported modules(DPDK driver) are loaded")
207             sys.exit(1)
208         else:
209             print("Warning - no supported modules(DPDK driver) are loaded")
210
211     # change DPDK driver list to only contain drivers that are loaded
212     dpdk_drivers = [mod["Name"] for mod in mods if mod["Found"]]
213
214
215 def has_driver(dev_id):
216     """return true if a device is assigned to a driver. False otherwise"""
217     return "Driver_str" in devices[dev_id]
218
219
220 def get_pci_device_details(dev_id):
221     """This function gets additional details for a PCI device"""
222     device = {}
223
224     extra_info = check_output(["lspci", "-vmmks", dev_id]).splitlines()
225
226     # parse lspci details
227     for line in extra_info:
228         if len(line) == 0:
229             continue
230         name, value = line.decode().split("\t", 1)
231         name = name.strip(":") + "_str"
232         device[name] = value
233     # check for a unix interface name
234     device["Interface"] = ""
235     for base, dirs, _ in os.walk("/sys/bus/pci/devices/%s/" % dev_id):
236         if "net" in dirs:
237             device["Interface"] = ",".join(os.listdir(os.path.join(base, "net")))
238             break
239     # check if a port is used for ssh connection
240     device["Ssh_if"] = False
241     device["Active"] = ""
242
243     return device
244
245
246 def get_nic_details():
247     """This function populates the "devices" dictionary. The keys used are
248     the pci addresses (domain:bus:slot.func). The values are themselves
249     dictionaries - one for each NIC."""
250     global devices
251     global dpdk_drivers
252
253     # clear any old data
254     devices = {}
255     # first loop through and read details for all devices
256     # request machine readable format, with numeric IDs
257     dev = {}
258     dev_lines = check_output(["lspci", "-Dvmmn"]).splitlines()
259     for dev_line in dev_lines:
260         if len(dev_line) == 0:
261             if dev["Class"][0:2] == NETWORK_BASE_CLASS:
262                 # convert device and vendor ids to numbers, then add to global
263                 dev["Vendor"] = int(dev["Vendor"], 16)
264                 dev["Device"] = int(dev["Device"], 16)
265                 # use dict to make copy of dev
266                 devices[dev["Slot"]] = dict(dev)
267         else:
268             name, value = dev_line.decode().split("\t", 1)
269             dev[name.rstrip(":")] = value
270
271     # check what is the interface if any for an ssh connection if
272     # any to this host, so we can mark it later.
273     ssh_if = []
274     route = check_output(["ip", "-o", "route"])
275     # filter out all lines for 169.254 routes
276     route = "\n".join(
277         filter(lambda ln: not ln.startswith("169.254"), route.decode().splitlines())
278     )
279     rt_info = route.split()
280     for i in range(len(rt_info) - 1):
281         if rt_info[i] == "dev":
282             ssh_if.append(rt_info[i + 1])
283
284     # based on the basic info, get extended text details
285     for d in devices.keys():
286         # get additional info and add it to existing data
287         devices[d] = devices[d].copy()
288         devices[d].update(get_pci_device_details(d).items())
289
290         for _if in ssh_if:
291             if _if in devices[d]["Interface"].split(","):
292                 devices[d]["Ssh_if"] = True
293                 devices[d]["Active"] = "*Active*"
294                 break
295
296         # add igb_uio to list of supporting modules if needed
297         if "Module_str" in devices[d]:
298             for driver in dpdk_drivers:
299                 if driver not in devices[d]["Module_str"]:
300                     devices[d]["Module_str"] = devices[d]["Module_str"] + ",%s" % driver
301         else:
302             devices[d]["Module_str"] = ",".join(dpdk_drivers)
303
304         # make sure the driver and module strings do not have any duplicates
305         if has_driver(d):
306             modules = devices[d]["Module_str"].split(",")
307             if devices[d]["Driver_str"] in modules:
308                 modules.remove(devices[d]["Driver_str"])
309                 devices[d]["Module_str"] = ",".join(modules)
310
311
312 def get_crypto_details():
313     """This function populates the "devices" dictionary. The keys used are
314     the pci addresses (domain:bus:slot.func). The values are themselves
315     dictionaries - one for each NIC."""
316     global devices
317     global dpdk_drivers
318
319     # clear any old data
320     # devices = {}
321     # first loop through and read details for all devices
322     # request machine readable format, with numeric IDs
323     dev = {}
324     dev_lines = check_output(["lspci", "-Dvmmn"]).splitlines()
325     for dev_line in dev_lines:
326         if len(dev_line) == 0:
327             if dev["Class"][0:2] == CRYPTO_BASE_CLASS:
328                 # convert device and vendor ids to numbers, then add to global
329                 dev["Vendor"] = int(dev["Vendor"], 16)
330                 dev["Device"] = int(dev["Device"], 16)
331                 # use dict to make copy of dev
332                 devices[dev["Slot"]] = dict(dev)
333         else:
334             name, value = dev_line.decode().split("\t", 1)
335             dev[name.rstrip(":")] = value
336
337     # based on the basic info, get extended text details
338     for d in devices.keys():
339         # get additional info and add it to existing data
340         devices[d] = devices[d].copy()
341         devices[d].update(get_pci_device_details(d).items())
342
343         # add igb_uio to list of supporting modules if needed
344         if "Module_str" in devices[d]:
345             for driver in dpdk_drivers:
346                 if driver not in devices[d]["Module_str"]:
347                     devices[d]["Module_str"] = devices[d]["Module_str"] + ",%s" % driver
348         else:
349             devices[d]["Module_str"] = ",".join(dpdk_drivers)
350
351         # make sure the driver and module strings do not have any duplicates
352         if has_driver(d):
353             modules = devices[d]["Module_str"].split(",")
354             if devices[d]["Driver_str"] in modules:
355                 modules.remove(devices[d]["Driver_str"])
356                 devices[d]["Module_str"] = ",".join(modules)
357
358
359 def dev_id_from_dev_name(dev_name):
360     """Take a device "name" - a string passed in by user to identify a NIC
361     device, and determine the device id - i.e. the domain:bus:slot.func - for
362     it, which can then be used to index into the devices array"""
363
364     # check if it's already a suitable index
365     if dev_name in devices:
366         return dev_name
367     # check if it's an index just missing the domain part
368     elif "0000:" + dev_name in devices:
369         return "0000:" + dev_name
370     else:
371         # check if it's an interface name, e.g. eth1
372         for d in devices.keys():
373             if dev_name in devices[d]["Interface"].split(","):
374                 return devices[d]["Slot"]
375     # if nothing else matches - error
376     print(
377         "Unknown device: %s. "
378         'Please specify device in "bus:slot.func" format' % dev_name
379     )
380     sys.exit(1)
381
382
383 def unbind_one(dev_id, force):
384     """Unbind the device identified by "dev_id" from its current driver"""
385     dev = devices[dev_id]
386     if not has_driver(dev_id):
387         print(
388             "%s %s %s is not currently managed by any driver\n"
389             % (dev["Slot"], dev["Device_str"], dev["Interface"])
390         )
391         return
392
393     # prevent us disconnecting ourselves
394     if dev["Ssh_if"] and not force:
395         print(
396             "Routing table indicates that interface %s is active. "
397             "Skipping unbind" % (dev_id)
398         )
399         return
400
401     # write to /sys to unbind
402     filename = "/sys/bus/pci/drivers/%s/unbind" % dev["Driver_str"]
403     try:
404         f = open(filename, "a")
405     except:
406         print("Error: unbind failed for %s - Cannot open %s" % (dev_id, filename))
407         sys.exit(1)
408     f.write(dev_id)
409     f.close()
410
411
412 def bind_one(dev_id, driver, force):
413     """Bind the device given by "dev_id" to the driver "driver". If the device
414     is already bound to a different driver, it will be unbound first"""
415     dev = devices[dev_id]
416     saved_driver = None  # used to rollback any unbind in case of failure
417
418     # prevent disconnection of our ssh session
419     if dev["Ssh_if"] and not force:
420         print(
421             "Routing table indicates that interface %s is active. "
422             "Not modifying" % (dev_id)
423         )
424         return
425
426     # unbind any existing drivers we don't want
427     if has_driver(dev_id):
428         if dev["Driver_str"] == driver:
429             print("%s already bound to driver %s, skipping\n" % (dev_id, driver))
430             return
431         else:
432             saved_driver = dev["Driver_str"]
433             unbind_one(dev_id, force)
434             dev["Driver_str"] = ""  # clear driver string
435
436     # if we are binding to one of DPDK drivers, add PCI id's to that driver
437     if driver in dpdk_drivers:
438         filename = "/sys/bus/pci/drivers/%s/new_id" % driver
439         try:
440             f = open(filename, "w")
441         except:
442             print("Error: bind failed for %s - Cannot open %s" % (dev_id, filename))
443             return
444         try:
445             f.write("%04x %04x" % (dev["Vendor"], dev["Device"]))
446             f.close()
447         except:
448             print(
449                 "Error: bind failed for %s - Cannot write new PCI ID to "
450                 "driver %s" % (dev_id, driver)
451             )
452             return
453
454     # do the bind by writing to /sys
455     filename = "/sys/bus/pci/drivers/%s/bind" % driver
456     try:
457         f = open(filename, "a")
458     except:
459         print("Error: bind failed for %s - Cannot open %s" % (dev_id, filename))
460         if saved_driver is not None:  # restore any previous driver
461             bind_one(dev_id, saved_driver, force)
462         return
463     try:
464         f.write(dev_id)
465         f.close()
466     except:
467         # for some reason, closing dev_id after adding a new PCI ID to new_id
468         # results in IOError. however, if the device was successfully bound,
469         # we don't care for any errors and can safely ignore IOError
470         tmp = get_pci_device_details(dev_id)
471         if "Driver_str" in tmp and tmp["Driver_str"] == driver:
472             return
473         print("Error: bind failed for %s - Cannot bind to driver %s" % (dev_id, driver))
474         if saved_driver is not None:  # restore any previous driver
475             bind_one(dev_id, saved_driver, force)
476         return
477
478
479 def unbind_all(dev_list, force=False):
480     """Unbind method, takes a list of device locations"""
481     dev_list = map(dev_id_from_dev_name, dev_list)
482     for d in dev_list:
483         unbind_one(d, force)
484
485
486 def bind_all(dev_list, driver, force=False):
487     """Bind method, takes a list of device locations"""
488     global devices
489
490     dev_list = map(dev_id_from_dev_name, dev_list)
491
492     for d in dev_list:
493         bind_one(d, driver, force)
494
495     # when binding devices to a generic driver (i.e. one that doesn't have a
496     # PCI ID table), some devices that are not bound to any other driver could
497     # be bound even if no one has asked them to. hence, we check the list of
498     # drivers again, and see if some of the previously-unbound devices were
499     # erroneously bound.
500     for d in devices.keys():
501         # skip devices that were already bound or that we know should be bound
502         if "Driver_str" in devices[d] or d in dev_list:
503             continue
504
505         # update information about this device
506         devices[d] = dict(devices[d].items() + get_pci_device_details(d).items())
507
508         # check if updated information indicates that the device was bound
509         if "Driver_str" in devices[d]:
510             unbind_one(d, force)
511
512
513 def display_devices(title, dev_list, extra_params=None):
514     """Displays to the user the details of a list of devices given in
515     "dev_list". The "extra_params" parameter, if given, should contain a string
516      with %()s fields in it for replacement by the named fields in each
517      device's dictionary."""
518     strings = []  # this holds the strings to print. We sort before printing
519     print("\n%s" % title)
520     print("=" * len(title))
521     if len(dev_list) == 0:
522         strings.append("<none>")
523     else:
524         for dev in dev_list:
525             if extra_params is not None:
526                 strings.append(
527                     "%s '%s' %s" % (dev["Slot"], dev["Device_str"], extra_params % dev)
528                 )
529             else:
530                 strings.append("%s '%s'" % (dev["Slot"], dev["Device_str"]))
531     # sort before printing, so that the entries appear in PCI order
532     strings.sort()
533     print("\n".join(strings))  # print one per line
534
535
536 def show_status():
537     """Function called when the script is passed the "--status" option.
538     Displays to the user what devices are bound to the igb_uio driver, the
539     kernel driver or to no driver"""
540     global dpdk_drivers
541     kernel_drv = []
542     dpdk_drv = []
543     no_drv = []
544
545     # split our list of network devices into the three categories above
546     for d in devices.keys():
547         if NETWORK_BASE_CLASS in devices[d]["Class"]:
548             if not has_driver(d):
549                 no_drv.append(devices[d])
550                 continue
551             if devices[d]["Driver_str"] in dpdk_drivers:
552                 dpdk_drv.append(devices[d])
553             else:
554                 kernel_drv.append(devices[d])
555
556     # print each category separately, so we can clearly see what's used by DPDK
557     display_devices(
558         "Network devices using DPDK-compatible driver",
559         dpdk_drv,
560         "drv=%(Driver_str)s unused=%(Module_str)s",
561     )
562     display_devices(
563         "Network devices using kernel driver",
564         kernel_drv,
565         "if=%(Interface)s drv=%(Driver_str)s " "unused=%(Module_str)s %(Active)s",
566     )
567     display_devices("Other network devices", no_drv, "unused=%(Module_str)s")
568
569     # split our list of crypto devices into the three categories above
570     kernel_drv = []
571     dpdk_drv = []
572     no_drv = []
573
574     for d in devices.keys():
575         if CRYPTO_BASE_CLASS in devices[d]["Class"]:
576             if not has_driver(d):
577                 no_drv.append(devices[d])
578                 continue
579             if devices[d]["Driver_str"] in dpdk_drivers:
580                 dpdk_drv.append(devices[d])
581             else:
582                 kernel_drv.append(devices[d])
583
584     display_devices(
585         "Crypto devices using DPDK-compatible driver",
586         dpdk_drv,
587         "drv=%(Driver_str)s unused=%(Module_str)s",
588     )
589     display_devices(
590         "Crypto devices using kernel driver",
591         kernel_drv,
592         "drv=%(Driver_str)s " "unused=%(Module_str)s",
593     )
594     display_devices("Other crypto devices", no_drv, "unused=%(Module_str)s")
595
596
597 def parse_args():
598     """Parses the command-line arguments given by the user and takes the
599     appropriate action for each"""
600     global b_flag
601     global status_flag
602     global force_flag
603     global args
604     if len(sys.argv) <= 1:
605         usage()
606         sys.exit(0)
607
608     try:
609         opts, args = getopt.getopt(
610             sys.argv[1:],
611             "b:us",
612             ["help", "usage", "status", "force", "bind=", "unbind"],
613         )
614     except getopt.GetoptError as error:
615         print(str(error))
616         print("Run '%s --usage' for further information" % sys.argv[0])
617         sys.exit(1)
618
619     for opt, arg in opts:
620         if opt == "--help" or opt == "--usage":
621             usage()
622             sys.exit(0)
623         if opt == "--status" or opt == "-s":
624             status_flag = True
625         if opt == "--force":
626             force_flag = True
627         if opt == "-b" or opt == "-u" or opt == "--bind" or opt == "--unbind":
628             if b_flag is not None:
629                 print("Error - Only one bind or unbind may be specified\n")
630                 sys.exit(1)
631             if opt == "-u" or opt == "--unbind":
632                 b_flag = "none"
633             else:
634                 b_flag = arg
635
636
637 def do_arg_actions():
638     """do the actual action requested by the user"""
639     global b_flag
640     global status_flag
641     global force_flag
642     global args
643
644     if b_flag is None and not status_flag:
645         print("Error: No action specified for devices." "Please give a -b or -u option")
646         print("Run '%s --usage' for further information" % sys.argv[0])
647         sys.exit(1)
648
649     if b_flag is not None and len(args) == 0:
650         print("Error: No devices specified.")
651         print("Run '%s --usage' for further information" % sys.argv[0])
652         sys.exit(1)
653
654     if b_flag == "none" or b_flag == "None":
655         unbind_all(args, force_flag)
656     elif b_flag is not None:
657         bind_all(args, b_flag, force_flag)
658     if status_flag:
659         if b_flag is not None:
660             get_nic_details()  # refresh if we have changed anything
661             get_crypto_details()  # refresh if we have changed anything
662         show_status()
663
664
665 def main():
666     """program main function"""
667     parse_args()
668     check_modules()
669     get_nic_details()
670     get_crypto_details()
671     do_arg_actions()
672
673
674 if __name__ == "__main__":
675     main()