FIX: af_xdp L2 data paths
[csit.git] / resources / libraries / python / InterfaceUtil.py
index 144a96c..481c122 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright (c) 2021 Cisco and/or its affiliates.
 # 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:
 # 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:
@@ -20,12 +20,13 @@ from ipaddress import ip_address
 from robot.api import logger
 
 from resources.libraries.python.Constants import Constants
 from robot.api import logger
 
 from resources.libraries.python.Constants import Constants
+from resources.libraries.python.CpuUtils import CpuUtils
 from resources.libraries.python.DUTSetup import DUTSetup
 from resources.libraries.python.IPAddress import IPAddress
 from resources.libraries.python.L2Util import L2Util
 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
 from resources.libraries.python.parsers.JsonParser import JsonParser
 from resources.libraries.python.DUTSetup import DUTSetup
 from resources.libraries.python.IPAddress import IPAddress
 from resources.libraries.python.L2Util import L2Util
 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
 from resources.libraries.python.parsers.JsonParser import JsonParser
-from resources.libraries.python.ssh import SSH, exec_cmd_no_error
+from resources.libraries.python.ssh import SSH, exec_cmd, exec_cmd_no_error
 from resources.libraries.python.topology import NodeType, Topology
 from resources.libraries.python.VPPUtil import VPPUtil
 
 from resources.libraries.python.topology import NodeType, Topology
 from resources.libraries.python.VPPUtil import VPPUtil
 
@@ -110,6 +111,13 @@ class RdmaMode(IntEnum):
     RDMA_API_MODE_DV = 2
 
 
     RDMA_API_MODE_DV = 2
 
 
+class AfXdpMode(IntEnum):
+    """AF_XDP interface mode."""
+    AF_XDP_API_MODE_AUTO = 0
+    AF_XDP_API_MODE_COPY = 1
+    AF_XDP_API_MODE_ZERO_COPY = 2
+
+
 class InterfaceUtil:
     """General utilities for managing interfaces"""
 
 class InterfaceUtil:
     """General utilities for managing interfaces"""
 
@@ -228,45 +236,97 @@ class InterfaceUtil:
             )
 
     @staticmethod
             )
 
     @staticmethod
-    def set_interface_ethernet_mtu(node, iface_key, mtu):
-        """Set Ethernet MTU for specified interface.
+    def set_interface_state_pci(
+            node, pf_pcis, namespace=None, state=u"up"):
+        """Set operational state for interface specified by PCI address.
+
+        :param node: Topology node.
+        :param pf_pcis: List of node's interfaces PCI addresses.
+        :param namespace: Exec command in namespace. (Optional, Default: none)
+        :param state: Up/Down. (Optional, default: up)
+        :type nodes: dict
+        :type pf_pcis: list
+        :type namespace: str
+        :type state: str
+        """
+        for pf_pci in pf_pcis:
+            pf_eth = InterfaceUtil.pci_to_eth(node, pf_pci)
+            InterfaceUtil.set_linux_interface_state(
+                node, pf_eth, namespace=namespace, state=state
+            )
 
 
-        Function can be used only for TGs.
+    @staticmethod
+    def set_interface_mtu(node, pf_pcis, mtu=9200):
+        """Set Ethernet MTU for specified interfaces.
 
 
-        :param node: Node where the interface is.
-        :param iface_key: Interface key from topology file.
-        :param mtu: MTU to set.
-        :type node: dict
-        :type iface_key: str
+        :param node: Topology node.
+        :param pf_pcis: List of node's interfaces PCI addresses.
+        :param mtu: MTU to set. Default: 9200.
+        :type nodes: dict
+        :type pf_pcis: list
         :type mtu: int
         :type mtu: int
-        :returns: Nothing.
-        :raises ValueError: If the node type is "DUT".
-        :raises ValueError: If the node has an unknown node type.
+        :raises RuntimeError: If failed to set MTU on interface.
         """
         """
-        if node[u"type"] == NodeType.DUT:
-            msg = f"Node {node[u'host']}: Setting Ethernet MTU for interface " \
-                f"on DUT nodes not supported"
-        elif node[u"type"] != NodeType.TG:
-            msg = f"Node {node[u'host']} has unknown NodeType: {node[u'type']}"
-        else:
-            iface_name = Topology.get_interface_name(node, iface_key)
-            cmd = f"ip link set {iface_name} mtu {mtu}"
+        for pf_pci in pf_pcis:
+            pf_eth = InterfaceUtil.pci_to_eth(node, pf_pci)
+            cmd = f"ip link set {pf_eth} mtu {mtu}"
             exec_cmd_no_error(node, cmd, sudo=True)
             exec_cmd_no_error(node, cmd, sudo=True)
