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 '''This function clears any old data'''
241 def get_device_details(devices_type):
242 '''This function populates the "devices" dictionary. The keys used are
243 the pci addresses (domain:bus:slot.func). The values are themselves
244 dictionaries - one for each NIC.'''
248 # first loop through and read details for all devices
249 # request machine readable format, with numeric IDs
251 dev_lines = check_output(["lspci", "-Dvmmn"]).splitlines()
252 for dev_line in dev_lines:
253 if len(dev_line) == 0:
254 if dev["Class"][0:2] == devices_type:
255 # convert device and vendor ids to numbers, then add to global
256 dev["Vendor"] = int(dev["Vendor"], 16)
257 dev["Device"] = int(dev["Device"], 16)
258 # use dict to make copy of dev
259 devices[dev["Slot"]] = dict(dev)
261 name, value = dev_line.decode().split("\t", 1)
262 dev[name.rstrip(":")] = value
264 if devices_type == NETWORK_BASE_CLASS:
265 # check what is the interface if any for an ssh connection if
266 # any to this host, so we can mark it later.
268 route = check_output(["ip", "-o", "route"])
269 # filter out all lines for 169.254 routes
270 route = "\n".join(filter(lambda ln: not ln.startswith("169.254"),
271 route.decode().splitlines()))
272 rt_info = route.split()
273 for i in range(len(rt_info) - 1):
274 if rt_info[i] == "dev":
275 ssh_if.append(rt_info[i+1])
277 # based on the basic info, get extended text details
278 for d in devices.keys():
279 if devices[d]["Class"][0:2] != devices_type:
282 # get additional info and add it to existing data
283 devices[d] = devices[d].copy()
284 devices[d].update(get_pci_device_details(d).items())
286 if devices_type == NETWORK_BASE_CLASS:
288 if _if in devices[d]["Interface"].split(","):
289 devices[d]["Ssh_if"] = True
290 devices[d]["Active"] = "*Active*"
293 # add igb_uio to list of supporting modules if needed
294 if "Module_str" in devices[d]:
295 for driver in dpdk_drivers:
296 if driver not in devices[d]["Module_str"]:
297 devices[d]["Module_str"] = \
298 devices[d]["Module_str"] + ",%s" % driver
300 devices[d]["Module_str"] = ",".join(dpdk_drivers)
302 # make sure the driver and module strings do not have any duplicates
304 modules = devices[d]["Module_str"].split(",")
305 if devices[d]["Driver_str"] in modules:
306 modules.remove(devices[d]["Driver_str"])
307 devices[d]["Module_str"] = ",".join(modules)
310 def dev_id_from_dev_name(dev_name):
311 '''Take a device "name" - a string passed in by user to identify a NIC
312 device, and determine the device id - i.e. the domain:bus:slot.func - for
313 it, which can then be used to index into the devices array'''
315 # check if it's already a suitable index
316 if dev_name in devices:
318 # check if it's an index just missing the domain part
319 elif "0000:" + dev_name in devices:
320 return "0000:" + dev_name
322 # check if it's an interface name, e.g. eth1
323 for d in devices.keys():
324 if dev_name in devices[d]["Interface"].split(","):
325 return devices[d]["Slot"]
326 # if nothing else matches - error
327 print("Unknown device: %s. "
328 "Please specify device in \"bus:slot.func\" format" % dev_name)
332 def unbind_one(dev_id, force):
333 '''Unbind the device identified by "dev_id" from its current driver'''
334 dev = devices[dev_id]
335 if not has_driver(dev_id):
336 print("%s %s %s is not currently managed by any driver\n" %
337 (dev["Slot"], dev["Device_str"], dev["Interface"]))
340 # prevent us disconnecting ourselves
341 if dev["Ssh_if"] and not force:
342 print("Routing table indicates that interface %s is active. "
343 "Skipping unbind" % (dev_id))
346 # write to /sys to unbind
347 filename = "/sys/bus/pci/drivers/%s/unbind" % dev["Driver_str"]
349 f = open(filename, "a")
351 print("Error: unbind failed for %s - Cannot open %s"
352 % (dev_id, filename))
358 def bind_one(dev_id, driver, force):
359 '''Bind the device given by "dev_id" to the driver "driver". If the device
360 is already bound to a different driver, it will be unbound first'''
361 dev = devices[dev_id]
362 saved_driver = None # used to rollback any unbind in case of failure
364 # prevent disconnection of our ssh session
365 if dev["Ssh_if"] and not force:
366 print("Routing table indicates that interface %s is active. "
367 "Not modifying" % (dev_id))
370 # unbind any existing drivers we don't want
371 if has_driver(dev_id):
372 if dev["Driver_str"] == driver:
373 print("%s already bound to driver %s, skipping\n"
377 saved_driver = dev["Driver_str"]
378 unbind_one(dev_id, force)
379 dev["Driver_str"] = "" # clear driver string
381 # if we are binding to one of DPDK drivers, add PCI id's to that driver
382 if driver in dpdk_drivers:
383 filename = "/sys/bus/pci/drivers/%s/new_id" % driver
385 f = open(filename, "w")
387 print("Error: bind failed for %s - Cannot open %s"
388 % (dev_id, filename))
391 f.write("%04x %04x" % (dev["Vendor"], dev["Device"]))
394 print("Error: bind failed for %s - Cannot write new PCI ID to "
395 "driver %s" % (dev_id, driver))
398 # do the bind by writing to /sys
399 filename = "/sys/bus/pci/drivers/%s/bind" % driver
401 f = open(filename, "a")
403 print("Error: bind failed for %s - Cannot open %s"
404 % (dev_id, filename))
405 if saved_driver is not None: # restore any previous driver
406 bind_one(dev_id, saved_driver, force)
412 # for some reason, closing dev_id after adding a new PCI ID to new_id
413 # results in IOError. however, if the device was successfully bound,
414 # we don't care for any errors and can safely ignore IOError
415 tmp = get_pci_device_details(dev_id)
416 if "Driver_str" in tmp and tmp["Driver_str"] == driver:
418 print("Error: bind failed for %s - Cannot bind to driver %s"
420 if saved_driver is not None: # restore any previous driver
421 bind_one(dev_id, saved_driver, force)
425 def unbind_all(dev_list, force=False):
426 """Unbind method, takes a list of device locations"""
427 dev_list = map(dev_id_from_dev_name, dev_list)
432 def bind_all(dev_list, driver, force=False):
433 """Bind method, takes a list of device locations"""
436 dev_list = map(dev_id_from_dev_name, dev_list)
439 bind_one(d, driver, force)
441 # when binding devices to a generic driver (i.e. one that doesn't have a
442 # PCI ID table), some devices that are not bound to any other driver could
443 # be bound even if no one has asked them to. hence, we check the list of
444 # drivers again, and see if some of the previously-unbound devices were
446 for d in devices.keys():
447 # skip devices that were already bound or that we know should be bound
448 if "Driver_str" in devices[d] or d in dev_list:
451 # update information about this device
452 devices[d] = dict(devices[d].items() +
453 get_pci_device_details(d).items())
455 # check if updated information indicates that the device was bound
456 if "Driver_str" in devices[d]:
460 def display_devices(title, dev_list, extra_params=None):
461 '''Displays to the user the details of a list of devices given in
462 "dev_list". The "extra_params" parameter, if given, should contain a string
463 with %()s fields in it for replacement by the named fields in each
464 device's dictionary.'''
465 strings = [] # this holds the strings to print. We sort before printing
466 print("\n%s" % title)
467 print("="*len(title))
468 if len(dev_list) == 0:
469 strings.append("<none>")
472 if extra_params is not None:
473 strings.append("%s '%s' %s" % (dev["Slot"],
477 strings.append("%s '%s'" % (dev["Slot"], dev["Device_str"]))
478 # sort before printing, so that the entries appear in PCI order
480 print("\n".join(strings)) # print one per line
482 def show_device_status(devices_type, device_name):
488 # split our list of network devices into the three categories above
489 for d in devices.keys():
490 if devices_type in devices[d]["Class"]:
491 if not has_driver(d):
492 no_drv.append(devices[d])
494 if devices[d]["Driver_str"] in dpdk_drivers:
495 dpdk_drv.append(devices[d])
497 kernel_drv.append(devices[d])
499 # print each category separately, so we can clearly see what's used by DPDK
500 display_devices("%s devices using DPDK-compatible driver" % device_name, dpdk_drv,
501 "drv=%(Driver_str)s unused=%(Module_str)s")
502 display_devices("%s devices using kernel driver" % device_name, kernel_drv,
503 "if=%(Interface)s drv=%(Driver_str)s "
504 "unused=%(Module_str)s %(Active)s")
505 display_devices("Other %s devices" % device_name, no_drv, "unused=%(Module_str)s")
508 '''Function called when the script is passed the "--status" option.
509 Displays to the user what devices are bound to the igb_uio driver, the
510 kernel driver or to no driver'''
512 show_device_status(network_devices, "Network")
513 show_device_status(crypto_devices, "Crypto")
516 '''Parses the command-line arguments given by the user and takes the
517 appropriate action for each'''
522 if len(sys.argv) <= 1:
527 opts, args = getopt.getopt(sys.argv[1:], "b:us",
528 ["help", "usage", "status", "force",
530 except getopt.GetoptError as error:
532 print("Run '%s --usage' for further information" % sys.argv[0])
535 for opt, arg in opts:
536 if opt == "--help" or opt == "--usage":
539 if opt == "--status" or opt == "-s":
543 if opt == "-b" or opt == "-u" or opt == "--bind" or opt == "--unbind":
544 if b_flag is not None:
545 print("Error - Only one bind or unbind may be specified\n")
547 if opt == "-u" or opt == "--unbind":
553 def do_arg_actions():
554 '''do the actual action requested by the user'''
560 if b_flag is None and not status_flag:
561 print("Error: No action specified for devices."
562 "Please give a -b or -u option")
563 print("Run '%s --usage' for further information" % sys.argv[0])
566 if b_flag is not None and len(args) == 0:
567 print("Error: No devices specified.")
568 print("Run '%s --usage' for further information" % sys.argv[0])
571 if b_flag == "none" or b_flag == "None":
572 unbind_all(args, force_flag)
573 elif b_flag is not None:
574 bind_all(args, b_flag, force_flag)
576 if b_flag is not None:
578 get_device_details(NETWORK_BASE_CLASS) # refresh if we have changed anything
579 get_device_details(CRYPTO_BASE_CLASS) # refresh if we have changed anything
584 '''program main function'''
588 get_device_details(NETWORK_BASE_CLASS)
589 get_device_details(CRYPTO_BASE_CLASS)
592 if __name__ == "__main__":