---
 dts/framework/remote_session/testpmd_shell.py | 342 +++++++++++++++++-
 dts/tests/TestSuite_rxtx_offload.py           | 153 ++++++++
 2 files changed, 493 insertions(+), 2 deletions(-)
 create mode 100644 dts/tests/TestSuite_rxtx_offload.py

diff --git a/dts/framework/remote_session/testpmd_shell.py 
b/dts/framework/remote_session/testpmd_shell.py
index ad8cb273dc..2867fbce7a 100644
--- a/dts/framework/remote_session/testpmd_shell.py
+++ b/dts/framework/remote_session/testpmd_shell.py
@@ -19,7 +19,7 @@
 import time
 from collections.abc import Callable, MutableSet
 from dataclasses import dataclass, field
-from enum import Flag, auto
+from enum import Enum, Flag, auto
 from os import environ
 from pathlib import PurePath
 from typing import TYPE_CHECKING, Any, ClassVar, Concatenate, Literal, 
ParamSpec, Tuple, TypeAlias
@@ -344,6 +344,13 @@ def make_parser(cls) -> ParserFn:
         )
 
 
+class RxTxArgFlag(Enum):
+    """Enum representing recieving or transmitting ports."""
+
+    TX = "tx"
+    RX = "rx"
+
+
 class DeviceCapabilitiesFlag(Flag):
     """Flag representing the device capabilities."""
 
@@ -1278,6 +1285,81 @@ class TestPmdVerbosePacket(TextParser):
     )
 
 
+class TxOffloadCapability(Flag):
+    """TX offload capabilities of a device.
+
+    The flags are taken from ``lib/ethdev/rte_ethdev.h``.
+    They're prefixed with ``RTE_ETH_TX_OFFLOAD`` in ``lib/ethdev/rte_ethdev.h``
+    instead of ``TX_OFFLOAD``, which is what testpmd changes the prefix to.
+    The values are not contiguous, so the correspondence is preserved
+    by specifying concrete values interspersed between auto() values.
+
+    The ``TX_OFFLOAD`` prefix has been preserved so that the same flag names 
can be used
+    in :class:`NicCapability`. The prefix is needed in :class:`NicCapability` 
since there's
+    no other qualifier which would sufficiently distinguish it from other 
capabilities.
+
+    References:
+        DPDK lib: ``lib/ethdev/rte_ethdev.h``
+        testpmd display function: 
``app/test-pmd/cmdline.c:print_rx_offloads()``
+    """
+
+    TX_OFFLOAD_VLAN_INSERT = auto()
+    TX_OFFLOAD_IPV4_CKSUM = auto()
+    TX_OFFLOAD_UDP_CKSUM = auto()
+    TX_OFFLOAD_TCP_CKSUM = auto()
+    TX_OFFLOAD_SCTP_CKSUM = auto()
+    TX_OFFLOAD_TCP_TSO = auto()
+    TX_OFFLOAD_UDP_TSO = auto()
+    TX_OFFLOAD_OUTER_IPV4_CKSUM = auto()
+    TX_OFFLOAD_QINQ_INSERT = auto()
+    TX_OFFLOAD_VXLAN_TNL_TSO = auto()
+    TX_OFFLOAD_GRE_TNL_TSO = auto()
+    TX_OFFLOAD_IPIP_TNL_TSO = auto()
+    TX_OFFLOAD_GENEVE_TNL_TSO = auto()
+    TX_OFFLOAD_MACSEC_INSERT = auto()
+    TX_OFFLOAD_MT_LOCKFREE = auto()
+    TX_OFFLOAD_MULTI_SEGS = auto()
+    TX_OFFLOAD_MBUF_FAST_FREE = auto()
+    TX_OFFLOAD_SECURITY = auto()
+    TX_OFFLOAD_UDP_TNL_TSO = auto()
+    TX_OFFLOAD_IP_TNL_TSO = auto()
+    TX_OFFLOAD_OUTER_UDP_CKSUM = auto()
+    TX_OFFLOAD_SEND_ON_TIMESTAMP = auto()
+
+    @classmethod
+    def from_string(cls, line: str) -> Self:
+        """Make an instance from a string containing the flag names separated 
with a space.
+
+        Args:
+            line: The line to parse.
+
+        Returns:
+            A new instance containing all found flags.
+        """
+        flag = cls(0)
+        for flag_name in line.split():
+            flag |= cls[f"TX_OFFLOAD_{flag_name}"]
+        return flag
+
+    @classmethod
+    def make_parser(cls, per_port: bool) -> ParserFn:
+        """Make a parser function.
+
+        Args:
+            per_port: If :data:`True`, will return capabilities per port. If 
:data:`False`,
+                will return capabilities per queue.
+
+        Returns:
+            ParserFn: A dictionary for the `dataclasses.field` metadata 
argument containing a
+                parser function that makes an instance of this flag from text.
+        """
+        granularity = "Port" if per_port else "Queue"
+        return TextParser.wrap(
+            TextParser.find(rf"Per {granularity}\s+:(.*)$", re.MULTILINE),
+            cls.from_string,
+        )
+
+
 class RxOffloadCapability(Flag):
     """Rx offload capabilities of a device.
 
@@ -1376,6 +1458,24 @@ def make_parser(cls, per_port: bool) -> ParserFn:
         )
 
 
+@dataclass
+class TxOffloadCapabilities(TextParser):
+    """The result of testpmd's ``show port <port_id> tx_offload capabilities`` 
command.
+
+    References:
+        testpmd command function: 
``app/test-pmd/cmdline.c:cmd_tx_offload_get_capa()``
+        testpmd display function: 
``app/test-pmd/cmdline.c:cmd_tx_offload_get_capa_parsed()``
+    """
+
+    port_id: int = field(
+        metadata=TextParser.find_int(r"Tx Offloading Capabilities of port 
(\d+) :")
+    )
+    #: Per-queue Tx offload capabilities.
+    per_queue: TxOffloadCapability = 
field(metadata=TxOffloadCapability.make_parser(False))
+    #: Capabilities other than per-queue Tx offload capabilities.
+    per_port: TxOffloadCapability = 
field(metadata=TxOffloadCapability.make_parser(True))
+
+
 @dataclass
 class RxOffloadCapabilities(TextParser):
     """The result of testpmd's ``show port <port_id> rx_offload capabilities`` 