-            return
-        raise ValueError(msg)
 
     @staticmethod
 
     @staticmethod
-    def set_default_ethernet_mtu_on_all_interfaces_on_node(node):
-        """Set default Ethernet MTU on all interfaces on node.
+    def set_interface_channels(
+            node, pf_pcis, num_queues=1, channel=u"combined"):
+        """Set interface channels for specified interfaces.
+
+        :param node: Topology node.
+        :param pf_pcis: List of node's interfaces PCI addresses.
+        :param num_queues: Number of channels. (Optional, Default: 1)
+        :param channel: Channel type. (Optional, Default: combined)
+        :type nodes: dict
+        :type pf_pcis: list
+        :type num_queues: int
+        :type channel: str
+        """
+        for pf_pci in pf_pcis:
+            pf_eth = InterfaceUtil.pci_to_eth(node, pf_pci)
+            cmd = f"ethtool --set-channels {pf_eth} {channel} {num_queues}"
+            exec_cmd_no_error(node, cmd, sudo=True)
 
 
-        Function can be used only for TGs.
+    @staticmethod
+    def set_interface_flow_control(node, pf_pcis, rxf=u"off", txf=u"off"):
+        """Set Ethernet flow control for specified interfaces.
 
 
-        :param node: Node where to set default MTU.
-        :type node: dict
-        :returns: Nothing.
+        :param node: Topology node.
+        :param pf_pcis: List of node's interfaces PCI addresses.
+        :param rxf: RX flow. (Optional, Default: off).
+        :param txf: TX flow. (Optional, Default: off).
+        :type nodes: dict
+        :type pf_pcis: list
+        :type rxf: str
+        :type txf: str
         """
         """
-        for ifc in node[u"interfaces"]:
-            InterfaceUtil.set_interface_ethernet_mtu(node, ifc, 1500)
+        for pf_pci in pf_pcis:
+            pf_eth = InterfaceUtil.pci_to_eth(node, pf_pci)
+            cmd = f"ethtool -A {pf_eth} rx {rxf} tx {txf}"
+            ret_code, _, _ = exec_cmd(node, cmd, sudo=True)
+            if int(ret_code) not in (0, 78):
+                raise RuntimeError("Failed to set flow control on {pf_eth}!")
+
+    @staticmethod
+    def set_pci_parameter(node, pf_pcis, key, value):
+        """Set PCI parameter for specified interfaces.
+
+        :param node: Topology node.
+        :param pf_pcis: List of node's interfaces PCI addresses.
+        :param key: Key to set.
+        :param value: Value to set.
+        :type nodes: dict
+        :type pf_pcis: list
+        :type key: str
+        :type value: str
+        """
+        for pf_pci in pf_pcis:
+            cmd = f"setpci -s {pf_pci} {key}={value}"
+            exec_cmd_no_error(node, cmd, sudo=True)
 
     @staticmethod
     def vpp_set_interface_mtu(node, interface, mtu=9200):
 
     @staticmethod
     def vpp_set_interface_mtu(node, interface, mtu=9200):
@@ -294,8 +354,7 @@ class InterfaceUtil:
             with PapiSocketExecutor(node) as papi_exec:
                 papi_exec.add(cmd, **args).get_reply(err_msg)
         except AssertionError as err:
             with PapiSocketExecutor(node) as papi_exec:
                 papi_exec.add(cmd, **args).get_reply(err_msg)
         except AssertionError as err:
-            # TODO: Make failure tolerance optional.
-            logger.debug(f"Setting MTU failed. Expected?\n{err}")
+            logger.debug(f"Setting MTU failed.\n{err}")
 
     @staticmethod
     def vpp_set_interfaces_mtu_on_node(node, mtu=9200):
 
     @staticmethod
     def vpp_set_interfaces_mtu_on_node(node, mtu=9200):
@@ -1015,9 +1074,11 @@ class InterfaceUtil:
         :raises RuntimeError: If it is not possible to create loopback on the
             node.
         """
         :raises RuntimeError: If it is not possible to create loopback on the
             node.
         """
