[RFC,v2,5/9] usertools/lib: add device information library
diff mbox series

Message ID 9af4b611a659983a8a155a7d1d10b078b74c1d3d.1542291869.git.anatoly.burakov@intel.com
State New
Delegated to: Thomas Monjalon
Headers show
Series
  • Modularize and enhance DPDK Python scripts
Related show

Checks

Context Check Description
ci/checkpatch success coding style OK

Commit Message

Burakov, Anatoly Nov. 15, 2018, 3:47 p.m. UTC
This library is mostly copy-paste of devbind script, but with few
additional bells and whistles, such as the ability to enumerate and
create/destroy VF devices.

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 usertools/DPDKConfigLib/DevInfo.py | 424 +++++++++++++++++++++++++++++
 usertools/DPDKConfigLib/DevUtil.py | 242 ++++++++++++++++
 usertools/DPDKConfigLib/Util.py    |  19 ++
 3 files changed, 685 insertions(+)
 create mode 100755 usertools/DPDKConfigLib/DevInfo.py
 create mode 100755 usertools/DPDKConfigLib/DevUtil.py

Patch
diff mbox series

diff --git a/usertools/DPDKConfigLib/DevInfo.py b/usertools/DPDKConfigLib/DevInfo.py
new file mode 100755
index 000000000..52edae771
--- /dev/null
+++ b/usertools/DPDKConfigLib/DevInfo.py
@@ -0,0 +1,424 @@ 
+#!/usr/bin/python
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2010-2018 Intel Corporation
+# Copyright(c) 2017 Cavium, Inc. All rights reserved.
+
+import glob
+from .Util import *
+
+__DEFAULT_DPDK_DRIVERS = ["igb_uio", "vfio-pci", "uio_pci_generic"]
+__dpdk_drivers = None  # list of detected dpdk drivers
+__devices = None  # map from PCI address to device objects
+
+# The PCI base class for all devices
+__network_class = {'Class': '02', 'Vendor': None, 'Device': None,
+                   'SVendor': None, 'SDevice': None}
+__encryption_class = {'Class': '10', 'Vendor': None, 'Device': None,
+                      'SVendor': None, 'SDevice': None}
+__intel_processor_class = {'Class': '0b', 'Vendor': '8086', 'Device': None,
+                           'SVendor': None, 'SDevice': None}
+__cavium_sso = {'Class': '08', 'Vendor': '177d', 'Device': 'a04b,a04d',
+                'SVendor': None, 'SDevice': None}
+__cavium_fpa = {'Class': '08', 'Vendor': '177d', 'Device': 'a053',
+                'SVendor': None, 'SDevice': None}
+__cavium_pkx = {'Class': '08', 'Vendor': '177d', 'Device': 'a0dd,a049',
+                'SVendor': None, 'SDevice': None}
+__cavium_tim = {'Class': '08', 'Vendor': '177d', 'Device': 'a051',
+                'SVendor': None, 'SDevice': None}
+__cavium_zip = {'Class': '12', 'Vendor': '177d', 'Device': 'a037',
+                'SVendor': None, 'SDevice': None}
+__avp_vnic = {'Class': '05', 'Vendor': '1af4', 'Device': '1110',
+              'SVendor': None, 'SDevice': None}
+
+# internal data, not supposed to be exposed, but available to local classes
+_network_devices = [__network_class, __cavium_pkx, __avp_vnic]
+_crypto_devices = [__encryption_class, __intel_processor_class]
+_eventdev_devices = [__cavium_sso, __cavium_tim]
+_mempool_devices = [__cavium_fpa]
+_compress_devices = [__cavium_zip]
+
+__DRIVER_PATH_FMT = "/sys/bus/pci/drivers/%s/"
+__DEVICE_PATH_FMT = "/sys/bus/pci/devices/%s/"
+
+
+def _get_pci_speed_info(pci_addr):
+    data = subprocess.check_output(["lspci", "-vvs", pci_addr]).splitlines()
+
+    # scan until we find capability structure
+    raw_data = {}
+    cur_key = ""
+    r = re.compile(r"Express \(v\d\) Endpoint")  # PCI-E cap
+    found_pci_express_cap = False
+    for line in data:
+        key, value = kv_split(line, ":")
+        if not found_pci_express_cap:
+            if key != "Capabilities":
+                continue
+            # this is a capability structure - check if it's a PCI-E cap
+            m = r.search(value)
+            if not m:
+                continue  # not a PCI-E cap
+            found_pci_express_cap = True
+            continue  # start scanning for info
+        elif key == "Capabilities":
+            break  # we've reached end of our PCI-E cap structure
+        if value is not None:
+            # this is a new key
+            cur_key = key
+        else:
+            value = key  # this is continuation of previous key
+        raw_data[cur_key] = " ".join([raw_data.get(cur_key, ""), value])
+
+    # now, get our data out of there
+    result = {
+        "speed_supported": 0,
+        "width_supported": 0,
+        "speed_active": 0,
+        "width_active": 0
+    }
+    speed_re = re.compile(r"Speed (\d+(\.\d+)?)GT/s")
+    width_re = re.compile(r"Width x(\d+)")
+
+    val = raw_data.get("LnkCap", "")
+    speed_m = speed_re.search(val)
+    width_m = width_re.search(val)
+    # return empty
+    if speed_m:
+        result["speed_supported"] = float(speed_m.group(1))
+    if width_m:
+        result["width_supported"] = int(width_m.group(1))
+
+    val = raw_data.get("LnkSta", "")
+    speed_m = speed_re.search(val)
+    width_m = width_re.search(val)
+    if speed_m:
+        result["speed_active"] = float(speed_m.group(1))
+    if width_m:
+        result["width_active"] = int(width_m.group(1))
+    return result
+
+
+def _device_type_match(dev_dict, devices_type):
+    for i in range(len(devices_type)):
+        param_count = len(
+            [x for x in devices_type[i].values() if x is not None])
+        match_count = 0
+        if dev_dict["Class"][0:2] == devices_type[i]["Class"]:
+            match_count = match_count + 1
+            for key in devices_type[i].keys():
+                if key != 'Class' and devices_type[i][key]:
+                    value_list = devices_type[i][key].split(',')
+                    for value in value_list:
+                        if value.strip() == dev_dict[key]:
+                            match_count = match_count + 1
+            # count must be the number of non None parameters to match
+            if match_count == param_count:
+                return True
+    return False
+
+
+def _get_numa_node(addr):
+    path = get_device_path(addr, "numa_node")
+    if not os.path.isfile(path):
+        return 0
+    val = int(read_file(path))
+    return val if val >= 0 else 0
+
+
+def _basename_from_symlink(path):
+    if not os.path.islink(path):
+        raise ValueError("Invalid link: %s" % path)
+    return os.path.basename(os.path.realpath(path))
+
+
+def _get_pf_addr(pci_addr):
+    return _basename_from_symlink(get_device_path(pci_addr, "physfn"))
+
+
+def _get_vf_addrs(pci_addr):
+    vf_path = get_device_path(pci_addr, "virtfn*")
+    return [_basename_from_symlink(path) for path in glob.glob(vf_path)]
+
+
+def _get_total_vfs(pci_addr):
+    path = get_device_path(pci_addr, "sriov_totalvfs")
+    if not os.path.isfile(path):
+        return 0
+    return int(read_file(path))
+
+
+# not allowed to use Enum because it's Python3.4+, so...
+class DeviceType:
+    '''Device type identifier'''
+    DEVTYPE_UNKNOWN = -1
+    DEVTYPE_NETWORK = 0
+    DEVTYPE_CRYPTO = 1
+    DEVTYPE_EVENT = 2
+    DEVTYPE_MEMPOOL = 3
+    DEVTYPE_COMPRESS = 4
+
+
+class DevInfo(object):
+    # map from lspci output to DevInfo attributes
+    __attr_map = {
+        'Class': 'class_id',
+        'Vendor': 'vendor_id',
+        'Device': 'device_id',
+        'SVendor': 'subsystem_vendor_id',
+        'SDevice': 'subsystem_device_id',
+        'Class_str': 'class_name',
+        'Vendor_str': 'vendor_name',
+        'Device_str': 'device_name',
+        'SVendor_str': 'subsystem_vendor_name',
+        'SDevice_str': 'subsystem_device_name',
+        'Driver': 'active_driver'
+    }
+
+    def __init__(self, pci_addr):
+        self.pci_addr = pci_addr  # Slot
+
+        # initialize all attributes
+        self.reset()
+
+        # we know our PCI address at this point, so read lspci
+        self.update()
+
+    def reset(self):
+        self.devtype = DeviceType.DEVTYPE_UNKNOWN  # start with unknown type
+        self.class_id = ""  # Class
+        self.vendor_id = ""  # Vendor
+        self.device_id = ""  # Device
+        self.subsystem_vendor_id = ""  # SVendor
+        self.subsystem_device_id = ""  # SDevice
+        self.class_name = ""  # Class_str
+        self.vendor_name = ""  # Vendor_str
+        self.device_name = ""  # Device_str
+        self.subsystem_vendor_name = ""  # SVendor_str
+        self.subsystem_device_name = ""  # SDevice_str
+        self.kernel_drivers = []  # list of drivers in Module
+        self.active_driver = ""  # Driver
+        self.available_drivers = []
+        self.numa_node = -1
+        self.is_virtual_function = False
+        self.virtual_functions = []  # list of VF pci addresses
+        self.physical_function = ""  # PF PCI address if this is a VF
+        self.numvfs = 0
+        self.totalvfs = 0
+        self.pci_width_supported = 0
+        self.pci_width_active = 0
+        self.pci_speed_supported = 0
+        self.pci_speed_active = 0
+
+    def update(self):
+        # clear everything
+        self.reset()
+
+        lspci_info = subprocess.check_output(["lspci", "-vmmnnks",
+                                              self.pci_addr]).splitlines()
+        lspci_dict = {}
+        r = re.compile(r"\[[\da-f]{4}\]$")
+
+        # parse lspci details
+        for line in lspci_info:
+            if len(line) == 0:
+                continue
+            name, value = line.decode().split("\t", 1)
+            name = name.strip(":")
+            has_id = r.search(value) is not None
+            if has_id:
+                namestr = name + "_str"
+                strvalue = value[:-7]  # cut off hex value for _str value
+                value = value[-5:-1]  # store hex value
+                lspci_dict[namestr] = strvalue
+            lspci_dict[name] = value
+
+        # update object using map of lspci values to object attributes
+        for key, value in lspci_dict.items():
+            if key in self.__attr_map:
+                setattr(self, self.__attr_map[key], value)
+
+        # match device type
+        if _device_type_match(lspci_dict, _network_devices):
+            self.devtype = DeviceType.DEVTYPE_NETWORK
+        elif _device_type_match(lspci_dict, _crypto_devices):
+            self.devtype = DeviceType.DEVTYPE_CRYPTO
+        elif _device_type_match(lspci_dict, _eventdev_devices):
+            self.devtype = DeviceType.DEVTYPE_EVENT
+        elif _device_type_match(lspci_dict, _mempool_devices):
+            self.devtype = DeviceType.DEVTYPE_MEMPOOL
+        elif _device_type_match(lspci_dict, _compress_devices):
+            self.devtype = DeviceType.DEVTYPE_COMPRESS
+
+        # special case - Module may have several drivers
+        if 'Module' in lspci_dict:
+            module_str = lspci_dict['Module'].split(',')
+            self.kernel_drivers = [d.strip() for d in module_str]
+
+        # read NUMA node
+        self.numa_node = _get_numa_node(self.pci_addr)
+
+        # check if device is a PF or a VF
+        try:
+            pf_addr = _get_pf_addr(self.pci_addr)
+            self.is_virtual_function = True
+            self.physical_function = pf_addr
+        except ValueError:
+            self.virtual_functions = _get_vf_addrs(self.pci_addr)
+            self.numvfs = len(self.virtual_functions)
+            self.totalvfs = _get_total_vfs(self.pci_addr)
+
+        if not self.is_virtual_function:
+            speed_info = _get_pci_speed_info(self.pci_addr)
+        else:
+            speed_info = _get_pci_speed_info(self.physical_function)
+
+        self.pci_width_active = speed_info["width_active"]
+        self.pci_width_supported = speed_info["width_supported"]
+        self.pci_speed_active = speed_info["speed_active"]
+        self.pci_speed_supported = speed_info["speed_supported"]
+
+        # update available drivers
+        all_drivers = self.kernel_drivers + get_loaded_dpdk_drivers()
+        self.available_drivers = [driver for driver in all_drivers
+                                  if driver != self.active_driver]
+
+
+# extends PCI device info with a few things unique to network devices
+class NetworkDevInfo(DevInfo):
+    def __init__(self, pci_addr):
+        super(NetworkDevInfo, self).__init__(pci_addr)
+
+    def reset(self):
+        super(NetworkDevInfo, self).reset()
+        self.interfaces = []
+        self.ssh_interface = ""
+        self.active_interface = False
+
+    def update(self):
+        # do regular update from lspci first
+        super(NetworkDevInfo, self).update()
+
+        # now, update network-device-specific stuff
+        dirs = glob.glob(get_device_path(self.pci_addr, "net/*"))
+        self.interfaces = [os.path.basename(d) for d in dirs]
+
+        # check what is the interface if any for an ssh connection if
+        # any to this host, so we can mark it.
+        route = subprocess.check_output(["ip", "-o", "route"])
+        # filter out all lines for 169.254 routes
+        route = "\n".join(filter(lambda ln: not ln.startswith("169.254"),
+                                 route.decode().splitlines()))
+        rt_info = route.split()
+        for i in range(len(rt_info) - 1):
+            if rt_info[i] == "dev":
+                iface = rt_info[i + 1]
+                if iface in self.interfaces:
+                    self.ssh_interface = iface
+                    self.active_interface = True
+                    break
+
+
+def __update_device_list():
+    global __devices
+
+    __devices = {}
+
+    non_network_devices = _crypto_devices + _mempool_devices +\
+                          _eventdev_devices + _compress_devices
+
+    # first loop through and read details for all devices
+    # request machine readable format, with numeric IDs and String
+    dev_dict = {}
+    lspci_lines = subprocess.check_output(["lspci", "-Dvmmnk"]).splitlines()
+    for line in lspci_lines:
+        if line.strip() == "":
+            # we've completed reading this device, so parse it
+            pci_addr = dev_dict['Slot']
+            if _device_type_match(dev_dict, _network_devices):
+                d = NetworkDevInfo(pci_addr)
+                __devices[pci_addr] = d
+            elif _device_type_match(dev_dict, non_network_devices):
+                d = DevInfo(pci_addr)
+                __devices[pci_addr] = d
+            else:
+                # unsupported device, ignore
+                pass
+            dev_dict = {}  # clear the dictionary for next
+            continue
+        name, value = line.decode().split("\t", 1)
+        name = name.rstrip(":")
+        # Numeric IDs
+        dev_dict[name] = value
+
+
+def __update_dpdk_driver_list():
+    global __dpdk_drivers
+
+    __dpdk_drivers = __DEFAULT_DPDK_DRIVERS[:]  # make a copy
+
+    # list of supported modules
+    mods = [{"Name": driver, "Found": False} for driver in __dpdk_drivers]
+
+    # first check if module is loaded
+    try:
+        # Get list of sysfs modules (both built-in and dynamically loaded)
+        sysfs_path = '/sys/module/'
+
+        # Get the list of directories in sysfs_path
+        sysfs_mods = [os.path.join(sysfs_path, o) for o
+                      in os.listdir(sysfs_path)
+                      if os.path.isdir(os.path.join(sysfs_path, o))]
+
+        # Extract the last element of '/sys/module/abc' in the array
+        sysfs_mods = [a.split('/')[-1] for a in sysfs_mods]
+
+        # special case for vfio_pci (module is named vfio-pci,
+        # but its .ko is named vfio_pci)
+        sysfs_mods = [a if a != 'vfio_pci' else 'vfio-pci' for a in sysfs_mods]
+
+        for mod in mods:
+            if mod["Name"] in sysfs_mods:
+                mod["Found"] = True
+    except:
+        pass
+
+    # change DPDK driver list to only contain drivers that are loaded
+    __dpdk_drivers = [mod["Name"] for mod in mods if mod["Found"]]
+
+
+# get a file/directory inside sysfs dir for a given PCI address
+def get_device_path(pci_addr, fname):
+    return os.path.join(__DEVICE_PATH_FMT % pci_addr, fname)
+
+
+# get a file/directory inside sysfs dir for a given driver
+def get_driver_path(driver, fname):
+    return os.path.join(__DRIVER_PATH_FMT % driver, fname)
+
+
+def get_loaded_dpdk_drivers(force_refresh=False):
+    '''Get list of loaded DPDK drivers'''
+    global __dpdk_drivers
+
+    if __dpdk_drivers is not None and not force_refresh:
+        return __dpdk_drivers
+
+    __update_dpdk_driver_list()
+
+    return __dpdk_drivers
+
+
+def get_supported_dpdk_drivers():
+    return __DEFAULT_DPDK_DRIVERS
+
+
+def get_devices(force_refresh=False):
+    '''Get list of detected devices'''
+    global __devices
+
+    if __devices is not None and not force_refresh:
+        return __devices
+
+    __update_device_list()
+
+    return __devices
diff --git a/usertools/DPDKConfigLib/DevUtil.py b/usertools/DPDKConfigLib/DevUtil.py
new file mode 100755
index 000000000..17ee657f7
--- /dev/null
+++ b/usertools/DPDKConfigLib/DevUtil.py
@@ -0,0 +1,242 @@ 
+#!/usr/bin/python
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2010-2018 Intel Corporation
+# Copyright(c) 2017 Cavium, Inc. All rights reserved.
+
+from .DevInfo import *
+import errno
+
+
+# check if we have support for driver_override by looking at sysfs and checking
+# if any of the PCI device directories have driver_override file inside them
+__have_override = len(glob.glob("/sys/bus/pci/devices/*/driver_override")) != 0
+
+
+# wrap custom exceptions, so that we can handle errors we expect, but still pass
+# through any unexpected errors to the caller (which might indicate a bug)
+class BindException(Exception):
+    def __init__(self, *args, **kwargs):
+        Exception.__init__(self, *args, **kwargs)
+
+
+class UnbindException(Exception):
+    def __init__(self, *args, **kwargs):
+        Exception.__init__(self, *args, **kwargs)
+
+
+# change num vfs for a given device
+def __write_numvfs(dev, num_vfs):
+    path = get_device_path(dev.pci_addr, "sriov_numvfs")
+    append_file(path, num_vfs)
+    dev.update()
+
+
+# unbind device from its driver
+def __unbind_device(dev):
+    '''Unbind the device identified by "addr" from its current driver'''
+    addr = dev.pci_addr
+
+    # For kernels >= 3.15 driver_override is used to bind a device to a driver.
+    # Before unbinding it, overwrite driver_override with empty string so that
+    # the device can be bound to any other driver.
+    if __have_override:
+        override_fname = get_device_path(dev.pci_addr, "driver_override")
+        try:
+            write_file(override_fname, "\00")
+        except IOError as e:
+            raise UnbindException("Couldn't overwrite 'driver_override' "
+                                  "for PCI device '%s': %s" %
+                                  (addr, e.strerror))
+
+    filename = get_driver_path(dev.active_driver, "unbind")
+    try:
+        append_file(filename, addr)
+    except IOError:
+        raise UnbindException("Couldn't unbind PCI device '%s'" % addr)
+    dev.update()
+
+
+# bind device to a specified driver
+def __bind_device_to_driver(dev, driver):
+    '''Bind the device given by "dev_id" to the driver "driver". If the device
+    is already bound to a different driver, it will be unbound first'''
+    addr = dev.pci_addr
+
+    # For kernels >= 3.15 driver_override can be used to specify the driver
+    # for a device rather than relying on the driver to provide a positive
+    # match of the device.  The existing process of looking up
+    # the vendor and device ID, adding them to the driver new_id,
+    # will erroneously bind other devices too which has the additional burden
+    # of unbinding those devices
+    if driver in get_loaded_dpdk_drivers():
+        if __have_override:
+            override_fname = get_device_path(dev.pci_addr, "driver_override")
+            try:
+                write_file(override_fname, driver)
+            except IOError as e:
+                raise BindException("Couldn't write 'driver_override' for "
+                                    "PCI device '%s': %s" % (addr, e.strerror))
+        # For kernels < 3.15 use new_id to add PCI id's to the driver
+        else:
+            newid_fname = get_driver_path(driver, "new_id")
+            try:
+                # Convert Device and Vendor Id to int to write to new_id
+                write_file(newid_fname, "%04x %04x" % (int(dev.vendor_id, 16),
+                                                       int(dev.device_id, 16)))
+            except IOError as e:
+                # for some reason, closing new_id after adding a new PCI
+                # ID to new_id results in IOError (with errno set to
+                # ENODEV). however, if the device was successfully bound, we
+                # don't care for any errors and can safely ignore the
+                # error.
+                if e.errno != errno.ENODEV:
+                    raise BindException("Couldn't write 'new_id' for PCI "
+                                        "device '%s': %s" % (addr, e.strerror))
+
+    print(get_driver_path(driver, "bind"))
+    bind_fname = get_driver_path(driver, "bind")
+    try:
+        append_file(bind_fname, addr)
+    except IOError as e:
+        dev.update()
+        print(driver)
+        print(dev.active_driver)
+        raise BindException("Couldn't bind PCI device '%s' to driver '%s': %s" %
+                            (addr, driver, e.strerror))
+    dev.update()
+
+
+def set_num_vfs(dev, num_vfs):
+    if not isinstance(dev, DevInfo):
+        dev = get_devices()[dev]
+    if dev.is_virtual_function:
+        raise ValueError("Device '%s' is a virtual function" % dev.pci_addr)
+    if num_vfs > dev.totalvfs:
+        raise ValueError("Device '%s' has '%i' virtual functions,"
+                         "'%i' requested" % (dev.pci_addr, dev.totalvfs,
+                                             num_vfs))
+    if dev.num_vfs == num_vfs:
+        return
+    __write_numvfs(dev, num_vfs)
+    dev.update()
+
+
+def unbind(addrs, force_unbind=False):
+    '''Unbind device(s) from all drivers'''
+    # build a list if we were not provided a list
+    pci_dev_list = []
+    try:
+        pci_dev_list.extend(addrs)
+    except AttributeError:
+        pci_dev_list.append(addrs)
+
+    # ensure we are only working with DevInfo objects
+    filter_func = (lambda d: d.active_interface != "" and
+                             (d.devtype != DeviceType.DEVTYPE_NETWORK or
+                              not d.active_interface or not force_unbind))
+    pci_dev_list = filter(filter_func, [a if isinstance(a, DevInfo)
+                                        else get_devices()[get_device_name(a)]
+                                        for a in pci_dev_list])
+    for d in pci_dev_list:
+        __unbind_device(d)
+
+
+# we are intentionally not providing a "simple" function to bind a single
+# device due to complexities involved with using kernels < 3.15. instead, we're
+# allowing to call this function with either one PCI address or a list of PCI
+# addresses, or one DevInfo object, or a list of DevInfo objects, and will
+# automatically do cleanup even if we fail to bind some devices
+def bind(addrs, driver, force_unbind=False):
+    '''Bind device(s) to a specified driver'''
+    # build a list if we were not provided a list
+    pci_dev_list = []
+    try:
+        pci_dev_list.extend(addrs)
+    except AttributeError:
+        pci_dev_list.append(addrs)
+
+    # we want devices that aren't already bound to the driver we want, and are
+    # either not network devices, or aren't active network interfaces, unless we
+    # are in force-unbind mode
+    filter_func = (lambda d: d.active_driver != driver and
+                             (d.devtype != DeviceType.DEVTYPE_NETWORK or
+                              not d.active_interface or not force_unbind))
+    # ensure we are working with DevInfo instances, and filter them out
+    pci_dev_list = list(filter(filter_func,
+                               [a if isinstance(a, DevInfo)
+                                else get_devices()[get_device_name(a)]
+                                for a in pci_dev_list]))
+    if len(pci_dev_list) == 0:
+        # nothing to be done, bail out
+        return
+    ex = None
+    try:
+        for dev in pci_dev_list:
+            old_driver = dev.active_driver
+            if dev.active_driver != "":
+                __unbind_device(dev)
+            __bind_device_to_driver(dev, driver)
+    except UnbindException as e:
+        # no need to roll back anything, but still stop
+        ex = e
+    except BindException as e:
+        # roll back changes, stop and raise later
+        dev.update()
+        if old_driver != dev.active_driver:
+            try:
+                __bind_device_to_driver(dev, old_driver)
+            except BindException:
+                # ignore this one, nothing we can do about it
+                pass
+        ex = e
+    finally:
+        # we need to do this regardless of whether we succeeded or failed
+
+        # For kernels < 3.15 when binding devices to a generic driver
+        # (i.e. one that doesn't have a PCI ID table) using new_id, some devices
+        # that are not bound to any other driver could be bound even if no one
+        # has asked them to. hence, we check the list of drivers again, and see
+        # if some of the previously-unbound devices were erroneously bound.
+        if not __have_override:
+            for dev in get_devices():
+                # skip devices that were already (or supposed to be) bound
+                if dev in pci_dev_list or dev.active_driver != "":
+                    continue
+
+                # update information about this device
+                dev.update()
+
+                # check if updated information indicates the device was bound
+                if dev.active_driver != "":
+                    try:
+                        __unbind_device(dev)
+                    except UnbindException as e:
+                        # if we already had an exception previously, don't throw
+                        # this one, because we have a higher-priority one that
+                        # we haven't thrown yet
+                        if ex is not None:
+                            break
+                        raise e
+        # if we've failed somewhere during the bind process, raise that
+        if ex is not None:
+            raise ex
+
+
+def get_device_name(name):
+    '''Take a device "name" - a string passed in by user to identify a NIC
+    device, and determine the device id - i.e. the domain:bus:slot.func - for
+    it, which can then be used to index into the devices array'''
+
+    # check if it's already a suitable index
+    if name in get_devices():
+        return name
+    # check if it's an index just missing the domain part
+    elif "0000:" + name in get_devices():
+        return "0000:" + name
+    else:
+        # check if it's an interface name, e.g. eth1
+        filter_func = (lambda i: i.devtype == DeviceType.DEVTYPE_NETWORK)
+        for dev in filter(filter_func, get_devices().values()):
+            if name in dev.interfaces:
+                return dev.pci_addr
+    return None
diff --git a/usertools/DPDKConfigLib/Util.py b/usertools/DPDKConfigLib/Util.py
index 42434e728..eb21cce15 100755
--- a/usertools/DPDKConfigLib/Util.py
+++ b/usertools/DPDKConfigLib/Util.py
@@ -2,6 +2,25 @@ 
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2018 Intel Corporation
 
+# read entire file and return the result
+def read_file(path):
+    with open(path, 'r') as f:
+        result = f.read().strip()
+    return result
+
+
+# write value to file
+def write_file(path, value):
+    with open(path, 'w') as f:
+        f.write(value)
+
+
+# append value to file
+def append_file(path, value):
+    with open(path, 'a') as f:
+        f.write(value)
+
+
 # split line into key-value pair, cleaning up the values in the process
 def kv_split(line, separator):
     # just in case