command.
@@ -2390,6 +2490,28 @@ def close(self) -> None:
     ====== Capability retrieval methods ======
     """
 
+    def get_capabilities_tx_offload(
+        self,
+        supported_capabilities: MutableSet["NicCapability"],
+        unsupported_capabilities: MutableSet["NicCapability"],
+    ) -> None:
+        """Get all TX offload capabilities and divide them into supported and 
unsupported.
+
+        Args:
+            supported_capabilities: Supported capabilities will be added to 
this set.
+            unsupported_capabilities: Unsupported capabilities will be added 
to this set.
+        """
+        self._logger.debug("Getting TX offload capabilities.")
+        command = f"show port {self.ports[0].id} tx_offload capabilities"
+        tx_offload_capabilities_out = self.send_command(command)
+        tx_offload_capabilities = 
TxOffloadCapabilities.parse(tx_offload_capabilities_out)
+        self._update_capabilities_from_flag(
+            supported_capabilities,
+            unsupported_capabilities,
+            TxOffloadCapability,
+            tx_offload_capabilities.per_port | 
tx_offload_capabilities.per_queue,
+        )
+
     def get_capabilities_rx_offload(
         self,
         supported_capabilities: MutableSet["NicCapability"],
@@ -2672,6 +2794,134 @@ def get_capabilities_physical_function(
         else:
             unsupported_capabilities.add(NicCapability.PHYSICAL_FUNCTION)
 
+    @requires_started_ports
+    def get_rxtx_offload_config(
+        self,
+        rxtx: RxTxArgFlag,
+        verify: bool,
+        port_id: int = 0,
+        num_queues: int = 0,
+    ) -> dict[int | str, str]:
+        """Get the RX or TX offload configuration of the queues from the given 
port.
+
+        Args:
+            rxtx: Whether to get the RX or TX configuration of the given 
queues.
+            verify: If :data:'True' the output of the command will be scanned 
in an attempt to
+                verify that the offload configuration was retrieved 
succesfully on all queues.
+            num_queues: The number of queues to get the offload configuration 
for.
+            port_id: The port ID that contains the desired queues.
+
+        Returns:
+            A dict containing port info at key 'port' and queue info keyed by 
the appropriate queue
+                id.
+
+        Raises:
+            InteractiveCommandExecutionError: If all queue offlaod 
configutrations could not be
+                retrieved.
+
+        """
+        returnDict: dict[int | str, str] = {}
+
+        config_output = self.send_command(f"show port {port_id} 
{rxtx.value}_offload configuration")
+        if verify:
+            if (
+                f"Rx Offloading Configuration of port {port_id}" not in 
config_output
+                and f"Tx Offloading Configuration of port {port_id}" not in 
config_output
+            ):
+                self._logger.debug(f"Get port offload config 
error\n{config_output}")
+                raise InteractiveCommandExecutionError(
+                    f"""Failed to get offload config on port 
{port_id}:\n{config_output}"""
+                )
+        # Actual ouput data starts on the thrid line
+        tempList: list[str] = config_output.splitlines()[3::]
+        returnDict["port"] = tempList[0]
+        for i in range(0, num_queues):
+            returnDict[i] = tempList[i + 1]
+        return returnDict
+
+    @requires_stopped_ports
+    def set_port_rxtx_mbuf_fast_free(
+        self, rxtx: RxTxArgFlag, on: bool, verify: bool, port_id: int = 0
+    ) -> None:
+        """Sets the mbuf_fast_free configuration for the RX or TX offload for 
a given port.
+
+        Args:
+            rxtx: Whether to set the mbuf_fast_free on the RX or TX port.
+            on: If :data:'True' mbuf_fast_free will be enabled, disable it 
otherwise.
+            verify: If :data:'True' the output of the command will be scanned 
in an attempt to
+                verify that the mbuf_fast_free was set successfully.
+            port_id: The port number to enable or disable mbuf_fast_free on.
+
+        Raises:
+            InteractiveCommandExecutionError: If mbuf_fast_free could not be 
set successfully
+        """
+        mbuf_output = self.send_command(
+            f"port config {port_id} {rxtx.value}_offload mbuf_fast_free {"on" 
if on else "off"}"
+        )
+
+        if "error" in mbuf_output and verify:
+            raise InteractiveCommandExecutionError(
+                f"""Unable to set mbuf_fast_free config on port 
{port_id}:\n{mbuf_output}"""
+            )
+
+    @requires_stopped_ports
+    def set_queue_rxtx_mbuf_fast_free(
+        self,
+        rxtx: RxTxArgFlag,
+        on: bool,
+        verify: bool,
+        port_id: int = 0,
+        queue_id: int = 0,
+    ) -> None:
+        """Sets RX or TX mbuf_fast_free configuration of the specified queue 
on a given port.
+
+        Args:
+            rxtx: Whether to set mbuf_fast_free for the RX or TX offload 
configuration on the
+                given queues.
+            on: If :data:'True' the mbuf_fast_free configuration will be 
enabled, otherwise
+                disabled.
+            verify: If :data:'True' the output of the command will be scanned 
in an attempt to
+                verify that mbuf_fast_free was set successfully on all ports.
+            queue_id: The queue to disable mbuf_fast_free on.
+            port_id: The ID of the port containing the queues.
+
+        Raises:
+            InteractiveCommandExecutionError: If all queues could not be set 
successfully.
+        """
+        toggle = "on" if on else "off"
+        output = self.send_command(
+            f"port {port_id} {rxtx.value}q {queue_id} {rxtx.value}_offload 
mbuf_fast_free {toggle}"
+        )
+        if verify:
+            if "Error" in output:
+                self._logger.debug(f"Set queue offload config error\n{output}")
+                raise InteractiveCommandExecutionError(
+                    f"Failed to get offload config on port {port_id}, queue 
{queue_id}:\n{output}"
+                )
+
+    def set_all_queues_rxtx_mbuf_fast_free(
+        self,
+        rxtx: RxTxArgFlag,
+        on: bool,
+        verify: bool,
+        port_id=0,
+        num_queues: int = 0,
+    ) -> None:
+        """Sets mbuf_fast_free configuration for the RX or TX offload of all 
queues on a given port.
+
+        Args:
+            rxtx: Whether to set mbuf_fast_free for the RX or TX offload 
configuration on the
+                given queues.
+            on: If :data:'True' the mbuf fast_free_configuration will be 
enabled, otherwise
+                disabled.
+            verify: If :data:'True' the output of the command will be scanned 
in an attempt to
+                verify that mbuf_fast_free was set successfully on all ports.
+            port_id: The ID of the port containing the queues.
+            num_queues: The queue to disable mbuf_fast_free on.
+        """
+        for i in range(0, num_queues):
+            self.set_queue_rxtx_mbuf_fast_free(rxtx, on, verify, 
port_id=port_id, queue_id=i)
+
 
 class NicCapability(NoAliasEnum):
     """A mapping between capability names and the associated 