-        cmd = u"create_loopback"
+        cmd = u"create_loopback_instance"
         args = dict(
         args = dict(
-            mac_address=L2Util.mac_to_bin(mac) if mac else 0
+            mac_address=L2Util.mac_to_bin(mac) if mac else 0,
+            is_specified=False,
+            user_instance=0,
         )
         err_msg = f"Failed to create loopback interface on host {node[u'host']}"
         with PapiSocketExecutor(node) as papi_exec:
         )
         err_msg = f"Failed to create loopback interface on host {node[u'host']}"
         with PapiSocketExecutor(node) as papi_exec:
@@ -1034,24 +1095,28 @@ class InterfaceUtil:
         return sw_if_index
 
     @staticmethod
         return sw_if_index
 
     @staticmethod
-    def vpp_create_bond_interface(node, mode, load_balance=None, mac=None):
+    def vpp_create_bond_interface(
+            node, mode, load_balance=None, mac=None, gso=False):
         """Create bond interface on VPP node.
 
         :param node: DUT node from topology.
         :param mode: Link bonding mode.
         :param load_balance: Load balance (optional, valid for xor and lacp
         """Create bond interface on VPP node.
 
         :param node: DUT node from topology.
         :param mode: Link bonding mode.
         :param load_balance: Load balance (optional, valid for xor and lacp
-            modes, otherwise ignored).
+            modes, otherwise ignored). Default: None.
         :param mac: MAC address to assign to the bond interface (optional).
         :param mac: MAC address to assign to the bond interface (optional).
+            Default: None.
+        :param gso: Enable GSO support (optional). Default: False.
         :type node: dict
         :type mode: str
         :type load_balance: str
         :type mac: str
         :type node: dict
         :type mode: str
         :type load_balance: str
         :type mac: str
+        :type gso: bool
         :returns: Interface key (name) in topology.
         :rtype: str
         :raises RuntimeError: If it is not possible to create bond interface on
             the node.
         """
         :returns: Interface key (name) in topology.
         :rtype: str
         :raises RuntimeError: If it is not possible to create bond interface on
             the node.
         """
-        cmd = u"bond_create"
+        cmd = u"bond_create2"
         args = dict(
             id=int(Constants.BITWISE_NON_ZERO),
             use_custom_mac=bool(mac is not None),
         args = dict(
             id=int(Constants.BITWISE_NON_ZERO),
             use_custom_mac=bool(mac is not None),
@@ -1064,7 +1129,8 @@ class InterfaceUtil:
                 LinkBondLoadBalanceAlgo,
                 f"BOND_API_LB_ALGO_{load_balance.upper()}"
             ).value,
                 LinkBondLoadBalanceAlgo,
                 f"BOND_API_LB_ALGO_{load_balance.upper()}"
             ).value,
-            numa_only=False
+            numa_only=False,
+            enable_gso=gso
         )
         err_msg = f"Failed to create bond interface on host {node[u'host']}"
         with PapiSocketExecutor(node) as papi_exec:
         )
         err_msg = f"Failed to create bond interface on host {node[u'host']}"
         with PapiSocketExecutor(node) as papi_exec:
@@ -1160,6 +1226,58 @@ class InterfaceUtil:
 
         return Topology.get_interface_by_sw_index(node, sw_if_index)
 
 
         return Topology.get_interface_by_sw_index(node, sw_if_index)
 
