]> git.proxmox.com Git - ceph.git/blame - ceph/src/seastar/scripts/dpdk_nic_bind.py
import quincy beta 17.1.0
[ceph.git] / ceph / src / seastar / scripts / dpdk_nic_bind.py
CommitLineData
9f95a23c 1#!/usr/bin/env python2
7c673cae
FG
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
11fdf7f2 35import sys, os, getopt, subprocess
7c673cae
FG
36from os.path import exists, abspath, dirname, basename
37
11fdf7f2
TL
38
39# The PCI device class for ETHERNET devices
40ETHERNET_CLASS = "0200"
7c673cae
FG
41
42# global dict ethernet devices present. Dictionary indexed by PCI address.
43# Each device within this is itself a dictionary of device properties
44devices = {}
45# list of supported DPDK drivers
11fdf7f2 46dpdk_drivers = [ "igb_uio", "vfio-pci", "uio_pci_generic" ]
7c673cae
FG
47
48# command-line arg flags
49b_flag = None
50status_flag = False
51force_flag = False
52args = []
53
7c673cae
FG
54def usage():
55 '''Print usage information for the program'''
56 argv0 = basename(sys.argv[0])
11fdf7f2 57 print """
7c673cae
FG
58Usage:
59------
60
61 %(argv0)s [options] DEVICE1 DEVICE2 ....
62
63where DEVICE1, DEVICE2 etc, are specified via PCI "domain:bus:slot.func" syntax
64or "bus:slot.func" syntax. For devices bound to Linux kernel drivers, they may
65also be referred to by Linux interface name e.g. eth0, eth1, em0, em1, etc.
66
67Options:
68 --help, --usage:
69 Display usage information and quit
70
11fdf7f2
TL
71 --status:
72 Print the current status of all known network interfaces.
7c673cae
FG
73 For each device, it displays the PCI domain, bus, slot and function,
74 along with a text description of the device. Depending upon whether the
75 device is being used by a kernel driver, the igb_uio driver, or no
76 driver, other relevant information will be displayed:
77 * the Linux interface name e.g. if=eth0
78 * the driver being used e.g. drv=igb_uio
79 * any suitable drivers not currently using that device
80 e.g. unused=igb_uio
11fdf7f2
TL
81 NOTE: if this flag is passed along with a bind/unbind option, the status
82 display will always occur after the other operations have taken place.
7c673cae
FG
83
84 -b driver, --bind=driver:
85 Select the driver to use or \"none\" to unbind the device
86
87 -u, --unbind:
88 Unbind a device (Equivalent to \"-b none\")
89
90 --force:
11fdf7f2 91 By default, devices which are used by Linux - as indicated by having
7c673cae
FG
92 routes in the routing table - cannot be modified. Using the --force
93 flag overrides this behavior, allowing active links to be forcibly
94 unbound.
95 WARNING: This can lead to loss of network connection and should be used
96 with caution.
97
98Examples:
99---------
100
101To display current device status:
102 %(argv0)s --status
103
104To bind eth1 from the current driver and move to use igb_uio
105 %(argv0)s --bind=igb_uio eth1
106
107To unbind 0000:01:00.0 from using any driver
108 %(argv0)s -u 0000:01:00.0
109
110To bind 0000:02:00.0 and 0000:02:00.1 to the ixgbe kernel driver
111 %(argv0)s -b ixgbe 02:00.0 02:00.1
112
11fdf7f2 113 """ % locals() # replace items from local variables
7c673cae
FG
114
115# This is roughly compatible with check_output function in subprocess module
116# which is only available in python 2.7.
117def check_output(args, stderr=None):
118 '''Run a command and capture its output'''
119 return subprocess.Popen(args, stdout=subprocess.PIPE,
120 stderr=stderr).communicate()[0]
121
7c673cae
FG
122def find_module(mod):
123 '''find the .ko file for kernel module named mod.
124 Searches the $RTE_SDK/$RTE_TARGET directory, the kernel
125 modules directory and finally under the parent directory of
126 the script '''
127 # check $RTE_SDK/$RTE_TARGET directory
128 if 'RTE_SDK' in os.environ and 'RTE_TARGET' in os.environ:
11fdf7f2 129 path = "%s/%s/kmod/%s.ko" % (os.environ['RTE_SDK'],\
7c673cae
FG
130 os.environ['RTE_TARGET'], mod)
131 if exists(path):
132 return path
133
134 # check using depmod
135 try:
11fdf7f2 136 depmod_out = check_output(["modinfo", "-n", mod], \
7c673cae
FG
137 stderr=subprocess.STDOUT).lower()
138 if "error" not in depmod_out:
139 path = depmod_out.strip()
140 if exists(path):
141 return path
11fdf7f2 142 except: # if modinfo can't find module, it fails, so continue
7c673cae
FG
143 pass
144
145 # check for a copy based off current path
146 tools_dir = dirname(abspath(sys.argv[0]))
147 if (tools_dir.endswith("tools")):
148 base_dir = dirname(tools_dir)
149 find_out = check_output(["find", base_dir, "-name", mod + ".ko"])
11fdf7f2 150 if len(find_out) > 0: #something matched
7c673cae
FG
151 path = find_out.splitlines()[0]
152 if exists(path):
153 return path
154
7c673cae
FG
155def check_modules():
156 '''Checks that igb_uio is loaded'''
157 global dpdk_drivers
158
11fdf7f2
TL
159 fd = file("/proc/modules")
160 loaded_mods = fd.readlines()
161 fd.close()
162
7c673cae 163 # list of supported modules
11fdf7f2 164 mods = [{"Name" : driver, "Found" : False} for driver in dpdk_drivers]
7c673cae
FG
165
166 # first check if module is loaded
11fdf7f2 167 for line in loaded_mods:
7c673cae 168 for mod in mods:
11fdf7f2
TL
169 if line.startswith(mod["Name"]):
170 mod["Found"] = True
171 # special case for vfio_pci (module is named vfio-pci,
172 # but its .ko is named vfio_pci)
173 elif line.replace("_", "-").startswith(mod["Name"]):
7c673cae 174 mod["Found"] = True
7c673cae
FG
175
176 # check if we have at least one loaded module
177 if True not in [mod["Found"] for mod in mods] and b_flag is not None:
178 if b_flag in dpdk_drivers:
11fdf7f2 179 print "Error - no supported modules(DPDK driver) are loaded"
7c673cae
FG
180 sys.exit(1)
181 else:
11fdf7f2 182 print "Warning - no supported modules(DPDK driver) are loaded"
7c673cae
FG
183
184 # change DPDK driver list to only contain drivers that are loaded
185 dpdk_drivers = [mod["Name"] for mod in mods if mod["Found"]]
186
7c673cae
FG
187def has_driver(dev_id):
188 '''return true if a device is assigned to a driver. False otherwise'''
189 return "Driver_str" in devices[dev_id]
190
7c673cae
FG
191def get_pci_device_details(dev_id):
192 '''This function gets additional details for a PCI device'''
193 device = {}
194
195 extra_info = check_output(["lspci", "-vmmks", dev_id]).splitlines()
196
197 # parse lspci details
198 for line in extra_info:
199 if len(line) == 0:
200 continue
11fdf7f2 201 name, value = line.split("\t", 1)
7c673cae
FG
202 name = name.strip(":") + "_str"
203 device[name] = value
204 # check for a unix interface name
11fdf7f2
TL
205 sys_path = "/sys/bus/pci/devices/%s/net/" % dev_id
206 if exists(sys_path):
207 device["Interface"] = ",".join(os.listdir(sys_path))
208 else:
209 device["Interface"] = ""
7c673cae
FG
210 # check if a port is used for ssh connection
211 device["Ssh_if"] = False
212 device["Active"] = ""
213
214 return device
215
7c673cae
FG
216def get_nic_details():
217 '''This function populates the "devices" dictionary. The keys used are
218 the pci addresses (domain:bus:slot.func). The values are themselves
219 dictionaries - one for each NIC.'''
220 global devices
221 global dpdk_drivers
222
223 # clear any old data
224 devices = {}
225 # first loop through and read details for all devices
226 # request machine readable format, with numeric IDs
11fdf7f2 227 dev = {};
7c673cae
FG
228 dev_lines = check_output(["lspci", "-Dvmmn"]).splitlines()
229 for dev_line in dev_lines:
230 if (len(dev_line) == 0):
11fdf7f2
TL
231 if dev["Class"] == ETHERNET_CLASS:
232 #convert device and vendor ids to numbers, then add to global
233 dev["Vendor"] = int(dev["Vendor"],16)
234 dev["Device"] = int(dev["Device"],16)
235 devices[dev["Slot"]] = dict(dev) # use dict to make copy of dev
7c673cae 236 else:
11fdf7f2 237 name, value = dev_line.split("\t", 1)
7c673cae
FG
238 dev[name.rstrip(":")] = value
239
240 # check what is the interface if any for an ssh connection if
241 # any to this host, so we can mark it later.
242 ssh_if = []
243 route = check_output(["ip", "-o", "route"])
244 # filter out all lines for 169.254 routes
245 route = "\n".join(filter(lambda ln: not ln.startswith("169.254"),
11fdf7f2 246 route.splitlines()))
7c673cae 247 rt_info = route.split()
11fdf7f2 248 for i in xrange(len(rt_info) - 1):
7c673cae
FG
249 if rt_info[i] == "dev":
250 ssh_if.append(rt_info[i+1])
251
252 # based on the basic info, get extended text details
253 for d in devices.keys():
254 # get additional info and add it to existing data
11fdf7f2
TL
255 devices[d] = dict(devices[d].items() +
256 get_pci_device_details(d).items())
7c673cae
FG
257
258 for _if in ssh_if:
259 if _if in devices[d]["Interface"].split(","):
260 devices[d]["Ssh_if"] = True
261 devices[d]["Active"] = "*Active*"
11fdf7f2 262 break;
7c673cae
FG
263
264 # add igb_uio to list of supporting modules if needed
265 if "Module_str" in devices[d]:
266 for driver in dpdk_drivers:
267 if driver not in devices[d]["Module_str"]:
11fdf7f2 268 devices[d]["Module_str"] = devices[d]["Module_str"] + ",%s" % driver
7c673cae
FG
269 else:
270 devices[d]["Module_str"] = ",".join(dpdk_drivers)
271
272 # make sure the driver and module strings do not have any duplicates
273 if has_driver(d):
274 modules = devices[d]["Module_str"].split(",")
275 if devices[d]["Driver_str"] in modules:
276 modules.remove(devices[d]["Driver_str"])
277 devices[d]["Module_str"] = ",".join(modules)
278
7c673cae
FG
279def dev_id_from_dev_name(dev_name):
280 '''Take a device "name" - a string passed in by user to identify a NIC
281 device, and determine the device id - i.e. the domain:bus:slot.func - for
282 it, which can then be used to index into the devices array'''
11fdf7f2 283 dev = None
7c673cae
FG
284 # check if it's already a suitable index
285 if dev_name in devices:
286 return dev_name
287 # check if it's an index just missing the domain part
288 elif "0000:" + dev_name in devices:
289 return "0000:" + dev_name
290 else:
291 # check if it's an interface name, e.g. eth1
292 for d in devices.keys():
293 if dev_name in devices[d]["Interface"].split(","):
294 return devices[d]["Slot"]
295 # if nothing else matches - error
11fdf7f2
TL
296 print "Unknown device: %s. " \
297 "Please specify device in \"bus:slot.func\" format" % dev_name
7c673cae
FG
298 sys.exit(1)
299
7c673cae
FG
300def unbind_one(dev_id, force):
301 '''Unbind the device identified by "dev_id" from its current driver'''
302 dev = devices[dev_id]
303 if not has_driver(dev_id):
11fdf7f2
TL
304 print "%s %s %s is not currently managed by any driver\n" % \
305 (dev["Slot"], dev["Device_str"], dev["Interface"])
7c673cae
FG
306 return
307
308 # prevent us disconnecting ourselves
309 if dev["Ssh_if"] and not force:
11fdf7f2
TL
310 print "Routing table indicates that interface %s is active" \
311 ". Skipping unbind" % (dev_id)
7c673cae
FG
312 return
313
314 # write to /sys to unbind
315 filename = "/sys/bus/pci/drivers/%s/unbind" % dev["Driver_str"]
316 try:
317 f = open(filename, "a")
318 except:
11fdf7f2
TL
319 print "Error: unbind failed for %s - Cannot open %s" % (dev_id, filename)
320 sys/exit(1)
7c673cae
FG
321 f.write(dev_id)
322 f.close()
323
7c673cae
FG
324def bind_one(dev_id, driver, force):
325 '''Bind the device given by "dev_id" to the driver "driver". If the device
326 is already bound to a different driver, it will be unbound first'''
327 dev = devices[dev_id]
11fdf7f2 328 saved_driver = None # used to rollback any unbind in case of failure
7c673cae
FG
329
330 # prevent disconnection of our ssh session
331 if dev["Ssh_if"] and not force:
11fdf7f2
TL
332 print "Routing table indicates that interface %s is active" \
333 ". Not modifying" % (dev_id)
7c673cae
FG
334 return
335
336 # unbind any existing drivers we don't want
337 if has_driver(dev_id):
338 if dev["Driver_str"] == driver:
11fdf7f2 339 print "%s already bound to driver %s, skipping\n" % (dev_id, driver)
7c673cae
FG
340 return
341 else:
342 saved_driver = dev["Driver_str"]
343 unbind_one(dev_id, force)
11fdf7f2 344 dev["Driver_str"] = "" # clear driver string
7c673cae
FG
345
346 # if we are binding to one of DPDK drivers, add PCI id's to that driver
347 if driver in dpdk_drivers:
348 filename = "/sys/bus/pci/drivers/%s/new_id" % driver
349 try:
350 f = open(filename, "w")
351 except:
11fdf7f2 352 print "Error: bind failed for %s - Cannot open %s" % (dev_id, filename)
7c673cae
FG
353 return
354 try:
355 f.write("%04x %04x" % (dev["Vendor"], dev["Device"]))
356 f.close()
357 except:
11fdf7f2
TL
358 print "Error: bind failed for %s - Cannot write new PCI ID to " \
359 "driver %s" % (dev_id, driver)
7c673cae
FG
360 return
361
362 # do the bind by writing to /sys
363 filename = "/sys/bus/pci/drivers/%s/bind" % driver
364 try:
365 f = open(filename, "a")
366 except:
11fdf7f2
TL
367 print "Error: bind failed for %s - Cannot open %s" % (dev_id, filename)
368 if saved_driver is not None: # restore any previous driver
7c673cae
FG
369 bind_one(dev_id, saved_driver, force)
370 return
371 try:
372 f.write(dev_id)
373 f.close()
374 except:
375 # for some reason, closing dev_id after adding a new PCI ID to new_id
376 # results in IOError. however, if the device was successfully bound,
377 # we don't care for any errors and can safely ignore IOError
378 tmp = get_pci_device_details(dev_id)
379 if "Driver_str" in tmp and tmp["Driver_str"] == driver:
380 return
11fdf7f2
TL
381 print "Error: bind failed for %s - Cannot bind to driver %s" % (dev_id, driver)
382 if saved_driver is not None: # restore any previous driver
7c673cae
FG
383 bind_one(dev_id, saved_driver, force)
384 return
385
386
387def unbind_all(dev_list, force=False):
388 """Unbind method, takes a list of device locations"""
389 dev_list = map(dev_id_from_dev_name, dev_list)
390 for d in dev_list:
391 unbind_one(d, force)
392
7c673cae
FG
393def bind_all(dev_list, driver, force=False):
394 """Bind method, takes a list of device locations"""
395 global devices
396
397 dev_list = map(dev_id_from_dev_name, dev_list)
398
399 for d in dev_list:
400 bind_one(d, driver, force)
401
402 # when binding devices to a generic driver (i.e. one that doesn't have a
403 # PCI ID table), some devices that are not bound to any other driver could
404 # be bound even if no one has asked them to. hence, we check the list of
405 # drivers again, and see if some of the previously-unbound devices were
406 # erroneously bound.
407 for d in devices.keys():
408 # skip devices that were already bound or that we know should be bound
409 if "Driver_str" in devices[d] or d in dev_list:
410 continue
411
412 # update information about this device
413 devices[d] = dict(devices[d].items() +
414 get_pci_device_details(d).items())
415
416 # check if updated information indicates that the device was bound
417 if "Driver_str" in devices[d]:
418 unbind_one(d, force)
419
11fdf7f2
TL
420def display_devices(title, dev_list, extra_params = None):
421 '''Displays to the user the details of a list of devices given in "dev_list"
422 The "extra_params" parameter, if given, should contain a string with
423 %()s fields in it for replacement by the named fields in each device's
424 dictionary.'''
425 strings = [] # this holds the strings to print. We sort before printing
426 print "\n%s" % title
427 print "="*len(title)
7c673cae
FG
428 if len(dev_list) == 0:
429 strings.append("<none>")
430 else:
431 for dev in dev_list:
432 if extra_params is not None:
11fdf7f2
TL
433 strings.append("%s '%s' %s" % (dev["Slot"], \
434 dev["Device_str"], extra_params % dev))
7c673cae
FG
435 else:
436 strings.append("%s '%s'" % (dev["Slot"], dev["Device_str"]))
437 # sort before printing, so that the entries appear in PCI order
438 strings.sort()
11fdf7f2 439 print "\n".join(strings) # print one per line
7c673cae
FG
440
441def show_status():
11fdf7f2
TL
442 '''Function called when the script is passed the "--status" option. Displays
443 to the user what devices are bound to the igb_uio driver, the kernel driver
444 or to no driver'''
7c673cae
FG
445 global dpdk_drivers
446 kernel_drv = []
447 dpdk_drv = []
448 no_drv = []
449
11fdf7f2 450 # split our list of devices into the three categories above
7c673cae 451 for d in devices.keys():
11fdf7f2
TL
452 if not has_driver(d):
453 no_drv.append(devices[d])
454 continue
455 if devices[d]["Driver_str"] in dpdk_drivers:
456 dpdk_drv.append(devices[d])
457 else:
458 kernel_drv.append(devices[d])
7c673cae
FG
459
460 # print each category separately, so we can clearly see what's used by DPDK
11fdf7f2 461 display_devices("Network devices using DPDK-compatible driver", dpdk_drv, \
7c673cae
FG
462 "drv=%(Driver_str)s unused=%(Module_str)s")
463 display_devices("Network devices using kernel driver", kernel_drv,
11fdf7f2
TL
464 "if=%(Interface)s drv=%(Driver_str)s unused=%(Module_str)s %(Active)s")
465 display_devices("Other network devices", no_drv,\
7c673cae 466 "unused=%(Module_str)s")
7c673cae
FG
467
468def parse_args():
469 '''Parses the command-line arguments given by the user and takes the
470 appropriate action for each'''
471 global b_flag
472 global status_flag
473 global force_flag
474 global args
475 if len(sys.argv) <= 1:
476 usage()
477 sys.exit(0)
478
479 try:
11fdf7f2
TL
480 opts, args = getopt.getopt(sys.argv[1:], "b:u",
481 ["help", "usage", "status", "force",
482 "bind=", "unbind"])
483 except getopt.GetoptError, error:
484 print str(error)
485 print "Run '%s --usage' for further information" % sys.argv[0]
7c673cae
FG
486 sys.exit(1)
487
488 for opt, arg in opts:
489 if opt == "--help" or opt == "--usage":
490 usage()
491 sys.exit(0)
11fdf7f2 492 if opt == "--status":
7c673cae
FG
493 status_flag = True
494 if opt == "--force":
495 force_flag = True
496 if opt == "-b" or opt == "-u" or opt == "--bind" or opt == "--unbind":
497 if b_flag is not None:
11fdf7f2 498 print "Error - Only one bind or unbind may be specified\n"
7c673cae
FG
499 sys.exit(1)
500 if opt == "-u" or opt == "--unbind":
501 b_flag = "none"
502 else:
503 b_flag = arg
504
7c673cae
FG
505def do_arg_actions():
506 '''do the actual action requested by the user'''
507 global b_flag
508 global status_flag
509 global force_flag
510 global args
511
512 if b_flag is None and not status_flag:
11fdf7f2
TL
513 print "Error: No action specified for devices. Please give a -b or -u option"
514 print "Run '%s --usage' for further information" % sys.argv[0]
7c673cae
FG
515 sys.exit(1)
516
517 if b_flag is not None and len(args) == 0:
11fdf7f2
TL
518 print "Error: No devices specified."
519 print "Run '%s --usage' for further information" % sys.argv[0]
7c673cae
FG
520 sys.exit(1)
521
522 if b_flag == "none" or b_flag == "None":
523 unbind_all(args, force_flag)
524 elif b_flag is not None:
525 bind_all(args, b_flag, force_flag)
526 if status_flag:
527 if b_flag is not None:
11fdf7f2 528 get_nic_details() # refresh if we have changed anything
7c673cae
FG
529 show_status()
530
7c673cae
FG
531def main():
532 '''program main function'''
533 parse_args()
534 check_modules()
535 get_nic_details()
7c673cae
FG
536 do_arg_actions()
537
538if __name__ == "__main__":
539 main()