# Copyright 2016-2017 Intel Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Tools for network card manipulation """ import os import subprocess import logging import glob from conf import settings _LOGGER = logging.getLogger('tools.networkcard') _PCI_DIR = '/sys/bus/pci/devices/{}/' _SRIOV_NUMVFS = os.path.join(_PCI_DIR, 'sriov_numvfs') _SRIOV_TOTALVFS = os.path.join(_PCI_DIR, 'sriov_totalvfs') _SRIOV_VF_PREFIX = 'virtfn' _SRIOV_PF = 'physfn' _PCI_NET = 'net' _PCI_DRIVER = 'driver' def check_pci(pci_handle): """ Checks if given extended PCI handle has correct length and fixes it if possible. :param pci_handle: PCI slot identifier. It can contain vsperf specific suffix after '|' with VF indication. e.g. '0000:05:00.0|vf1' :returns: PCI handle """ pci = pci_handle.split('|') pci_len = len(pci[0]) if pci_len == 12: return pci_handle elif pci_len == 7: pci[0] = '0000:' + pci[0][-7:] _LOGGER.debug('Adding domain part to PCI slot %s', pci[0]) return '|'.join(pci) elif pci_len > 12: pci[0] = pci[0][-12:] _LOGGER.warning('PCI slot is too long, it will be shortened to %s', pci[0]) return '|'.join(pci) else: # pci_handle has a strange length, but let us try to use it _LOGGER.error('Unknown format of PCI slot %s', pci_handle) return pci_handle def is_sriov_supported(pci_handle): """ Checks if sriov is supported by given NIC :param pci_handle: PCI slot identifier with domain part. :returns: True on success, False otherwise """ return os.path.isfile(_SRIOV_TOTALVFS.format(pci_handle)) def is_sriov_nic(pci_handle): """ Checks if given extended PCI ID refers to the VF :param pci_handle: PCI slot identifier with domain part. It can contain vsperf specific suffix after '|' with VF indication. e.g. '0000:05:00.0|vf1' :returns: True on success, False otherwise """ for item in pci_handle.split('|'): if item.lower().startswith('vf'): return True return False def set_sriov_numvfs(pci_handle, numvfs): """ Checks if sriov is supported and configures given number of VFs :param pci_handle: PCI slot identifier with domain part. :param numvfs: Number of VFs to be configured at given NIC. :returns: True on success, False otherwise """ if not is_sriov_supported(pci_handle): return False if get_sriov_numvfs(pci_handle) == numvfs: return True if numvfs and get_sriov_numvfs(pci_handle) != 0: if not set_sriov_numvfs(pci_handle, 0): return False try: subprocess.call('sudo bash -c "echo {} > {}"'.format(numvfs, _SRIOV_NUMVFS.format(pci_handle)), shell=True) return get_sriov_numvfs(pci_handle) == numvfs except OSError: _LOGGER.debug('Number of VFs cant be changed to %s for PF %s', numvfs, pci_handle) return False def get_sriov_numvfs(pci_handle): """ Returns the number of configured VFs :param pci_handle: PCI slot identifier with domain part :returns: the number of configured VFs """ if is_sriov_supported(pci_handle): with open(_SRIOV_NUMVFS.format(pci_handle), 'r') as numvfs: return int(numvfs.readline().rstrip('\n')) return None def get_sriov_totalvfs(pci_handle): """ Checks if sriov is supported and returns max number of supported VFs :param pci_handle: PCI slot identifier with domain part :returns: the max number of supported VFs by given NIC """ if is_sriov_supported(pci_handle): with open(_SRIOV_TOTALVFS.format(pci_handle), 'r') as total: return int(total.readline().rstrip('\n')) return None def get_sriov_vfs_list(pf_pci_handle): """ Returns list of PCI handles of VFs configured at given NIC/PF :param pf_pci_handle: PCI slot identifier of PF with domain part. :returns: list """ vfs = [] if is_sriov_supported(pf_pci_handle): for vf_name in glob.glob(os.path.join(_PCI_DIR, _SRIOV_VF_PREFIX + '*').format(pf_pci_handle)): vfs.append(os.path.basename(os.path.realpath(vf_name))) return vfs def get_sriov_pf(vf_pci_handle): """ Get PCI handle of PF which belongs to given VF :param vf_pci_handle: PCI slot identifier of VF with domain part. :returns: PCI handle of parent PF """ pf_path = os.path.join(_PCI_DIR, _SRIOV_PF).format(vf_pci_handle) if os.path.isdir(pf_path): return os.path.basename(os.path.realpath(pf_path)) return None def get_driver(pci_handle): """ Returns name of kernel driver assigned to given NIC :param pci_handle: PCI slot identifier with domain part. :returns: string with assigned kernel driver, None otherwise """ driver_path = os.path.join(_PCI_DIR, _PCI_DRIVER).format(pci_handle) if os.path.isdir(driver_path): return os.path.basename(os.path.realpath(driver_path)) return None def get_device_name(pci_handle): """ Returns name of network card device name :param pci_handle: PCI slot identifier with domain part. :returns: string with assigned NIC device name, None otherwise """ net_path = os.path.join(_PCI_DIR, _PCI_NET).format(pci_handle) try: return os.listdir(net_path)[0] except FileNotFoundError: return None except IndexError: return None return None def get_mac(pci_handle): """ Returns MAC address of given NIC :param pci_handle: PCI slot identifier with domain part. :returns: string with assigned MAC address, None otherwise """ mac_path = glob.glob(os.path.join(_PCI_DIR, _PCI_NET, '*', 'address').format(pci_handle)) # kernel driver is loaded and MAC can be read if mac_path and os.path.isfile(mac_path[0]): with open(mac_path[0], 'r') as _file: return _file.readline().rstrip('\n') # MAC address is unknown, e.g. NIC is assigned to DPDK return None def get_nic_info(full_pci_handle): """ Parse given pci handle with additional info and returns requested NIC info. :param full_pci_handle: A string with extended network card PCI ID. extended PCI ID syntax: PCI_ID[|vfx][|(mac|dev)] examples: 0000:06:00.0 - returns the same value 0000:06:00.0|vf0 - returns PCI ID of 1st virtual function of given NIC 0000:06:00.0|mac - returns MAC address of given NIC 0000:06:00.0|vf0|mac - returns MAC address of 1st virtual function of given NIC :returns: A string with requested NIC data or None if data cannot be read. """ parsed_handle = full_pci_handle.split('|') if len(parsed_handle) not in (1, 2, 3): _LOGGER.error("Invalid PCI device name: '%s'", full_pci_handle) return None pci_handle = parsed_handle[0] for action in parsed_handle[1:]: # in case of SRIOV get PCI handle of given virtual function if action.lower().startswith('vf'): try: vf_num = int(action[2:]) pci_handle = get_sriov_vfs_list(pci_handle)[vf_num] except ValueError: _LOGGER.error("Pci device '%s', does not have VF with index '%s'", pci_handle, action[2:]) return None except IndexError: _LOGGER.error("Pci device '%s', does not have VF with index '%s'", pci_handle, vf_num) return None continue # return requested info for given PCI handle if action.lower() == 'mac': return get_mac(pci_handle) elif action.lower() == 'dev': return get_device_name(pci_handle) else: _LOGGER.error("Invalid item '%s' in PCI handle '%s'", action, full_pci_handle) return None return pci_handle def reinit_vfs(pf_pci_handle): """ Reinitializates all VFs, which belong to given PF :param pf_pci_handle: PCI slot identifier of PF with domain part. """ rte_pci_tool = settings.getValue('TOOLS')['bind-tool'] for vf_nic in get_sriov_vfs_list(pf_pci_handle): nic_driver = get_driver(vf_nic) if nic_driver: try: subprocess.call(['sudo', rte_pci_tool, '--unbind', vf_nic], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) subprocess.call(['sudo', rte_pci_tool, '--bind=' + nic_driver, vf_nic], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except subprocess.CalledProcessError: _LOGGER.warning('Error during reinitialization of VF %s', vf_nic) else: _LOGGER.warning("Can't detect driver for VF %s", vf_nic)