+    @staticmethod
+    def vpp_create_af_xdp_interface(
+            node, if_key, num_rx_queues=None, rxq_size=0, txq_size=0,
+            mode=u"auto"):
+        """Create AF_XDP interface on VPP node.
+
+        :param node: DUT node from topology.
+        :param if_key: Physical interface key from topology file of interface
+            to be bound to compatible driver.
+        :param num_rx_queues: Number of RX queues. (Optional, Default: none)
+        :param rxq_size: Size of RXQ (0 = Default API; 512 = Default VPP).
+        :param txq_size: Size of TXQ (0 = Default API; 512 = Default VPP).
+        :param mode: AF_XDP interface mode. (Optional, Default: auto).
+        :type node: dict
+        :type if_key: str
+        :type num_rx_queues: int
+        :type rxq_size: int
+        :type txq_size: int
+        :type mode: str
+        :returns: Interface key (name) in topology file.
+        :rtype: str
+        :raises RuntimeError: If it is not possible to create AF_XDP interface
+            on the node.
+        """
+        PapiSocketExecutor.run_cli_cmd(
+            node, u"set logging class af_xdp level debug"
+        )
+
+        cmd = u"af_xdp_create"
+        pci_addr = Topology.get_interface_pci_addr(node, if_key)
+        args = dict(
+            name=InterfaceUtil.pci_to_eth(node, pci_addr),
+            host_if=InterfaceUtil.pci_to_eth(node, pci_addr),
+            rxq_num=int(num_rx_queues) if num_rx_queues else 0,
+            rxq_size=rxq_size,
+            txq_size=txq_size,
+            mode=getattr(AfXdpMode, f"AF_XDP_API_MODE_{mode.upper()}").value
+        )
+        err_msg = f"Failed to create AF_XDP interface on host {node[u'host']}"
+        with PapiSocketExecutor(node) as papi_exec:
+            sw_if_index = papi_exec.add(cmd, **args).get_sw_if_index(err_msg)
+
+        InterfaceUtil.vpp_set_interface_mac(
+            node, sw_if_index, Topology.get_interface_mac(node, if_key)
+        )
+        InterfaceUtil.add_eth_interface(
+            node, sw_if_index=sw_if_index, ifc_pfx=u"eth_af_xdp",
+            host_if_key=if_key
+        )
+
+        return Topology.get_interface_by_sw_index(node, sw_if_index)
+
     @staticmethod
     def vpp_create_rdma_interface(
             node, if_key, num_rx_queues=None, rxq_size=0, txq_size=0,
     @staticmethod
     def vpp_create_rdma_interface(
             node, if_key, num_rx_queues=None, rxq_size=0, txq_size=0,
@@ -1184,7 +1302,11 @@ class InterfaceUtil:
         :raises RuntimeError: If it is not possible to create RDMA interface on
             the node.
         """
         :raises RuntimeError: If it is not possible to create RDMA interface on
             the node.
         """
-        cmd = u"rdma_create"
+        PapiSocketExecutor.run_cli_cmd(
+            node, u"set logging class rdma level debug"
+        )
+
+        cmd = u"rdma_create_v2"
         pci_addr = Topology.get_interface_pci_addr(node, if_key)
         args = dict(
             name=InterfaceUtil.pci_to_eth(node, pci_addr),
         pci_addr = Topology.get_interface_pci_addr(node, if_key)
         args = dict(
             name=InterfaceUtil.pci_to_eth(node, pci_addr),
@@ -1193,6 +1315,9 @@ class InterfaceUtil:
             rxq_size=rxq_size,
             txq_size=txq_size,
             mode=getattr(RdmaMode, f"RDMA_API_MODE_{mode.upper()}").value,
             rxq_size=rxq_size,
             txq_size=txq_size,
             mode=getattr(RdmaMode, f"RDMA_API_MODE_{mode.upper()}").value,
+            # Note: Set True for non-jumbo packets.
+            no_multi_seg=False,
+            max_pktlen=0,
         )
         err_msg = f"Failed to create RDMA interface on host {node[u'host']}"
         with PapiSocketExecutor(node) as papi_exec:
         )
         err_msg = f"Failed to create RDMA interface on host {node[u'host']}"
         with PapiSocketExecutor(node) as papi_exec:
@@ -1209,8 +1334,8 @@ class InterfaceUtil:
         return Topology.get_interface_by_sw_index(node, sw_if_index)
 
     @staticmethod
         return Topology.get_interface_by_sw_index(node, sw_if_index)
 
     @staticmethod
-    def vpp_enslave_physical_interface(node, interface, bond_if):
-        """Enslave physical interface to bond interface on VPP node.
+    def vpp_add_bond_member(node, interface, bond_if):
+        """Add member interface to bond interface on VPP node.
 
         :param node: DUT node from topology.
         :param interface: Physical interface key from topology file.
 
         :param node: DUT node from topology.
         :param interface: Physical interface key from topology file.
@@ -1218,18 +1343,18 @@ class InterfaceUtil:
         :type node: dict
         :type interface: str
         :type bond_if: str
         :type node: dict
         :type interface: str
         :type bond_if: str
-        :raises RuntimeError: If it is not possible to enslave physical
-            interface to bond interface on the node.
+        :raises RuntimeError: If it is not possible to add member to bond
+            interface on the node.
         """
         """
-        cmd = u"bond_enslave"
+        cmd = u"bond_add_member"
         args = dict(
             sw_if_index=Topology.get_interface_sw_index(node, interface),
             bond_sw_if_index=Topology.get_interface_sw_index(node, bond_if),
             is_passive=False,
             is_long_timeout=False
         )
         args = dict(
             sw_if_index=Topology.get_interface_sw_index(node, interface),
             bond_sw_if_index=Topology.get_interface_sw_index(node, bond_if),
             is_passive=False,
             is_long_timeout=False
         )
-        err_msg = f"Failed to enslave physical interface {interface} to bond " \
-            f"interface {bond_if} on host {node[u'host']}"
+        err_msg = f"Failed to add member {interface} to bond interface " \
+            f"{bond_if} on host {node[u'host']}"
         with PapiSocketExecutor(node) as papi_exec:
             papi_exec.add(cmd, **args).get_reply(err_msg)
 
         with PapiSocketExecutor(node) as papi_exec:
             papi_exec.add(cmd, **args).get_reply(err_msg)
 
@@ -1242,7 +1367,7 @@ class InterfaceUtil:
         :type node: dict
         :type verbose: bool
         """
         :type node: dict
         :type verbose: bool
         """
-        cmd = u"sw_interface_bond_dump"
+        cmd = u"sw_bond_interface_dump"
         err_msg = f"Failed to get bond interface dump on host {node[u'host']}"
 
         data = f"Bond data on node {node[u'host']}:\n"
         err_msg = f"Failed to get bond interface dump on host {node[u'host']}"
 
         data = f"Bond data on node {node[u'host']}:\n"
@@ -1257,26 +1382,26 @@ class InterfaceUtil:
             data += u"  load balance: {lb}\n".format(
                 lb=bond[u"lb"].name.replace(u"BOND_API_LB_ALGO_", u"").lower()
             )
             data += u"  load balance: {lb}\n".format(
                 lb=bond[u"lb"].name.replace(u"BOND_API_LB_ALGO_", u"").lower()
             )
-            data += f"  number of active slaves: {bond[u'active_slaves']}\n"
+            data += f"  number of active members: {bond[u'active_members']}\n"
             if verbose:
             if verbose:
-                slave_data = InterfaceUtil.vpp_bond_slave_dump(
+                member_data = InterfaceUtil.vpp_bond_member_dump(
                     node, Topology.get_interface_by_sw_index(
                         node, bond[u"sw_if_index"]
                     )
                 )
                     node, Topology.get_interface_by_sw_index(
                         node, bond[u"sw_if_index"]
                     )
                 )
-                for slave in slave_data:
-                    if not slave[u"is_passive"]:
-                        data += f"    {slave[u'interface_name']}\n"
-            data += f"  number of slaves: {bond[u'slaves']}\n"
+                for member in member_data:
+                    if not member[u"is_passive"]:
+                        data += f"    {member[u'interface_name']}\n"
+            data += f"  number of members: {bond[u'members']}\n"
             if verbose:
             if verbose:
-                for slave in slave_data:
-                    data += f"    {slave[u'interface_name']}\n"
+                for member in member_data:
+                    data += f"    {member[u'interface_name']}\n"
             data += f"  interface id: {bond[u'id']}\n"
             data += f"  sw_if_index: {bond[u'sw_if_index']}\n"
         logger.info(data)
 
     @staticmethod
             data += f"  interface id: {bond[u'id']}\n"
             data += f"  sw_if_index: {bond[u'sw_if_index']}\n"
         logger.info(data)
 
     @staticmethod
-    def vpp_bond_slave_dump(node, interface):
+    def vpp_bond_member_dump(node, interface):
         """Get bond interface slave(s) data on VPP node.
 
         :param node: DUT node from topology.
         """Get bond interface slave(s) data on VPP node.
 
         :param node: DUT node from topology.
@@ -1286,7 +1411,7 @@ class InterfaceUtil:
         :returns: Bond slave interface data.
         :rtype: dict
         """
         :returns: Bond slave interface data.
         :rtype: dict
         """
-        cmd = u"sw_interface_slave_dump"
+        cmd = u"sw_member_interface_dump"
         args = dict(
             sw_if_index=Topology.get_interface_sw_index(node, interface)
         )
         args = dict(
             sw_if_index=Topology.get_interface_sw_index(node, interface)
         )
@@ -1295,7 +1420,7 @@ class InterfaceUtil:
         with PapiSocketExecutor(node) as papi_exec:
             details = papi_exec.add(cmd, **args).get_details(err_msg)
 
         with PapiSocketExecutor(node) as papi_exec:
             details = papi_exec.add(cmd, **args).get_details(err_msg)
 
-        logger.debug(f"Slave data:\n{details}")
+        logger.debug(f"Member data:\n{details}")
         return details
 
     @staticmethod
         return details
 
     @staticmethod
@@ -1487,6 +1612,29 @@ class InterfaceUtil:
         cmd = f"{ns_str} ip link set {interface} {mac_str}"
         exec_cmd_no_error(node, cmd, sudo=True)
 
         cmd = f"{ns_str} ip link set {interface} {mac_str}"
         exec_cmd_no_error(node, cmd, sudo=True)
 
+    @staticmethod
+    def set_linux_interface_promisc(
+            node, interface, namespace=None, vf_id=None, state=u"on"):
+        """Set promisc state for interface in linux.
+
+        :param node: Node where to execute command.
+        :param interface: Interface in namespace.
+        :param namespace: Exec command in namespace. (Optional, Default: None)
+        :param vf_id: Virtual Function id. (Optional, Default: None)
+        :param state: State of feature. (Optional, Default: on)
+        :type node: dict
+        :type interface: str
+        :type namespace: str
+        :type vf_id: int
+        :type state: str
+        """
+        promisc_str = f"vf {vf_id} promisc {state}" if vf_id is not None \
+            else f"promisc {state}"
+        ns_str = f"ip netns exec {namespace}" if namespace else u""
+
+        cmd = f"{ns_str} ip link set dev {interface} {promisc_str}"
+        exec_cmd_no_error(node, cmd, sudo=True)
+
     @staticmethod
     def set_linux_interface_trust_on(
             node, interface, namespace=None, vf_id=None):
     @staticmethod
     def set_linux_interface_trust_on(
             node, interface, namespace=None, vf_id=None):
@@ -1529,9 +1677,71 @@ class InterfaceUtil:
         exec_cmd_no_error(node, cmd, sudo=True)
 
     @staticmethod
         exec_cmd_no_error(node, cmd, sudo=True)
 
     @staticmethod
-    def init_avf_interface(node, ifc_key, numvfs=1, osi_layer=u"L2"):
-        """Init PCI device by creating VIFs and bind them to vfio-pci for AVF
-        driver testing on DUT.
+    def set_linux_interface_state(
+            node, interface, namespace=None, state=u"up"):
+        """Set operational state for interface in linux.
+
+        :param node: Node where to execute command.
+        :param interface: Interface in namespace.
+        :param namespace: Execute command in namespace. Optional
+        :param state: Up/Down.
+        :type node: dict
+        :type interface: str
+        :type namespace: str
+        :type state: str
+        """
+        ns_str = f"ip netns exec {namespace}" if namespace else u""
+
+        cmd = f"{ns_str} ip link set dev {interface} {state}"
+        exec_cmd_no_error(node, cmd, sudo=True)
+
+    @staticmethod
+    def init_interface(node, ifc_key, driver, numvfs=0, osi_layer=u"L2"):
+        """Init PCI device. Check driver compatibility and bind to proper
+        drivers. Optionally create NIC VFs.
+
+        :param node: DUT node.
+        :param ifc_key: Interface key from topology file.
+        :param driver: Base driver to use.
+        :param numvfs: Number of VIFs to initialize, 0 - disable the VIFs.
+        :param osi_layer: OSI Layer type to initialize TG with.
+            Default value "L2" sets linux interface spoof off.
+        :type node: dict
+        :type ifc_key: str
+        :type driver: str
+        :type numvfs: int
+        :type osi_layer: str
+        :returns: Virtual Function topology interface keys.
+        :rtype: list
+        :raises RuntimeError: If a reason preventing initialization is found.
+        """
+        kernel_driver = Topology.get_interface_driver(node, ifc_key)
+        vf_keys = []
+        if driver == u"avf":
+            if kernel_driver not in (
+                    u"ice", u"iavf", u"i40e", u"i40evf"):
+                raise RuntimeError(
+                    f"AVF needs ice or i40e compatible driver, not "
+                    f"{kernel_driver} at node {node[u'host']} ifc {ifc_key}"
+                )
+            vf_keys = InterfaceUtil.init_generic_interface(
+                node, ifc_key, numvfs=numvfs, osi_layer=osi_layer
+            )
+        elif driver == u"af_xdp":
+            if kernel_driver not in (
+                    u"ice", u"iavf", u"i40e", u"i40evf", u"mlx5_core"):
+                raise RuntimeError(
+                    f"AF_XDP needs ice or i40e or rdma compatible driver, not "
+                    f"{kernel_driver} at node {node[u'host']} ifc {ifc_key}"
+                )
+            vf_keys = InterfaceUtil.init_generic_interface(
+                node, ifc_key, numvfs=numvfs, osi_layer=osi_layer
+            )
+        return vf_keys
+
+    @staticmethod
+    def init_generic_interface(node, ifc_key, numvfs=0, osi_layer=u"L2"):
+        """Init PCI device. Bind to proper drivers. Optionally create NIC VFs.
 
         :param node: DUT node.
         :param ifc_key: Interface key from topology file.
 
         :param node: DUT node.
         :param ifc_key: Interface key from topology file.
@@ -1551,13 +1761,9 @@ class InterfaceUtil:
         pf_mac_addr = Topology.get_interface_mac(node, ifc_key).split(":")
         uio_driver = Topology.get_uio_driver(node)
         kernel_driver = Topology.get_interface_driver(node, ifc_key)
         pf_mac_addr = Topology.get_interface_mac(node, ifc_key).split(":")
         uio_driver = Topology.get_uio_driver(node)
         kernel_driver = Topology.get_interface_driver(node, ifc_key)
-        if kernel_driver not in (u"i40e", u"i40evf"):
-            raise RuntimeError(
-                f"AVF needs i40e-compatible driver, not {kernel_driver} "
-                f"at node {node[u'host']} ifc {ifc_key}"
-            )
         current_driver = DUTSetup.get_pci_dev_driver(
             node, pf_pci_addr.replace(u":", r"\:"))
         current_driver = DUTSetup.get_pci_dev_driver(
             node, pf_pci_addr.replace(u":", r"\:"))
+        pf_dev = f"`basename /sys/bus/pci/devices/{pf_pci_addr}/net/*`"
 
         VPPUtil.stop_vpp_service(node)
         if current_driver != kernel_driver:
 
         VPPUtil.stop_vpp_service(node)
         if current_driver != kernel_driver:
@@ -1572,6 +1778,10 @@ class InterfaceUtil:
         # Initialize PCI VFs.
         DUTSetup.set_sriov_numvfs(node, pf_pci_addr, numvfs)
 
         # Initialize PCI VFs.
         DUTSetup.set_sriov_numvfs(node, pf_pci_addr, numvfs)
 
+        if not numvfs:
+            if osi_layer == u"L2":
+                InterfaceUtil.set_linux_interface_promisc(node, pf_dev)
+
         vf_ifc_keys = []
         # Set MAC address and bind each virtual function to uio driver.
         for vf_id in range(numvfs):
         vf_ifc_keys = []
         # Set MAC address and bind each virtual function to uio driver.
         for vf_id in range(numvfs):
@@ -1581,7 +1791,6 @@ class InterfaceUtil:
                  ]
             )
 
                  ]
             )
 
