# # Copyright (c) 2013-2016 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # # vim: tabstop=4 shiftwidth=4 softtabstop=4 # All Rights Reserved. # """ inventory pci Utilities and helper functions.""" from eventlet.green import subprocess import glob import os import shlex from oslo_log import log as logging from sysinv._i18n import _ from sysinv.common import constants from sysinv.common import device as dconstants from sysinv.common import utils LOG = logging.getLogger(__name__) # Look for PCI class 0x0200 and 0x0280 so that we get generic ethernet # controllers and those that may report as "other" network controllers. ETHERNET_PCI_CLASSES = ['ethernet controller', 'network controller'] # Look for other devices we may want to inventory. KNOWN_PCI_DEVICES = [{"vendor_id": constants.NOVA_PCI_ALIAS_QAT_PF_VENDOR, "device_id": constants.NOVA_PCI_ALIAS_QAT_DH895XCC_PF_DEVICE, "class_id": constants.NOVA_PCI_ALIAS_QAT_CLASS}, {"vendor_id": constants.NOVA_PCI_ALIAS_QAT_PF_VENDOR, "device_id": constants.NOVA_PCI_ALIAS_QAT_C62X_PF_DEVICE, "class_id": constants.NOVA_PCI_ALIAS_QAT_CLASS}, {"class_id": constants.NOVA_PCI_ALIAS_GPU_CLASS}, {"class_id": dconstants.PCI_DEVICE_CLASS_FPGA}] # PCI-SIG 0x06 bridge devices to not inventory. IGNORE_BRIDGE_PCI_CLASSES = ['bridge', 'isa bridge', 'host bridge'] # PCI-SIG 0x08 generic peripheral devices to not inventory. IGNORE_PERIPHERAL_PCI_CLASSES = ['system peripheral', 'pic', 'dma controller', 'iommu', 'rtc'] # PCI-SIG 0x11 signal processing devices to not inventory. IGNORE_SIGNAL_PROCESSING_PCI_CLASSES = ['performance counters'] # Blacklist of devices we do not want to inventory, because they are dealt # with separately (ie. Ethernet devices), or do not make sense to expose # to a guest. IGNORE_PCI_CLASSES = ETHERNET_PCI_CLASSES + IGNORE_BRIDGE_PCI_CLASSES + \ IGNORE_PERIPHERAL_PCI_CLASSES + \ IGNORE_SIGNAL_PROCESSING_PCI_CLASSES pciaddr = 0 pclass = 1 pvendor = 2 pdevice = 3 prevision = 4 psvendor = 5 psdevice = 6 VALID_PORT_SPEED = ['10', '100', '1000', '10000', '40000', '100000'] # Network device flags (from include/uapi/linux/if.h) IFF_UP = 1 << 0 IFF_BROADCAST = 1 << 1 IFF_DEBUG = 1 << 2 IFF_LOOPBACK = 1 << 3 IFF_POINTOPOINT = 1 << 4 IFF_NOTRAILERS = 1 << 5 IFF_RUNNING = 1 << 6 IFF_NOARP = 1 << 7 IFF_PROMISC = 1 << 8 IFF_ALLMULTI = 1 << 9 IFF_MASTER = 1 << 10 IFF_SLAVE = 1 << 11 IFF_MULTICAST = 1 << 12 IFF_PORTSEL = 1 << 13 IFF_AUTOMEDIA = 1 << 14 IFF_DYNAMIC = 1 << 15 class PCI(object): '''Class to encapsulate PCI data for System Inventory''' def __init__(self, pciaddr, pclass, pvendor, pdevice, prevision, psvendor, psdevice): '''Construct a Ipci object with the given values.''' self.pciaddr = pciaddr self.pclass = pclass self.pvendor = pvendor self.pdevice = pdevice self.prevision = prevision self.psvendor = psvendor self.psdevice = psdevice def __eq__(self, rhs): return (self.pvendor == rhs.pvendor and self.pdevice == rhs.pdevice) def __hash__(self): return hash((self.pciaddr, self.pclass, self.pvendor, self.pdevice, self.prevision, self.psvendor, self.psdevice)) def __ne__(self, rhs): return (self.pvendor != rhs.pvendor or self.pdevice != rhs.pdevice) def __str__(self): return "%s [%s] [%s]" % (self.pciaddr, self.pvendor, self.pdevice) def __repr__(self): return "" % str(self) class Port(object): '''Class to encapsulate PCI data for System Inventory''' def __init__(self, ipci, **kwargs): '''Construct an Iport object with the given values.''' self.ipci = ipci self.name = kwargs.get('name') self.mac = kwargs.get('mac') self.mtu = kwargs.get('mtu') self.speed = kwargs.get('speed') self.link_mode = kwargs.get('link_mode') self.numa_node = kwargs.get('numa_node') self.dev_id = kwargs.get('dev_id') self.sriov_totalvfs = kwargs.get('sriov_totalvfs') self.sriov_numvfs = kwargs.get('sriov_numvfs') self.sriov_vfs_pci_address = kwargs.get('sriov_vfs_pci_address') self.sriov_vf_driver = kwargs.get('sriov_vf_driver') self.sriov_vf_pdevice_id = kwargs.get('sriov_vf_pdevice_id') self.driver = kwargs.get('driver') self.dpdksupport = kwargs.get('dpdksupport') def __str__(self): return "%s %s: [%s] [%s] [%s], [%s], [%s], [%s], [%s]" % ( self.ipci, self.name, self.mac, self.mtu, self.speed, self.link_mode, self.numa_node, self.dev_id, self.dpdksupport) def __repr__(self): return "" % str(self) class PCIDevice(object): '''Class to encapsulate extended PCI data for System Inventory''' def __init__(self, pci, **kwargs): '''Construct a PciDevice object with the given values.''' self.pci = pci self.name = kwargs.get('name') self.pclass_id = kwargs.get('pclass_id') self.pvendor_id = kwargs.get('pvendor_id') self.pdevice_id = kwargs.get('pdevice_id') self.numa_node = kwargs.get('numa_node') self.sriov_totalvfs = kwargs.get('sriov_totalvfs') self.sriov_numvfs = kwargs.get('sriov_numvfs') self.sriov_vfs_pci_address = kwargs.get('sriov_vfs_pci_address') self.sriov_vf_driver = kwargs.get('sriov_vf_driver') self.sriov_vf_pdevice_id = kwargs.get('sriov_vf_pdevice_id') self.driver = kwargs.get('driver') self.enabled = kwargs.get('enabled') self.extra_info = kwargs.get('extra_info') def __str__(self): return "%s %s: [%s]" % ( self.pci, self.numa_node, self.driver) def __repr__(self): return "" % str(self) class PCIOperator(object): '''Class to encapsulate PCI operations for System Inventory''' def format_lspci_output(self, device): # hack for now # NOTE: this does not properly handle the case where we have both # "-r" and "-p" optional info in the lspci output. if device[prevision].strip() == device[pvendor].strip(): # no revision info reported, device[prevision] now stores the # psvendor, and device[psvendor] now stores the psdevice. We # need to put things where they should be. device.append(device[psvendor]) device[psvendor] = device[prevision] device[prevision] = "0" elif len(device) <= 6: # one less entry, no revision LOG.debug("update psdevice length=%s" % len(device)) device.append(device[psvendor]) return device def get_pci_numa_node(self, pciaddr): fnuma_node = '/sys/bus/pci/devices/' + pciaddr + '/numa_node' try: with open(fnuma_node, 'r') as f: numa_node = f.readline().strip() LOG.debug("ATTR numa_node: %s " % numa_node) except Exception: LOG.debug("ATTR numa_node unknown for: %s " % pciaddr) numa_node = None return numa_node def get_pci_sriov_totalvfs(self, pciaddr): fsriov_totalvfs = '/sys/bus/pci/devices/' + pciaddr + '/sriov_totalvfs' try: with open(fsriov_totalvfs, 'r') as f: sriov_totalvfs = f.readline() LOG.debug("ATTR sriov_totalvfs: %s " % sriov_totalvfs) f.close() except Exception: LOG.debug("ATTR sriov_totalvfs unknown for: %s " % pciaddr) sriov_totalvfs = None pass return sriov_totalvfs def get_pci_sriov_numvfs(self, pciaddr): fsriov_numvfs = '/sys/bus/pci/devices/' + pciaddr + '/sriov_numvfs' try: with open(fsriov_numvfs, 'r') as f: sriov_numvfs = f.readline() LOG.debug("ATTR sriov_numvfs: %s " % sriov_numvfs) f.close() except Exception: LOG.debug("ATTR sriov_numvfs unknown for: %s " % pciaddr) sriov_numvfs = 0 pass LOG.debug("sriov_numvfs: %s" % sriov_numvfs) return sriov_numvfs def get_pci_sriov_vfs_pci_address(self, pciaddr, sriov_numvfs): dirpcidev = '/sys/bus/pci/devices/' + pciaddr sriov_vfs_pci_address = [] i = 0 while i < int(sriov_numvfs): lvf = dirpcidev + '/virtfn' + str(i) try: sriov_vfs_pci_address.append(os.path.basename(os.readlink(lvf))) except Exception: LOG.warning("virtfn link %s non-existent (sriov_numvfs=%s)" % (lvf, sriov_numvfs)) pass i += 1 LOG.debug("sriov_vfs_pci_address: %s" % sriov_vfs_pci_address) return sriov_vfs_pci_address def get_pci_sriov_vf_device_id(self, pciaddr, sriov_vfs_pci_address): vf_device_id = None for addr in sriov_vfs_pci_address: fdevice = '/sys/bus/pci/devices/' + addr + '/device' try: with open(fdevice, 'r') as f: # Device id is a 16 bit hex value. Strip off the hex # identifier and trailing whitespace vf_device_id = f.readline().rstrip()[2:] if len(vf_device_id) < 4: # Should never happen raise ValueError(_( "Device Id must be a 16 bit hex value")) LOG.debug("ATTR sriov_vf_device_id: %s " % vf_device_id) except Exception: LOG.debug("ATTR sriov_vf_device_id unknown for: %s " % pciaddr) pass # All VFs have the same device id per device. if vf_device_id: break return vf_device_id def get_lspci_output_by_addr(self, pciaddr): with open(os.devnull, "w") as fnull: output = subprocess.check_output( # pylint: disable=not-callable ['lspci', '-vmmks', pciaddr], stderr=fnull, universal_newlines=True) return output def get_pci_sriov_vf_driver_name(self, pciaddr, sriov_vfs_pci_address): vf_driver = None for addr in sriov_vfs_pci_address: try: output = self.get_lspci_output_by_addr(addr) except Exception as e: LOG.error("Error getting PCI data for SR-IOV " "VF address %s: %s", addr, e) continue for line in output.split('\n'): pci_attr = shlex.split(line.strip()) if (pci_attr and len(pci_attr) == 2 and 'Driver' in pci_attr[0]): vf_driver = pci_attr[1] break # All VFs have the same driver per device. if vf_driver: break return vf_driver def get_pci_sriov_vf_module_name(self, pciaddr, sriov_vfs_pci_address): vf_module = None for addr in sriov_vfs_pci_address: try: output = self.get_lspci_output_by_addr(addr) except Exception as e: LOG.error("Error getting PCI data for SR-IOV " "VF address %s: %s", addr, e) continue for line in output.split('\n'): pci_attr = shlex.split(line.strip()) if (pci_attr and len(pci_attr) == 2 and 'Module' in pci_attr[0]): vf_module = pci_attr[1] break # All VFs have the same module per device. if vf_module: break return vf_module def get_pci_driver_name(self, pciaddr): ddriver = '/sys/bus/pci/devices/' + pciaddr + '/driver/module/drivers' try: drivers = [ os.path.basename(os.readlink(ddriver + '/' + d)) for d in os.listdir(ddriver) ] driver = str(','.join(str(d) for d in drivers)) except Exception: LOG.debug("ATTR driver unknown for: %s " % pciaddr) driver = None pass LOG.debug("driver: %s" % driver) return driver def pci_devices_get(self, vendor=None, device=None): cmd = ["lspci", "-Dm"] # See if the caller wants to limit us to a specific vendor/device. if vendor and device: option = "-d " + vendor + ":" + device cmd.append(option) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True) pci_devices = [] for line in p.stdout: pci_device = shlex.split(line.strip()) pci_device = self.format_lspci_output(pci_device) if any(x in pci_device[pclass].lower() for x in IGNORE_PCI_CLASSES): continue dirpcidev = '/sys/bus/pci/devices/' physfn = dirpcidev + pci_device[pciaddr] + '/physfn' if not os.path.isdir(physfn): # Do not report VFs pci_devices.append(PCI(pci_device[pciaddr], pci_device[pclass], pci_device[pvendor], pci_device[pdevice], pci_device[prevision], pci_device[psvendor], pci_device[psdevice])) p.wait() return pci_devices def inics_get(self): p = subprocess.Popen(["lspci", "-Dmnn"], stdout=subprocess.PIPE, universal_newlines=True) pci_inics = [] for line in p.stdout: inic = shlex.split(line.strip()) if any(x in inic[pclass].lower() for x in ETHERNET_PCI_CLASSES): # hack for now if inic[prevision].strip() == inic[pvendor].strip(): # no revision info inic.append(inic[psvendor]) inic[psvendor] = inic[prevision] inic[prevision] = "0" elif len(inic) <= 6: # one less entry, no revision LOG.debug("update psdevice length=%s" % len(inic)) inic.append(inic[psvendor]) dirpcidev = '/sys/bus/pci/devices/' physfn = dirpcidev + inic[pciaddr] + '/physfn' if os.path.isdir(physfn): # Do not report VFs continue pci_inics.append(PCI(inic[pciaddr], inic[pclass], inic[pvendor], inic[pdevice], inic[prevision], inic[psvendor], inic[psdevice])) p.wait() return pci_inics def pci_get_enabled_attr(self, class_id, vendor_id, product_id): for known_device in KNOWN_PCI_DEVICES: if (class_id == known_device.get("class_id", None) or (vendor_id == known_device.get("vendor_id", None) and product_id == known_device.get("device_id", None))): return True return False def pci_get_device_attrs(self, pciaddr): ''' For this pciaddr, build a list of device attributes ''' pci_attrs_array = [] dirpcidev = '/sys/bus/pci/devices/' pciaddrs = os.listdir(dirpcidev) for a in pciaddrs: if ((a == pciaddr) or (a == ("0000:" + pciaddr))): LOG.debug("Found device pci bus: %s " % a) dirpcideva = dirpcidev + a numa_node = self.get_pci_numa_node(a) sriov_totalvfs = self.get_pci_sriov_totalvfs(a) sriov_numvfs = self.get_pci_sriov_numvfs(a) sriov_vfs_pci_address = self.get_pci_sriov_vfs_pci_address(a, sriov_numvfs) sriov_vf_driver = self.get_pci_sriov_vf_driver_name(a, sriov_vfs_pci_address) driver = self.get_pci_driver_name(a) fclass = dirpcideva + '/class' fvendor = dirpcideva + '/vendor' fdevice = dirpcideva + '/device' try: with open(fvendor, 'r') as f: pvendor_id = f.readline().strip('0x').strip() except Exception: LOG.debug("ATTR vendor unknown for: %s " % a) pvendor_id = None try: with open(fdevice, 'r') as f: pdevice_id = f.readline().replace('0x', '').strip() except Exception: LOG.debug("ATTR device unknown for: %s " % a) pdevice_id = None try: with open(fclass, 'r') as f: pclass_id = f.readline().replace('0x', '').strip() except Exception: LOG.debug("ATTR class unknown for: %s " % a) pclass_id = None name = "pci_" + a.replace(':', '_').replace('.', '_') sriov_vf_pdevice_id = self.get_pci_sriov_vf_device_id( a, sriov_vfs_pci_address) attrs = { "name": name, "pci_address": a, "pclass_id": pclass_id, "pvendor_id": pvendor_id, "pdevice_id": pdevice_id, "numa_node": numa_node, "sriov_totalvfs": sriov_totalvfs, "sriov_numvfs": sriov_numvfs, "sriov_vfs_pci_address": ','.join(str(x) for x in sriov_vfs_pci_address), "sriov_vf_driver": sriov_vf_driver, "sriov_vf_pdevice_id": sriov_vf_pdevice_id, "driver": driver, "enabled": self.pci_get_enabled_attr(pclass_id, pvendor_id, pdevice_id), } pci_attrs_array.append(attrs) return pci_attrs_array def get_pci_net_directory(self, pciaddr): device_directory = '/sys/bus/pci/devices/' + pciaddr # Look for the standard device 'net' directory net_directory = device_directory + '/net/' if os.path.exists(net_directory): return net_directory # Otherwise check whether this is a virtio based device net_pattern = device_directory + '/virtio*/net/' results = glob.glob(net_pattern) if not results: return None if len(results) > 1: LOG.warning("PCI device {} has multiple virtio " "sub-directories".format(pciaddr)) return results[0] def _read_flags(self, fflags): try: with open(fflags, 'r') as f: hex_str = f.readline().rstrip() flags = int(hex_str, 16) except Exception: flags = None return flags def _get_netdev_flags(self, dirpcinet, pci): fflags = dirpcinet + pci + '/flags' return self._read_flags(fflags) def pci_get_net_flags(self, name): fflags = '/sys/class/net/' + name + '/flags' return self._read_flags(fflags) def pci_get_net_names(self): '''build a list of network device names.''' names = [] for name in os.listdir('/sys/class/net/'): if os.path.isdir('/sys/class/net/' + name): names.append(name) return names def pci_get_net_attrs(self, pciaddr): ''' For this pciaddr, build a list of network attributes per port ''' pci_attrs_array = [] dirpcidev = '/sys/bus/pci/devices/' pciaddrs = os.listdir(dirpcidev) for a in pciaddrs: if ((a == pciaddr) or (a == ("0000:" + pciaddr))): # Look inside net expect to find address,speed,mtu etc. info # There may be more than 1 net device for this NIC. LOG.debug("Found NIC pci bus: %s " % a) dirpcideva = dirpcidev + a numa_node = self.get_pci_numa_node(a) sriov_totalvfs = self.get_pci_sriov_totalvfs(a) sriov_numvfs = self.get_pci_sriov_numvfs(a) sriov_vfs_pci_address = self.get_pci_sriov_vfs_pci_address(a, sriov_numvfs) # For network devices, return the supported kernel module # as the sriov_vf_driver. This is used to determine the driver # to use if an interface has an SR-IOV VF driver of 'netdevice' sriov_vf_driver = self.get_pci_sriov_vf_module_name(a, sriov_vfs_pci_address) sriov_vf_pdevice_id = self.get_pci_sriov_vf_device_id(a, sriov_vfs_pci_address) driver = self.get_pci_driver_name(a) # Determine DPDK support dpdksupport = False fvendor = dirpcideva + '/vendor' fdevice = dirpcideva + '/device' try: with open(fvendor, 'r') as f: vendor = f.readline().strip() except Exception: LOG.debug("ATTR vendor unknown for: %s " % a) vendor = None try: with open(fdevice, 'r') as f: device = f.readline().strip() except Exception: LOG.debug("ATTR device unknown for: %s " % a) device = None try: with open(os.devnull, "w") as fnull: subprocess.check_call(["query_pci_id", "-v " + str(vendor), # pylint: disable=not-callable "-d " + str(device)], stdout=fnull, stderr=fnull) dpdksupport = True LOG.debug("DPDK does support NIC " "(vendor: %s device: %s)", vendor, device) except subprocess.CalledProcessError as e: dpdksupport = False if e.returncode == 1: # NIC is not supprted LOG.debug("DPDK does not support NIC " "(vendor: %s device: %s)", vendor, device) else: # command failed, default to DPDK support to False LOG.info("Could not determine DPDK support for " "NIC (vendor %s device: %s), defaulting " "to False", vendor, device) # determine the net directory for this device dirpcinet = self.get_pci_net_directory(a) if dirpcinet is None: LOG.warning("no /net for PCI device: %s " % a) continue # go to next PCI device # determine which netdevs are associated to this device netdevs = os.listdir(dirpcinet) for n in netdevs: mac = None fmac = dirpcinet + n + '/' + "address" fmaster = dirpcinet + n + '/' + "master" # if a port is a member of a bond the port MAC address # must be retrieved from /proc/net/bonding/ if os.path.exists(fmaster): dirmaster = os.path.realpath(fmaster) master_name = os.path.basename(dirmaster) procnetbonding = '/proc/net/bonding/' + master_name found_interface = False try: with open(procnetbonding, 'r') as f: for line in f: if 'Slave Interface: ' + n in line: found_interface = True if found_interface and 'Permanent HW addr:' in line: mac = line.split(': ')[1].rstrip() mac = utils.validate_and_normalize_mac(mac) break if not mac: LOG.info("ATTR mac could not be determined " "for slave interface %s" % n) except Exception: LOG.info("ATTR mac could not be determined, " "could not open %s" % procnetbonding) else: try: with open(fmac, 'r') as f: mac = f.readline().rstrip() mac = utils.validate_and_normalize_mac(mac) except Exception: LOG.info("ATTR mac unknown for: %s " % n) fmtu = dirpcinet + n + '/' + "mtu" try: with open(fmtu, 'r') as f: mtu = f.readline().rstrip() except Exception: LOG.debug("ATTR mtu unknown for: %s " % n) mtu = None # Check the administrative state before reading the speed flags = self._get_netdev_flags(dirpcinet, n) # If administrative state is down, bring it up momentarily if not(flags & IFF_UP): LOG.warning("Enabling device %s to query link speed" % n) cmd = 'ip link set dev %s up' % n subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) # Read the speed fspeed = dirpcinet + n + '/' + "speed" try: with open(fspeed, 'r') as f: speed = f.readline().rstrip() if speed not in VALID_PORT_SPEED: LOG.error("Invalid port speed = %s for %s " % (speed, n)) speed = None except Exception: LOG.warning("ATTR speed unknown for: %s (flags: %s)" % (n, hex(flags))) speed = None # If the administrative state was down, take it back down if not(flags & IFF_UP): LOG.warning("Disabling device %s after querying link speed" % n) cmd = 'ip link set dev %s down' % n subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) flink_mode = dirpcinet + n + '/' + "link_mode" try: with open(flink_mode, 'r') as f: link_mode = f.readline().rstrip() except Exception: LOG.debug("ATTR link_mode unknown for: %s " % n) link_mode = None fdevport = dirpcinet + n + '/' + "dev_port" try: with open(fdevport, 'r') as f: dev_port = int(f.readline().rstrip(), 0) except Exception: LOG.debug("ATTR dev_port unknown for: %s " % n) # Kernel versions older than 3.15 used dev_id # (incorrectly) to identify the network devices, # therefore support the fallback if dev_port is not # available try: fdevid = dirpcinet + n + '/' + "dev_id" with open(fdevid, 'r') as f: dev_port = int(f.readline().rstrip(), 0) except Exception: LOG.debug("ATTR dev_id unknown for: %s " % n) dev_port = 0 attrs = { "name": n, "numa_node": numa_node, "sriov_totalvfs": sriov_totalvfs, "sriov_numvfs": sriov_numvfs, "sriov_vfs_pci_address": ','.join(str(x) for x in sriov_vfs_pci_address), "sriov_vf_driver": sriov_vf_driver, "sriov_vf_pdevice_id": sriov_vf_pdevice_id, "driver": driver, "pci_address": a, "mac": mac, "mtu": mtu, "speed": speed, "link_mode": link_mode, "dev_id": dev_port, "dpdksupport": dpdksupport } pci_attrs_array.append(attrs) return pci_attrs_array