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