-            pf_dev = f"`basename /sys/bus/pci/devices/{pf_pci_addr}/net/*`"
             InterfaceUtil.set_linux_interface_trust_on(
                 node, pf_dev, vf_id=vf_id
             )
             InterfaceUtil.set_linux_interface_trust_on(
                 node, pf_dev, vf_id=vf_id
             )
@@ -1592,6 +1801,9 @@ class InterfaceUtil:
             InterfaceUtil.set_linux_interface_mac(
                 node, pf_dev, vf_mac_addr, vf_id=vf_id
             )
             InterfaceUtil.set_linux_interface_mac(
                 node, pf_dev, vf_mac_addr, vf_id=vf_id
             )
+            InterfaceUtil.set_linux_interface_state(
+                node, pf_dev, state=u"up"
+            )
 
             DUTSetup.pci_vf_driver_unbind(node, pf_pci_addr, vf_id)
             DUTSetup.pci_vf_driver_bind(node, pf_pci_addr, vf_id, uio_driver)
 
             DUTSetup.pci_vf_driver_unbind(node, pf_pci_addr, vf_id)
             DUTSetup.pci_vf_driver_bind(node, pf_pci_addr, vf_id, uio_driver)
@@ -1632,6 +1844,19 @@ class InterfaceUtil:
             details = papi_exec.get_details(err_msg)
         return sorted(details, key=lambda k: k[u"sw_if_index"])
 
             details = papi_exec.get_details(err_msg)
         return sorted(details, key=lambda k: k[u"sw_if_index"])
 
