5 # Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions
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
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.
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.
39 from os.path import exists, abspath, dirname, basename
41 # The PCI base class for NETWORK devices
42 NETWORK_BASE_CLASS = "02"
43 CRYPTO_BASE_CLASS = "0b"
45 # global dict ethernet devices present. Dictionary indexed by PCI address.
46 # Each device within this is itself a dictionary of device properties
48 # list of supported DPDK drivers
49 dpdk_drivers = ["igb_uio", "vfio-pci", "uio_pci_generic"]
51 # command-line arg flags
59 '''Print usage information for the program'''
60 argv0 = basename(sys.argv[0])
65 %(argv0)s [options] DEVICE1 DEVICE2 ....
67 where DEVICE1, DEVICE2 etc, are specified via PCI "domain:bus:slot.func" syntax
68 or "bus:slot.func" syntax. For devices bound to Linux kernel drivers, they may
69 also be referred to by Linux interface name e.g. eth0, eth1, em0, em1, etc.
73 Display usage information and quit
76 Print the current status of all known network and crypto devices.
77 For each device, it displays the PCI domain, bus, slot and function,
78 along with a text description of the device. Depending upon whether the
79 device is being used by a kernel driver, the igb_uio driver, or no
80 driver, other relevant information will be displayed:
81 * the Linux interface name e.g. if=eth0
82 * the driver being used e.g. drv=igb_uio
83 * any suitable drivers not currently using that device
85 NOTE: if this flag is passed along with a bind/unbind option, the
86 status display will always occur after the other operations have taken
89 -b driver, --bind=driver:
90 Select the driver to use or \"none\" to unbind the device
93 Unbind a device (Equivalent to \"-b none\")
96 By default, network devices which are used by Linux - as indicated by
97 having routes in the routing table - cannot be modified. Using the
98 --force flag overrides this behavior, allowing active links to be
100 WARNING: This can lead to loss of network connection and should be used
106 To display current device status:
109 To bind eth1 from the current driver and move to use igb_uio
110 %(argv0)s --bind=igb_uio eth1
112 To unbind 0000:01:00.0 from using any driver
113 %(argv0)s -u 0000:01:00.0
115 To bind 0000:02:00.0 and 0000:02:00.1 to the ixgbe kernel driver
116 %(argv0)s -b ixgbe 02:00.0 02:00.1
118 """ % locals()) # replace items from local variables
121 # This is roughly compatible with check_output function in subprocess module
122 # which is only available in python 2.7.
123 def check_output(args, stderr=None):
124 '''Run a command and capture its output'''
125 return subprocess.Popen(args, stdout=subprocess.PIPE,
126 stderr=stderr).communicate()[0]
129 def find_module(mod):
130 '''find the .ko file for kernel module named mod.
131 Searches the $RTE_SDK/$RTE_TARGET directory, the kernel
132 modules directory and finally under the parent directory of
134 # check $RTE_SDK/$RTE_TARGET directory
135 if 'RTE_SDK' in os.environ and 'RTE_TARGET' in os.environ:
136 path = "%s/%s/kmod/%s.ko" % (os.environ['RTE_SDK'],
137 os.environ['RTE_TARGET'], mod)
143 depmod_out = check_output(["modinfo", "-n", mod],
144 stderr=subprocess.STDOUT).lower()
145 if "error" not in depmod_out:
146 path = depmod_out.strip()
149 except: # if modinfo can't find module, it fails, so continue
152 # check for a copy based off current path
153 tools_dir = dirname(abspath(sys.argv[0]))
154 if tools_dir.endswith("tools"):
155 base_dir = dirname(tools_dir)
156 find_out = check_output(["find", base_dir, "-name", mod + ".ko"])
157 if len(find_out) > 0: # something matched
158 path = find_out.splitlines()[0]
164 '''Checks that igb_uio is loaded'''
167 # list of supported modules
168 mods = [{"Name": driver, "Found": False} for driver in dpdk_drivers]
170 # first check if module is loaded
172 # Get list of sysfs modules (both built-in and dynamically loaded)
173 sysfs_path = '/sys/module/'
175 # Get the list of directories in sysfs_path
176 sysfs_mods = [os.path.join(sysfs_path, o) for o
177 in os.listdir(sysfs_path)
178 if os.path.isdir(os.path.join(sysfs_path, o))]
180 # Extract the last element of '/sys/module/abc' in the array
181 sysfs_mods = [a.split('/')[-1] for a in sysfs_mods]
183 # special case for vfio_pci (module is named vfio-pci,
184 # but its .ko is named vfio_pci)
185 sysfs_mods = map(lambda a:
186 a if a != 'vfio_pci' else 'vfio-pci', sysfs_mods)
189 if mod["Name"] in sysfs_mods:
194 # check if we have at least one loaded module
195 if True not in [mod["Found"] for mod in mods] and b_flag is not None:
196 if b_flag in dpdk_drivers:
197 print("Error - no supported modules(DPDK driver) are loaded")
200 print("Warning - no supported modules(DPDK driver) are loaded")
202 # change DPDK driver list to only contain drivers that are loaded
203 dpdk_drivers = [mod["Name"] for mod in mods if mod["Found"]]
206 def has_driver(dev_id):
207 '''return true if a device is assigned to a driver. False otherwise'''
208 return "Driver_str" in devices[dev_id]
211 def get_pci_device_details(dev_id):
212 '''This function gets additional details for a PCI device'''
215 extra_info = check_output(["lspci", "-vmmks", dev_id]).splitlines()
217 # parse lspci details
218 for line in extra_info:
221 name, value = line.decode().split("\t", 1)
222 name = name.strip(":") + "_str"
224 # check for a unix interface name
225 device["Interface"] = ""
226 for base, dirs, _ in os.walk("/sys/bus/pci/devices/%s/" % dev_id):
228 device["Interface"] = \
229 ",".join(os.listdir(os.path.join(base, "net")))
231 # check if a port is used for ssh connection
232 device["Ssh_if"] = False
233 device["Active"] = ""
238 def get_nic_details():
239 '''This function populates the "devices" dictionary. The keys used are
240 the pci addresses (domain:bus:slot.func). The values are themselves
241 dictionaries - one for each NIC.'''
247 # first loop through and read details for all devices
248 # request machine readable format, with numeric IDs
250 dev_lines = check_output(["lspci", "-Dvmmn"]).splitlines()
251 for dev_line in dev_lines:
252 if len(dev_line) == 0:
253 if dev["Class"][0:2] == NETWORK_BASE_CLASS:
254 # convert device and vendor ids to numbers, then add to global
255 dev["Vendor"] = int(dev["Vendor"], 16)
256 dev["Device"] = int(dev["Device"], 16)
257 # use dict to make copy of dev
258 devices[dev["Slot"]] = dict(dev)
260 name, value = dev_line.decode().split("\t", 1)
261 dev[name.rstrip(":")] = value
263 # check what is the interface if any for an ssh connection if
264 # any to this host, so we can mark it later.
266 route = check_output(["ip", "-o", "route"])
267 # filter out all lines for 169.254 routes
268 route = "\n".join(filter(lambda ln: not ln.startswith("169.254"),
269 route.decode().splitlines()))
270 rt_info = route.split()
271 for i in range(len(rt_info) - 1):
272 if rt_info[i] == "dev":
273 ssh_if.append(rt_info[i+1])
275 # based on the basic info, get extended text details
276 for d in devices.keys():
277 # get additional info and add it to existing data
278 devices[d] = devices[d].copy()
279 devices[d].update(get_pci_device_details(d).items())
282 if _if in devices[d]["Interface"].split(","):
283 devices[d]["Ssh_if"] = True
284 devices[d]["Active"] = "*Active*"
287 # add igb_uio to list of supporting modules if needed
288 if "Module_str" in devices[d]:
289 for driver in dpdk_drivers:
290 if driver not in devices[d]["Module_str"]:
291 devices[d]["Module_str"] = \
292 devices[d]["Module_str"] + ",%s" % driver
294 devices[d]["Module_str"] = ",".join(dpdk_drivers)
296 # make sure the driver and module strings do not have any duplicates
298 modules = devices[d]["Module_str"].split(",")
299 if devices[d]["Driver_str"] in modules:
300 modules.remove(devices[d]["Driver_str"])
301 devices[d]["Module_str"] = ",".join(modules)
304 def get_crypto_details():
305 '''This function populates the "devices" dictionary. The keys used are
306 the pci addresses (domain:bus:slot.func). The values are themselves
307 dictionaries - one for each NIC.'''
313 # first loop through and read details for all devices
314 # request machine readable format, with numeric IDs
316 dev_lines = check_output(["lspci", "-Dvmmn"]).splitlines()
317 for dev_line in dev_lines:
318 if len(dev_line) == 0:
319 if dev["Class"][0:2] == CRYPTO_BASE_CLASS:
320 # convert device and vendor ids to numbers, then add to global
321 dev["Vendor"] = int(dev["Vendor"], 16)
322 dev["Device"] = int(dev["Device"], 16)
323 # use dict to make copy of dev
324 devices[dev["Slot"]] = dict(dev)
326 name, value = dev_line.decode().split("\t", 1)
327 dev[name.rstrip(":")] = value
329 # based on the basic info, get extended text details
330 for d in devices.keys():
331 # get additional info and add it to existing data
332 devices[d] = devices[d].copy()
333 devices[d].update(get_pci_device_details(d).items())
335 # add igb_uio to list of supporting modules if needed
336 if "Module_str" in devices[d]:
337 for driver in dpdk_drivers:
338 if driver not in devices[d]["Module_str"]:
339 devices[d]["Module_str"] = \
340 devices[d]["Module_str"] + ",%s" % driver
342 devices[d]["Module_str"] = ",".join(dpdk_drivers)
344 # make sure the driver and module strings do not have any duplicates
346 modules = devices[d]["Module_str"].split(",")
347 if devices[d]["Driver_str"] in modules:
348 modules.remove(devices[d]["Driver_str"])
349 devices[d]["Module_str"] = ",".join(modules)
352 def dev_id_from_dev_name(dev_name):
353 '''Take a device "name" - a string passed in by user to identify a NIC
354 device, and determine the device id - i.e. the domain:bus:slot.func - for
355 it, which can then be used to index into the devices array'''
357 # check if it's already a suitable index
358 if dev_name in devices:
360 # check if it's an index just missing the domain part
361 elif "0000:" + dev_name in devices:
362 return "0000:" + dev_name
364 # check if it's an interface name, e.g. eth1
365 for d in devices.keys():
366 if dev_name in devices[d]["Interface"].split(","):
367 return devices[d]["Slot"]
368 # if nothing else matches - error
369 print("Unknown device: %s. "
370 "Please specify device in \"bus:slot.func\" format" % dev_name)
374 def unbind_one(dev_id, force):
375 '''Unbind the device identified by "dev_id" from its current driver'''
376 dev = devices[dev_id]
377 if not has_driver(dev_id):
378 print("%s %s %s is not currently managed by any driver\n" %
379 (dev["Slot"], dev["Device_str"], dev["Interface"]))
382 # prevent us disconnecting ourselves
383 if dev["Ssh_if"] and not force:
384 print("Routing table indicates that interface %s is active. "
385 "Skipping unbind" % (dev_id))
388 # write to /sys to unbind
389 filename = "/sys/bus/pci/drivers/%s/unbind" % dev["Driver_str"]
391 f = open(filename, "a")
393 print("Error: unbind failed for %s - Cannot open %s"
394 % (dev_id, filename))
400 def bind_one(dev_id, driver, force):
401 '''Bind the device given by "dev_id" to the driver "driver". If the device
402 is already bound to a different driver, it will be unbound first'''
403 dev = devices[dev_id]
404 saved_driver = None # used to rollback any unbind in case of failure
406 # prevent disconnection of our ssh session
407 if dev["Ssh_if"] and not force:
408 print("Routing table indicates that interface %s is active. "
409 "Not modifying" % (dev_id))
412 # unbind any existing drivers we don't want
413 if has_driver(dev_id):
414 if dev["Driver_str"] == driver:
415 print("%s already bound to driver %s, skipping\n"
419 saved_driver = dev["Driver_str"]
420 unbind_one(dev_id, force)
421 dev["Driver_str"] = "" # clear driver string
423 # if we are binding to one of DPDK drivers, add PCI id's to that driver
424 if driver in dpdk_drivers:
425 filename = "/sys/bus/pci/drivers/%s/new_id" % driver
427 f = open(filename, "w")
429 print("Error: bind failed for %s - Cannot open %s"
430 % (dev_id, filename))
433 f.write("%04x %04x" % (dev["Vendor"], dev["Device"]))
436 print("Error: bind failed for %s - Cannot write new PCI ID to "
437 "driver %s" % (dev_id, driver))
440 # do the bind by writing to /sys
441 filename = "/sys/bus/pci/drivers/%s/bind" % driver
443 f = open(filename, "a")
445 print("Error: bind failed for %s - Cannot open %s"
446 % (dev_id, filename))
447 if saved_driver is not None: # restore any previous driver
448 bind_one(dev_id, saved_driver, force)
454 # for some reason, closing dev_id after adding a new PCI ID to new_id
455 # results in IOError. however, if the device was successfully bound,
456 # we don't care for any errors and can safely ignore IOError
457 tmp = get_pci_device_details(dev_id)
458 if "Driver_str" in tmp and tmp["Driver_str"] == driver:
460 print("Error: bind failed for %s - Cannot bind to driver %s"
462 if saved_driver is not None: # restore any previous driver
463 bind_one(dev_id, saved_driver, force)
467 def unbind_all(dev_list, force=False):
468 """Unbind method, takes a list of device locations"""
469 dev_list = map(dev_id_from_dev_name, dev_list)
474 def bind_all(dev_list, driver, force=False):
475 """Bind method, takes a list of device locations"""
478 dev_list = map(dev_id_from_dev_name, dev_list)
481 bind_one(d, driver, force)
483 # when binding devices to a generic driver (i.e. one that doesn't have a
484 # PCI ID table), some devices that are not bound to any other driver could
485 # be bound even if no one has asked them to. hence, we check the list of
486 # drivers again, and see if some of the previously-unbound devices were
488 for d in devices.keys():
489 # skip devices that were already bound or that we know should be bound
490 if "Driver_str" in devices[d] or d in dev_list:
493 # update information about this device
494 devices[d] = dict(devices[d].items() +
495 get_pci_device_details(d).items())
497 # check if updated information indicates that the device was bound
498 if "Driver_str" in devices[d]:
502 def display_devices(title, dev_list, extra_params=None):
503 '''Displays to the user the details of a list of devices given in
504 "dev_list". The "extra_params" parameter, if given, should contain a string
505 with %()s fields in it for replacement by the named fields in each
506 device's dictionary.'''
507 strings = [] # this holds the strings to print. We sort before printing
508 print("\n%s" % title)
509 print("="*len(title))
510 if len(dev_list) == 0:
511 strings.append("<none>")
514 if extra_params is not None:
515 strings.append("%s '%s' %s" % (dev["Slot"],
519 strings.append("%s '%s'" % (dev["Slot"], dev["Device_str"]))
520 # sort before printing, so that the entries appear in PCI order
522 print("\n".join(strings)) # print one per line
526 '''Function called when the script is passed the "--status" option.
527 Displays to the user what devices are bound to the igb_uio driver, the
528 kernel driver or to no driver'''
534 # split our list of network devices into the three categories above
535 for d in devices.keys():
536 if NETWORK_BASE_CLASS in devices[d]["Class"]:
537 if not has_driver(d):
538 no_drv.append(devices[d])
540 if devices[d]["Driver_str"] in dpdk_drivers:
541 dpdk_drv.append(devices[d])
543 kernel_drv.append(devices[d])
545 # print each category separately, so we can clearly see what's used by DPDK
546 display_devices("Network devices using DPDK-compatible driver", dpdk_drv,
547 "drv=%(Driver_str)s unused=%(Module_str)s")
548 display_devices("Network devices using kernel driver", kernel_drv,
549 "if=%(Interface)s drv=%(Driver_str)s "
550 "unused=%(Module_str)s %(Active)s")
551 display_devices("Other network devices", no_drv, "unused=%(Module_str)s")
553 # split our list of crypto devices into the three categories above
558 for d in devices.keys():
559 if CRYPTO_BASE_CLASS in devices[d]["Class"]:
560 if not has_driver(d):
561 no_drv.append(devices[d])
563 if devices[d]["Driver_str"] in dpdk_drivers:
564 dpdk_drv.append(devices[d])
566 kernel_drv.append(devices[d])
568 display_devices("Crypto devices using DPDK-compatible driver", dpdk_drv,
569 "drv=%(Driver_str)s unused=%(Module_str)s")
570 display_devices("Crypto devices using kernel driver", kernel_drv,
571 "drv=%(Driver_str)s "
572 "unused=%(Module_str)s")
573 display_devices("Other crypto devices", no_drv, "unused=%(Module_str)s")
577 '''Parses the command-line arguments given by the user and takes the
578 appropriate action for each'''
583 if len(sys.argv) <= 1:
588 opts, args = getopt.getopt(sys.argv[1:], "b:us",
589 ["help", "usage", "status", "force",
591 except getopt.GetoptError as error:
593 print("Run '%s --usage' for further information" % sys.argv[0])
596 for opt, arg in opts:
597 if opt == "--help" or opt == "--usage":
600 if opt == "--status" or opt == "-s":
604 if opt == "-b" or opt == "-u" or opt == "--bind" or opt == "--unbind":
605 if b_flag is not None:
606 print("Error - Only one bind or unbind may be specified\n")
608 if opt == "-u" or opt == "--unbind":
614 def do_arg_actions():
615 '''do the actual action requested by the user'''
621 if b_flag is None and not status_flag:
622 print("Error: No action specified for devices."
623 "Please give a -b or -u option")
624 print("Run '%s --usage' for further information" % sys.argv[0])
627 if b_flag is not None and len(args) == 0:
628 print("Error: No devices specified.")
629 print("Run '%s --usage' for further information" % sys.argv[0])
632 if b_flag == "none" or b_flag == "None":
633 unbind_all(args, force_flag)
634 elif b_flag is not None:
635 bind_all(args, b_flag, force_flag)
637 if b_flag is not None:
638 get_nic_details() # refresh if we have changed anything
639 get_crypto_details() # refresh if we have changed anything
644 '''program main function'''
651 if __name__ == "__main__":