:class:`TestPmdShell` methods.
@@ -2698,7 +2948,95 @@ class NicCapability(NoAliasEnum):
     we don't go looking for it again if a different test case also needs it.
     """
 
-    #: Scattered packets Rx enabled
+    TX_OFFLOAD_VLAN_INSERT: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_IPV4_CKSUM: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_UDP_CKSUM: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_TCP_CKSUM: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_SCTP_CKSUM: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_TCP_TSO: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_UDP_TSO: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_OUTER_IPV4_CKSUM: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_QINQ_INSERT: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_VXLAN_TNL_TSO: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_GRE_TNL_TSO: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_IPIP_TNL_TSO: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_GENEVE_TNL_TSO: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_MACSEC_INSERT: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_MT_LOCKFREE: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_MULTI_SEGS: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_MBUF_FAST_FREE: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_SECURITY: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_UDP_TNL_TSO: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_IP_TNL_TSO: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_OUTER_UDP_CKSUM: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    TX_OFFLOAD_SEND_ON_TIMESTAMP: TestPmdShellNicCapability = (
+        TestPmdShell.get_capabilities_tx_offload,
+        None,
+    )
+    # : Scattered packets Rx enabled
     SCATTERED_RX_ENABLED: TestPmdShellNicCapability = (
         TestPmdShell.get_capabilities_rxq_info,
         add_remove_mtu(9000),
diff --git a/dts/tests/TestSuite_rxtx_offload.py 
b/dts/tests/TestSuite_rxtx_offload.py
new file mode 100644
index 0000000000..90ba58bb25
--- /dev/null
+++ b/dts/tests/TestSuite_rxtx_offload.py
@@ -0,0 +1,153 @@
+# Copyright(c) 2025 University of New Hampshire
+
+"""RX TX offload test suite.
+
+Test the testpmd feature of configuring RX and TX offloads
+"""
+
+from time import sleep
+from framework.remote_session.testpmd_shell import (
+    RxTxArgFlag,
+    TestPmdShell,
+)
+from framework.test_suite import TestSuite, func_test
+from framework.testbed_model.capability import requires, NicCapability
+
+
+@requires(NicCapability.TX_OFFLOAD_MACSEC_INSERT)
+class TestRxTxOffload(TestSuite):
+    """RX/TX offload test suite."""
+
+    def check_port_config(
+        self,
+        testpmd: TestPmdShell,
+        offload: str,
+        rxtx: RxTxArgFlag,
+        verify: bool,
+        port_id: int = 0,
+    ) -> bool:
+        """Checks that the current port configuration matches the given 
offload.
+
+        Args:
+            testpmd: The currrent testpmd shell session to send commands to.
+            offload: The expected configuration of the given port.
+            rxtx: Wether to check the RX or TX configuration of the given port.
+            verify: Wether to verify the result of call to testpmd.
+            port_id: Id of the port to check.
+
+        Returns:
+            Whether current configuration matches given offload.
+        """
+        output = testpmd.get_rxtx_offload_config(rxtx, verify, port_id, 0)
+        return offload in output["port"] or (
+            offload == "NULL" and "MBUF_FAST_FREE" not in output["port"]
+        )
+
+    def check_queue_config(
+        self,
+        testpmd: TestPmdShell,
+        offload: list[str],
+        rxtx: RxTxArgFlag,
+        verify: bool,
+        port_id: int = 0,
+        num_queues: int = 0,
+    ) -> bool:
+        """Checks that the queue configuration matches the given offlad.
+
+        Args:
+            testpmd: The currrent testpmd shell session to send commands to.
+            offload: The expected configuration of the queues, each index 
corresponds
+                to the queue id.
+            rxtx: Whether to check the RX or TX configuration of the given 
queues.
+            verify: Whether to verify commands sent to testpmd.
+            port_id: The port of which the queues reside.
+            num_queues: The number of queues to check.
+
+        Returns:
+            Whether current configuration matches given offload
+        """
+        output = testpmd.get_rxtx_offload_config(rxtx, verify, port_id, 
num_queues)
+        for i in range(0, num_queues):
+            if not (
+                offload[i] in output[i]
+                or (offload[i] == "NULL" and "MBUF_FAST_FREE" not in output[i])
+            ):
+                return False
+        return True
+
+    @func_test
+    def test_mbuf_fast_free_configurations(self) -> None:
+        """Ensure mbuf_fast_free can be configured with testpmd.
+
+        Steps:
+            Start up testpmd shell.
+            Toggle mbuf_fast_free on.
+            Toggle mbuf_fast_free off.
+
+        Verify:
+            Mbuf_fast_free starts disabled.
+            Mbuf_fast_free can be configured on.
+            Mbuf_fast_free can be configured off.
+        """
+        with TestPmdShell() as testpmd:
+            verify: bool = True
+            port_id: int = 0
+            num_queues: int = 4
+            queue_off: list[str] = []
+            queue_on: list[str] = []
+            mbuf_on = "MBUF_FAST_FREE"
+            mbuf_off = "NULL"
+            tx = RxTxArgFlag.TX
+
+            for _ in range(0, num_queues):
+                queue_off.append(mbuf_off)
+                queue_on.append(mbuf_on)
+
+            testpmd.set_ports_queues(num_queues)
+            testpmd.start_all_ports()
+
+            # Ensure mbuf_fast_free is disabled by default on port and queues
+            self.verify(
+                self.check_port_config(testpmd, mbuf_off, tx, verify, port_id),
+                "Mbuf_fast_free enabled on port start",
+            )
+            self.verify(
+                self.check_queue_config(testpmd, queue_off, tx, verify, 
port_id, num_queues),
+                "Mbuf_fast_free enabled on queue start",
+            )
+
+            # Enable mbuf_fast_free per queue and verify
+            testpmd.set_all_queues_rxtx_mbuf_fast_free(tx, True, verify, 
port_id, num_queues)
+            self.verify(
+                self.check_port_config(testpmd, mbuf_off, tx, verify, port_id),
+                "Port configuration changed without call",
+            )
+            self.verify(
+                self.check_queue_config(testpmd, queue_on, tx, verify, 
port_id, num_queues),
+                "Queues failed to enable mbuf_fast_free",
+            )
+
+            # Enable mbuf_fast_free per port and verify
+            testpmd.set_port_rxtx_mbuf_fast_free(tx, True, verify, port_id)
+            self.verify(
+                self.check_port_config(testpmd, mbuf_on, tx, verify, port_id),
+                "Port failed to enable mbuf_fast_free",
+            )
+
+            # Disable mbuf_fast_free per queue and verify
+            testpmd.set_all_queues_rxtx_mbuf_fast_free(tx, False, verify, 
port_id, num_queues)
+            self.verify(
+                self.check_port_config(testpmd, mbuf_on, tx, verify, port_id),
+                "Port configuration changed without call",
+            )
+            self.verify(
+                self.check_queue_config(testpmd, queue_off, tx, verify, 
port_id, num_queues),
+                "Queues failed to disable mbuf_fast_free",
+            )
+
+            # Disable mbuf_fast_free per port and verify
+            testpmd.set_port_rxtx_mbuf_fast_free(tx, False, verify, port_id)
+            self.verify(
+                self.check_port_config(testpmd, mbuf_off, tx, verify, port_id),
+                "Port failed to disable mbuf_fast_free",
+            )
-- 
2.50.1

Reply via email to