+    @staticmethod
+    def vpp_sw_interface_rx_placement_dump_on_all_duts(nodes):
+        """Dump VPP interface RX placement on all given nodes.
+
+        :param nodes: Nodes to run command on.
+        :type nodes: dict
+        :returns: Thread mapping information as a list of dictionaries.
+        :rtype: list
+        """
+        for node in nodes.values():
+            if node[u"type"] == NodeType.DUT:
+                InterfaceUtil.vpp_sw_interface_rx_placement_dump(node)
+
     @staticmethod
     def vpp_sw_interface_set_rx_placement(
             node, sw_if_index, queue_id, worker_id):
     @staticmethod
     def vpp_sw_interface_set_rx_placement(
             node, sw_if_index, queue_id, worker_id):
@@ -1661,17 +1886,28 @@ class InterfaceUtil:
             papi_exec.add(cmd, **args).get_reply(err_msg)
 
     @staticmethod
             papi_exec.add(cmd, **args).get_reply(err_msg)
 
     @staticmethod
-    def vpp_round_robin_rx_placement(node, prefix):
+    def vpp_round_robin_rx_placement(
+            node, prefix, dp_worker_limit=None):
         """Set Round Robin interface RX placement on all worker threads
         on node.
 
         """Set Round Robin interface RX placement on all worker threads
         on node.
 
