Renato Botelho do Couto | ead1e53 | 2019-10-31 13:31:07 -0500 | [diff] [blame] | 1 | #! /usr/bin/env python3 |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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(): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 59 | """Print usage information for the program""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 60 | argv0 = basename(sys.argv[0]) |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 61 | print( |
| 62 | """ |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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 | |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 119 | """ |
| 120 | % locals() |
| 121 | ) # replace items from local variables |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 127 | """Run a command and capture its output""" |
| 128 | return subprocess.Popen(args, stdout=subprocess.PIPE, stderr=stderr).communicate()[ |
| 129 | 0 |
| 130 | ] |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 131 | |
| 132 | |
| 133 | def find_module(mod): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 134 | """find the .ko file for kernel module named mod. |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 135 | Searches the $RTE_SDK/$RTE_TARGET directory, the kernel |
| 136 | modules directory and finally under the parent directory of |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 137 | the script""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 138 | # check $RTE_SDK/$RTE_TARGET directory |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 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 | ) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 145 | if exists(path): |
| 146 | return path |
| 147 | |
| 148 | # check using depmod |
| 149 | try: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 150 | depmod_out = check_output( |
| 151 | ["modinfo", "-n", mod], stderr=subprocess.STDOUT |
| 152 | ).lower() |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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])) |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 162 | if tools_dir.endswith("tools"): |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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(): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 172 | """Checks that igb_uio is loaded""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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) |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 181 | sysfs_path = "/sys/module/" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 182 | |
| 183 | # Get the list of directories in sysfs_path |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 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 | ] |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 189 | |
| 190 | # Extract the last element of '/sys/module/abc' in the array |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 191 | sysfs_mods = [a.split("/")[-1] for a in sysfs_mods] |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 192 | |
| 193 | # special case for vfio_pci (module is named vfio-pci, |
| 194 | # but its .ko is named vfio_pci) |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 195 | sysfs_mods = map(lambda a: a if a != "vfio_pci" else "vfio-pci", sysfs_mods) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 216 | """return true if a device is assigned to a driver. False otherwise""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 217 | return "Driver_str" in devices[dev_id] |
| 218 | |
| 219 | |
| 220 | def get_pci_device_details(dev_id): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 221 | """This function gets additional details for a PCI device""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 237 | device["Interface"] = ",".join(os.listdir(os.path.join(base, "net"))) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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(): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 247 | """This function populates the "devices" dictionary. The keys used are |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 248 | the pci addresses (domain:bus:slot.func). The values are themselves |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 249 | dictionaries - one for each NIC.""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 260 | if len(dev_line) == 0: |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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 |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 276 | route = "\n".join( |
| 277 | filter(lambda ln: not ln.startswith("169.254"), route.decode().splitlines()) |
| 278 | ) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 279 | rt_info = route.split() |
| 280 | for i in range(len(rt_info) - 1): |
| 281 | if rt_info[i] == "dev": |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 282 | ssh_if.append(rt_info[i + 1]) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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"]: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 300 | devices[d]["Module_str"] = devices[d]["Module_str"] + ",%s" % driver |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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(): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 313 | """This function populates the "devices" dictionary. The keys used are |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 314 | the pci addresses (domain:bus:slot.func). The values are themselves |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 315 | dictionaries - one for each NIC.""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 326 | if len(dev_line) == 0: |
| 327 | if dev["Class"][0:2] == CRYPTO_BASE_CLASS: |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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"]: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 347 | devices[d]["Module_str"] = devices[d]["Module_str"] + ",%s" % driver |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 360 | """Take a device "name" - a string passed in by user to identify a NIC |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 361 | device, and determine the device id - i.e. the domain:bus:slot.func - for |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 362 | it, which can then be used to index into the devices array""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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 |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 376 | print( |
| 377 | "Unknown device: %s. " |
| 378 | 'Please specify device in "bus:slot.func" format' % dev_name |
| 379 | ) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 380 | sys.exit(1) |
| 381 | |
| 382 | |
| 383 | def unbind_one(dev_id, force): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 384 | """Unbind the device identified by "dev_id" from its current driver""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 385 | dev = devices[dev_id] |
| 386 | if not has_driver(dev_id): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 387 | print( |
| 388 | "%s %s %s is not currently managed by any driver\n" |
| 389 | % (dev["Slot"], dev["Device_str"], dev["Interface"]) |
| 390 | ) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 391 | return |
| 392 | |
| 393 | # prevent us disconnecting ourselves |
| 394 | if dev["Ssh_if"] and not force: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 395 | print( |
| 396 | "Routing table indicates that interface %s is active. " |
| 397 | "Skipping unbind" % (dev_id) |
| 398 | ) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 406 | print("Error: unbind failed for %s - Cannot open %s" % (dev_id, filename)) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 407 | sys.exit(1) |
| 408 | f.write(dev_id) |
| 409 | f.close() |
| 410 | |
| 411 | |
| 412 | def bind_one(dev_id, driver, force): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 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""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 420 | print( |
| 421 | "Routing table indicates that interface %s is active. " |
| 422 | "Not modifying" % (dev_id) |
| 423 | ) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 424 | return |
| 425 | |
| 426 | # unbind any existing drivers we don't want |
| 427 | if has_driver(dev_id): |
| 428 | if dev["Driver_str"] == driver: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 429 | print("%s already bound to driver %s, skipping\n" % (dev_id, driver)) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 442 | print("Error: bind failed for %s - Cannot open %s" % (dev_id, filename)) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 443 | return |
| 444 | try: |
| 445 | f.write("%04x %04x" % (dev["Vendor"], dev["Device"])) |
| 446 | f.close() |
| 447 | except: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 448 | print( |
| 449 | "Error: bind failed for %s - Cannot write new PCI ID to " |
| 450 | "driver %s" % (dev_id, driver) |
| 451 | ) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 459 | print("Error: bind failed for %s - Cannot open %s" % (dev_id, filename)) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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 |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 473 | print("Error: bind failed for %s - Cannot bind to driver %s" % (dev_id, driver)) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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 |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 506 | devices[d] = dict(devices[d].items() + get_pci_device_details(d).items()) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 514 | """Displays to the user the details of a list of devices given in |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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 |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 517 | device's dictionary.""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 518 | strings = [] # this holds the strings to print. We sort before printing |
| 519 | print("\n%s" % title) |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 520 | print("=" * len(title)) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 526 | strings.append( |
| 527 | "%s '%s' %s" % (dev["Slot"], dev["Device_str"], extra_params % dev) |
| 528 | ) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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(): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 537 | """Function called when the script is passed the "--status" option. |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 538 | Displays to the user what devices are bound to the igb_uio driver, the |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 539 | kernel driver or to no driver""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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(): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 547 | if NETWORK_BASE_CLASS in devices[d]["Class"]: |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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 |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 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 | ) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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(): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 575 | if CRYPTO_BASE_CLASS in devices[d]["Class"]: |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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 | |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 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 | ) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 594 | display_devices("Other crypto devices", no_drv, "unused=%(Module_str)s") |
| 595 | |
| 596 | |
| 597 | def parse_args(): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 598 | """Parses the command-line arguments given by the user and takes the |
| 599 | appropriate action for each""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 609 | opts, args = getopt.getopt( |
| 610 | sys.argv[1:], |
| 611 | "b:us", |
| 612 | ["help", "usage", "status", "force", "bind=", "unbind"], |
| 613 | ) |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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(): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 638 | """do the actual action requested by the user""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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: |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 645 | print("Error: No action specified for devices." "Please give a -b or -u option") |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 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(): |
Klement Sekera | d9b0c6f | 2022-04-26 19:02:15 +0200 | [diff] [blame] | 666 | """program main function""" |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 667 | parse_args() |
| 668 | check_modules() |
| 669 | get_nic_details() |
| 670 | get_crypto_details() |
| 671 | do_arg_actions() |
| 672 | |
Paul Vinciguerra | 339bc6b | 2018-12-19 02:05:25 -0800 | [diff] [blame] | 673 | |
John DeNisco | 68b0ee3 | 2017-09-27 16:35:23 -0400 | [diff] [blame] | 674 | if __name__ == "__main__": |
| 675 | main() |