new file mode 100755
@@ -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
new file mode 100755
@@ -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
@@ -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