+        If specified, dp_core_limit limits the number of physical cores used
+        for data plane I/O work. Other cores are presumed to do something else,
+        e.g. asynchronous crypto processing.
+        None means all workers are used for data plane work.
+        Note this keyword specifies workers, not cores.
+
         :param node: Topology nodes.
         :param prefix: Interface name prefix.
         :param node: Topology nodes.
         :param prefix: Interface name prefix.
+        :param dp_worker_limit: How many cores for data plane work.
         :type node: dict
         :type prefix: str
         :type node: dict
         :type prefix: str
+        :type dp_worker_limit: Optional[int]
         """
         worker_id = 0
         worker_cnt = len(VPPUtil.vpp_show_threads(node)) - 1
         """
         worker_id = 0
         worker_cnt = len(VPPUtil.vpp_show_threads(node)) - 1
+        if dp_worker_limit is not None:
+            worker_cnt = min(worker_cnt, dp_worker_limit)
         if not worker_cnt:
             return
         for placement in InterfaceUtil.vpp_sw_interface_rx_placement_dump(node):
         if not worker_cnt:
             return
         for placement in InterfaceUtil.vpp_sw_interface_rx_placement_dump(node):
@@ -1685,15 +1921,30 @@ class InterfaceUtil:
                     worker_id += 1
 
     @staticmethod
                     worker_id += 1
 
     @staticmethod
-    def vpp_round_robin_rx_placement_on_all_duts(nodes, prefix):
+    def vpp_round_robin_rx_placement_on_all_duts(
+            nodes, prefix, dp_core_limit=None):
         """Set Round Robin interface RX placement on all worker threads
         on all DUTs.
 
         """Set Round Robin interface RX placement on all worker threads
         on all DUTs.
 
+        If specified, dp_core_limit limits the number of physical cores used
+        for data plane I/O work. Other cores are presumed to do something else,
+        e.g. asynchronous crypto processing.
+        None means all cores are used for data plane work.
+        Note this keyword specifies cores, not workers.
+
         :param nodes: Topology nodes.
         :param prefix: Interface name prefix.
         :param nodes: Topology nodes.
         :param prefix: Interface name prefix.
+        :param dp_worker_limit: How many cores for data plane work.
         :type nodes: dict
         :type prefix: str
         :type nodes: dict
         :type prefix: str
+        :type dp_worker_limit: Optional[int]
         """
         for node in nodes.values():
             if node[u"type"] == NodeType.DUT:
         """
         for node in nodes.values():
             if node[u"type"] == NodeType.DUT:
-                InterfaceUtil.vpp_round_robin_rx_placement(node, prefix)
+                dp_worker_limit = CpuUtils.worker_count_from_cores_and_smt(
+                    phy_cores=dp_core_limit,
+                    smt_used=CpuUtils.is_smt_enabled(node[u"cpuinfo"]),
+                )
+                InterfaceUtil.vpp_round_robin_rx_placement(
+                    node, prefix, dp_worker_limit
+                )