DPDK-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 0/2] eal: tailq fixes
From: Stephen Hemminger @ 2026-06-09 15:53 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger
In-Reply-To: <20260607150418.30885-1-stephen@networkplumber.org>

A couple of small fixes to EAL tailq

Stephen Hemminger (2):
  eal: fix off by one in in tailq name init
  eal: add destructor to unregister tailq on unload

 lib/eal/common/eal_common_tailqs.c | 15 ++++++++++++++-
 lib/eal/include/rte_tailq.h        | 16 ++++++++++++++++
 2 files changed, 30 insertions(+), 1 deletion(-)

-- 
2.53.0


^ permalink raw reply

* Re: [PATCH 0/3] net/iavf: vf reset fixes
From: Bruce Richardson @ 2026-06-09 14:53 UTC (permalink / raw)
  To: Ciara Loftus; +Cc: dev
In-Reply-To: <20260608145518.1705524-1-ciara.loftus@intel.com>

On Mon, Jun 08, 2026 at 02:55:15PM +0000, Ciara Loftus wrote:
> The patch [1] aimed to address a race condition in the iavf driver
> during a reset and also reduced noisy logging during resets.
> Patch 1 of this series extracts the noisy logging fix into its own
> commit.
> Patch 2 offers an alternative approach to fixing the race condition.
> Patch 3 fixes a pre-existing refcount imbalance in the shared event
> handler thread that became visible while investigating the reset path.
> 
> [1] https://patches.dpdk.org/project/dpdk/patch/20260605123646.1328492-1-chaitanyababux.talluri@intel.com/
> 
> Ciara Loftus (2):
>   net/iavf: wait for PF reset start before reinitializing
>   net/iavf: fix event handler refcount leak on HW reset
> 
> Talluri Chaitanyababu (1):
>   net/iavf: downgrade opcode 0 ARQ log to debug
> 
>  drivers/net/intel/iavf/iavf.h        |  1 +
>  drivers/net/intel/iavf/iavf_ethdev.c | 14 +++++++++++++-
>  drivers/net/intel/iavf/iavf_vchnl.c  | 11 +++++++++--
>  3 files changed, 23 insertions(+), 3 deletions(-)
> 
Series-acked-by: Bruce Richardson <bruce.richardson@intel.com>

Series applied to dpdk-next-net-intel.
Thanks,
/Bruce

^ permalink raw reply

* [PATCH v1] dts: update dts check format script and resolve errors
From: Koushik Bhargav Nimoji @ 2026-06-09 14:45 UTC (permalink / raw)
  To: luca.vizzarro, patrickrobb1997, dev
  Cc: abailey, ahassick, lylavoie, Koushik Bhargav Nimoji

This patch updates the tool versions used in the dts-check-format.sh
script;by doing so, formatting and type hinting errors that weren't
previously visible have now appeared. This patch also resolves those
new formatting and type hinting errors.

Signed-off-by: Koushik Bhargav Nimoji <knimoji@iol.unh.edu>
---
 dts/api/packet.py                             |  15 +-
 dts/api/testpmd/__init__.py                   |  12 +-
 dts/api/testpmd/types.py                      |   6 +-
 dts/framework/config/__init__.py              |  24 +-
 dts/framework/config/test_run.py              |  12 +-
 dts/framework/context.py                      |   8 +-
 .../interactive_remote_session.py             |   2 +-
 .../remote_session/interactive_shell.py       |   6 +-
 .../remote_session/remote_session.py          |   4 +-
 dts/framework/remote_session/shell_pool.py    |   2 +-
 dts/framework/settings.py                     |   8 +-
 dts/framework/testbed_model/cpu.py            |   2 +-
 .../testbed_model/traffic_generator/scapy.py  |   6 +-
 .../testbed_model/traffic_generator/trex.py   |   6 +-
 dts/framework/utils.py                        |   9 +-
 dts/poetry.lock                               | 368 ++++++++++++++----
 dts/pyproject.toml                            |   8 +-
 dts/tests/TestSuite_cryptodev_throughput.py   |   4 +-
 dts/tests/TestSuite_port_control.py           |   3 +-
 .../TestSuite_single_core_forward_perf.py     |   2 +-
 20 files changed, 369 insertions(+), 138 deletions(-)

diff --git a/dts/api/packet.py b/dts/api/packet.py
index 094a1b7a9d..3dda18e781 100644
--- a/dts/api/packet.py
+++ b/dts/api/packet.py
@@ -87,9 +87,9 @@ def send_packets_and_capture(
         CapturingTrafficGenerator,
     )
 
-    assert isinstance(
-        get_ctx().func_tg, CapturingTrafficGenerator
-    ), "Cannot capture with a non-capturing traffic generator"
+    assert isinstance(get_ctx().func_tg, CapturingTrafficGenerator), (
+        "Cannot capture with a non-capturing traffic generator"
+    )
     tg: CapturingTrafficGenerator = cast(CapturingTrafficGenerator, get_ctx().func_tg)
     # TODO: implement @requires for types of traffic generator
     packets = adjust_addresses(packets)
@@ -308,8 +308,7 @@ def _verify_l2_frame(received_packet: Ether, contains_l3: bool) -> bool:
     if contains_l3:
         expected_src_mac = get_ctx().topology.sut_port_egress.mac_address
     log_debug(
-        f"Comparing received src mac '{received_packet.src}' "
-        f"with expected '{expected_src_mac}'."
+        f"Comparing received src mac '{received_packet.src}' with expected '{expected_src_mac}'."
     )
     if received_packet.src != expected_src_mac:
         return False
@@ -344,9 +343,9 @@ def assess_performance_by_packet(
         PerformanceTrafficGenerator,
     )
 
-    assert isinstance(
-        get_ctx().perf_tg, PerformanceTrafficGenerator
-    ), "Cannot send performance traffic with non-performance traffic generator"
+    assert isinstance(get_ctx().perf_tg, PerformanceTrafficGenerator), (
+        "Cannot send performance traffic with non-performance traffic generator"
+    )
     tg: PerformanceTrafficGenerator = cast(PerformanceTrafficGenerator, get_ctx().perf_tg)
     # TODO: implement @requires for types of traffic generator
     return tg.calculate_traffic_and_stats(packet, duration, send_mpps)
diff --git a/dts/api/testpmd/__init__.py b/dts/api/testpmd/__init__.py
index e9187440bb..1bde66d876 100644
--- a/dts/api/testpmd/__init__.py
+++ b/dts/api/testpmd/__init__.py
@@ -76,7 +76,9 @@ def _requires_stopped_ports(func: TestPmdMethod) -> TestPmdMethod:
     """
 
     @functools.wraps(func)
-    def _wrapper(self: "TestPmd", *args: P.args, **kwargs: P.kwargs) -> Any:
+    def _wrapper(
+        self: "TestPmd", *args: P.args, **kwargs: P.kwargs
+    ) -> Callable[P, "TestPmd"] | Any:
         if self.ports_started:
             self._logger.debug("Ports need to be stopped to continue.")
             self.stop_all_ports()
@@ -100,7 +102,9 @@ def _requires_started_ports(func: TestPmdMethod) -> TestPmdMethod:
     """
 
     @functools.wraps(func)
-    def _wrapper(self: "TestPmd", *args: P.args, **kwargs: P.kwargs) -> Any:
+    def _wrapper(
+        self: "TestPmd", *args: P.args, **kwargs: P.kwargs
+    ) -> Callable[P, "TestPmd"] | Any:
         if not self.ports_started:
             self._logger.debug("Ports need to be started to continue.")
             self.start_all_ports()
@@ -128,7 +132,9 @@ def _add_remove_mtu(mtu: int = 1500) -> Callable[[TestPmdMethod], TestPmdMethod]
 
     def decorator(func: TestPmdMethod) -> TestPmdMethod:
         @functools.wraps(func)
-        def wrapper(self: "TestPmd", *args: P.args, **kwargs: P.kwargs) -> Any:
+        def wrapper(
+            self: "TestPmd", *args: P.args, **kwargs: P.kwargs
+        ) -> Callable[P, TestPmd] | Any:
             original_mtu = self.ports[0].mtu
             self.set_port_mtu_all(mtu=mtu, verify=False)
             retval = func(self, *args, **kwargs)
diff --git a/dts/api/testpmd/types.py b/dts/api/testpmd/types.py
index 0d322aece2..af3263682e 100644
--- a/dts/api/testpmd/types.py
+++ b/dts/api/testpmd/types.py
@@ -279,7 +279,7 @@ def from_list_string(cls, names: str) -> Self:
         Returns:
             An instance of this flag.
         """
-        flag = cls(0)
+        flag: RSSOffloadTypesFlag = cls(0)
         for name in names.split():
             flag |= cls.from_str(name)
         return flag
@@ -960,7 +960,7 @@ def from_list_string(cls, names: str) -> Self:
         Returns:
             An instance of this flag.
         """
-        flag = cls(0)
+        flag: PacketOffloadFlag = cls(0)
         for name in names.split():
             flag |= cls.from_str(name)
         return flag
@@ -1168,7 +1168,7 @@ def from_list_string(cls, names: str) -> Self:
         Returns:
             An instance of this flag.
         """
-        flag = cls(0)
+        flag: RtePTypes = cls(0)
         for name in names.split():
             flag |= cls.from_str(name)
         return flag
diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init__.py
index d2f0138e4a..a8861894b7 100644
--- a/dts/framework/config/__init__.py
+++ b/dts/framework/config/__init__.py
@@ -85,9 +85,9 @@ def validate_port_links(self) -> Self:
             sut_node_port_peer = existing_port_links.get(
                 (self.test_run.system_under_test_node, link.sut_port), None
             )
-            assert (
-                sut_node_port_peer is not None
-            ), f"Invalid SUT node port specified for link port_topology.{link_idx}."
+            assert sut_node_port_peer is not None, (
+                f"Invalid SUT node port specified for link port_topology.{link_idx}."
+            )
 
             assert sut_node_port_peer is False or sut_node_port_peer == link.right, (
                 f"The SUT node port for link port_topology.{link_idx} is "
@@ -97,9 +97,9 @@ def validate_port_links(self) -> Self:
             tg_node_port_peer = existing_port_links.get(
                 (self.test_run.traffic_generator_node, link.tg_port), None
             )
-            assert (
-                tg_node_port_peer is not None
-            ), f"Invalid TG node port specified for link port_topology.{link_idx}."
+            assert tg_node_port_peer is not None, (
+                f"Invalid TG node port specified for link port_topology.{link_idx}."
+            )
 
             assert tg_node_port_peer is False or sut_node_port_peer == link.left, (
                 f"The TG node port for link port_topology.{link_idx} is "
@@ -117,16 +117,16 @@ def validate_test_run_against_nodes(self) -> Self:
         sut_node_name = self.test_run.system_under_test_node
         sut_node = next((n for n in self.nodes if n.name == sut_node_name), None)
 
-        assert (
-            sut_node is not None
-        ), f"The system_under_test_node {sut_node_name} is not a valid node name."
+        assert sut_node is not None, (
+            f"The system_under_test_node {sut_node_name} is not a valid node name."
+        )
 
         tg_node_name = self.test_run.traffic_generator_node
         tg_node = next((n for n in self.nodes if n.name == tg_node_name), None)
 
-        assert (
-            tg_node is not None
-        ), f"The traffic_generator_name {tg_node_name} is not a valid node name."
+        assert tg_node is not None, (
+            f"The traffic_generator_name {tg_node_name} is not a valid node name."
+        )
 
         return self
 
diff --git a/dts/framework/config/test_run.py b/dts/framework/config/test_run.py
index 76e24d1785..3cd643981d 100644
--- a/dts/framework/config/test_run.py
+++ b/dts/framework/config/test_run.py
@@ -233,9 +233,9 @@ def test_suite_spec(self) -> "TestSuiteSpec":
         from framework.test_suite import find_by_name
 
         test_suite_spec = find_by_name(self.test_suite_name)
-        assert (
-            test_suite_spec is not None
-        ), f"{self.test_suite_name} is not a valid test suite module name."
+        assert test_suite_spec is not None, (
+            f"{self.test_suite_name} is not a valid test suite module name."
+        )
         return test_suite_spec
 
     @cached_property
@@ -384,9 +384,9 @@ def convert_from_string(cls, data: Any) -> Any:
     @model_validator(mode="after")
     def verify_distinct_nodes(self) -> Self:
         """Verify that each side of the link has distinct nodes."""
-        assert (
-            self.left.node_type != self.right.node_type
-        ), "Linking ports of the same node is unsupported."
+        assert self.left.node_type != self.right.node_type, (
+            "Linking ports of the same node is unsupported."
+        )
         return self
 
 
diff --git a/dts/framework/context.py b/dts/framework/context.py
index 8f1021dc96..efe9af0645 100644
--- a/dts/framework/context.py
+++ b/dts/framework/context.py
@@ -60,9 +60,9 @@ def reset(self) -> None:
                 else _field.default
             )
 
-            assert (
-                default is not MISSING
-            ), "{LocalContext.__name__} must have defaults on all fields!"
+            assert default is not MISSING, (
+                "{LocalContext.__name__} must have defaults on all fields!"
+            )
 
             setattr(self, _field.name, default)
 
@@ -108,7 +108,7 @@ def filter_cores(
 ) -> Callable[[type["TestProtocol"]], Callable]:
     """Decorates functions that require a temporary update to the lcore specifier."""
 
-    def decorator(func: type["TestProtocol"]) -> Callable:
+    def decorator(func: type["TestProtocol"]) -> Callable[P, type["TestProtocol"]]:
         @functools.wraps(func)
         def wrapper(*args: P.args, **kwargs: P.kwargs) -> Any:
             local_ctx = get_ctx().local
diff --git a/dts/framework/remote_session/interactive_remote_session.py b/dts/framework/remote_session/interactive_remote_session.py
index c8156b4345..fc42e862bc 100644
--- a/dts/framework/remote_session/interactive_remote_session.py
+++ b/dts/framework/remote_session/interactive_remote_session.py
@@ -109,7 +109,7 @@ def _connect(self) -> None:
                 self._logger.debug(traceback.format_exc())
                 self._logger.warning(e)
                 self._logger.info(
-                    f"Retrying interactive session connection: retry number {retry_attempt +1}"
+                    f"Retrying interactive session connection: retry number {retry_attempt + 1}"
                 )
             else:
                 break
diff --git a/dts/framework/remote_session/interactive_shell.py b/dts/framework/remote_session/interactive_shell.py
index a65cbce209..6bba58a4f6 100644
--- a/dts/framework/remote_session/interactive_shell.py
+++ b/dts/framework/remote_session/interactive_shell.py
@@ -50,7 +50,9 @@
 def only_active(func: InteractiveShellMethod) -> InteractiveShellMethod:
     """This decorator will skip running the method if the SSH channel is not active."""
 
-    def _wrapper(self: "InteractiveShell", *args: P.args, **kwargs: P.kwargs) -> R | None:
+    def _wrapper(
+        self: "InteractiveShell", *args: P.args, **kwargs: P.kwargs
+    ) -> Callable[P, "InteractiveShell"] | None:
         if self._ssh_channel.active:
             return func(self, *args, **kwargs)
         return None
@@ -167,7 +169,7 @@ def start_application(self, prompt: str | None = None, add_to_shell_pool: bool =
                 break
             except InteractiveSSHTimeoutError:
                 self._logger.info(
-                    f"Interactive shell failed to start (attempt {attempt+1} out of "
+                    f"Interactive shell failed to start (attempt {attempt + 1} out of "
                     f"{self._init_attempts})"
                 )
         else:
diff --git a/dts/framework/remote_session/remote_session.py b/dts/framework/remote_session/remote_session.py
index 158325bb7f..fb5f6fedf5 100644
--- a/dts/framework/remote_session/remote_session.py
+++ b/dts/framework/remote_session/remote_session.py
@@ -72,9 +72,7 @@ def __post_init__(self, init_stdout: str, init_stderr: str) -> None:
     def __str__(self) -> str:
         """Format the command outputs."""
         return (
-            f"stdout: '{self.stdout}'\n"
-            f"stderr: '{self.stderr}'\n"
-            f"return_code: '{self.return_code}'"
+            f"stdout: '{self.stdout}'\nstderr: '{self.stderr}'\nreturn_code: '{self.return_code}'"
         )
 
 
diff --git a/dts/framework/remote_session/shell_pool.py b/dts/framework/remote_session/shell_pool.py
index 241737eab3..710107c6cb 100644
--- a/dts/framework/remote_session/shell_pool.py
+++ b/dts/framework/remote_session/shell_pool.py
@@ -74,7 +74,7 @@ def unregister_shell(self, shell: "InteractiveShell") -> None:
 
     def start_new_pool(self) -> None:
         """Start a new shell pool."""
-        self._logger.debug(f"Starting new shell pool and advancing to level {self.pool_level+1}.")
+        self._logger.debug(f"Starting new shell pool and advancing to level {self.pool_level + 1}.")
         self._pools.append(set())
 
     def terminate_current_pool(self) -> None:
diff --git a/dts/framework/settings.py b/dts/framework/settings.py
index b08373b7ea..f329677804 100644
--- a/dts/framework/settings.py
+++ b/dts/framework/settings.py
@@ -249,9 +249,9 @@ def error(self, message) -> NoReturn:
             if _is_from_env(action):
                 action_name = _get_action_name(action)
                 env_var_name = _get_env_var_name(action)
-                assert (
-                    env_var_name is not None
-                ), "Action was set from environment, but no environment variable name was found."
+                assert env_var_name is not None, (
+                    "Action was set from environment, but no environment variable name was found."
+                )
                 env_var_value = os.environ.get(env_var_name)
 
                 message = message.replace(
@@ -260,7 +260,7 @@ def error(self, message) -> NoReturn:
                 )
 
         print(f"{self.prog}: error: {message}\n", file=sys.stderr)
-        self.exit(2, "For help and usage, " "run the command with the --help flag.\n")
+        self.exit(2, "For help and usage, run the command with the --help flag.\n")
 
 
 class _EnvVarHelpFormatter(ArgumentDefaultsHelpFormatter):
diff --git a/dts/framework/testbed_model/cpu.py b/dts/framework/testbed_model/cpu.py
index 6e2ecca080..a9471709dd 100644
--- a/dts/framework/testbed_model/cpu.py
+++ b/dts/framework/testbed_model/cpu.py
@@ -105,7 +105,7 @@ def __init__(self, lcore_list: list[int] | list[str] | list[LogicalCore] | str)
 
         # the input lcores may not be sorted
         self._lcore_list.sort()
-        self._lcore_str = f'{",".join(self._get_consecutive_lcores_range(self._lcore_list))}'
+        self._lcore_str = f"{','.join(self._get_consecutive_lcores_range(self._lcore_list))}"
 
     @property
     def lcore_list(self) -> list[int]:
diff --git a/dts/framework/testbed_model/traffic_generator/scapy.py b/dts/framework/testbed_model/traffic_generator/scapy.py
index c6e9006205..62853a34e4 100644
--- a/dts/framework/testbed_model/traffic_generator/scapy.py
+++ b/dts/framework/testbed_model/traffic_generator/scapy.py
@@ -314,9 +314,9 @@ def __init__(self, tg_node: Node, config: ScapyTrafficGeneratorConfig, **kwargs:
             kwargs: Additional keyword arguments. Supported arguments correspond to the parameters
                 of :meth:`PythonShell.__init__` in this case.
         """
-        assert (
-            tg_node.config.os == OS.linux
-        ), "Linux is the only supported OS for scapy traffic generation"
+        assert tg_node.config.os == OS.linux, (
+            "Linux is the only supported OS for scapy traffic generation"
+        )
 
         super().__init__(tg_node=tg_node, config=config, **kwargs)
 
diff --git a/dts/framework/testbed_model/traffic_generator/trex.py b/dts/framework/testbed_model/traffic_generator/trex.py
index 22cd20dea9..0314dfc881 100644
--- a/dts/framework/testbed_model/traffic_generator/trex.py
+++ b/dts/framework/testbed_model/traffic_generator/trex.py
@@ -94,9 +94,9 @@ def __init__(self, tg_node: Node, config: TrexTrafficGeneratorConfig) -> None:
             tg_node: TG node the TRex instance is operating on.
             config: Traffic generator config provided for TRex instance.
         """
-        assert (
-            tg_node.config.os == OS.linux
-        ), "Linux is the only supported OS for trex traffic generation"
+        assert tg_node.config.os == OS.linux, (
+            "Linux is the only supported OS for trex traffic generation"
+        )
 
         super().__init__(tg_node=tg_node, config=config)
         self._tg_node_config = tg_node.config
diff --git a/dts/framework/utils.py b/dts/framework/utils.py
index 9917ffbfaa..d16580ec85 100644
--- a/dts/framework/utils.py
+++ b/dts/framework/utils.py
@@ -154,7 +154,11 @@ def extension(self) -> str:
         For other compression formats, the extension will be in the format
         'tar.{compression format}'.
         """
-        return f"{self.value}" if self == self.none else f"{type(self).none.value}.{self.value}"
+        return (
+            f"{self.value}"
+            if self == self.none
+            else f"{TarCompressionFormat.none.value}.{self.value}"
+        )
 
 
 def convert_to_list_of_string(value: Any | list[Any]) -> list[str]:
@@ -207,7 +211,8 @@ def filter_func(tarinfo: tarfile.TarInfo) -> tarfile.TarInfo | None:
         return None
 
     target_tarball_path = dir_path.with_suffix(f".{compress_format.extension}")
-    with tarfile.open(target_tarball_path, f"w:{compress_format.value}") as tar:
+    compression_format: Any = f"w:{compress_format.value}"
+    with tarfile.open(target_tarball_path, compression_format) as tar:
         tar.add(dir_path, arcname=dir_path.name, filter=create_filter_function(exclude))
 
     return target_tarball_path
diff --git a/dts/poetry.lock b/dts/poetry.lock
index 0ad5d32b85..eda7cc2fec 100644
--- a/dts/poetry.lock
+++ b/dts/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
+# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand.
 
 [[package]]
 name = "aenum"
@@ -6,6 +6,7 @@ version = "3.1.15"
 description = "Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants"
 optional = false
 python-versions = "*"
+groups = ["main"]
 files = [
     {file = "aenum-3.1.15-py2-none-any.whl", hash = "sha256:27b1710b9d084de6e2e695dab78fe9f269de924b51ae2850170ee7e1ca6288a5"},
     {file = "aenum-3.1.15-py3-none-any.whl", hash = "sha256:e0dfaeea4c2bd362144b87377e2c61d91958c5ed0b4daf89cb6f45ae23af6288"},
@@ -18,17 +19,62 @@ version = "0.7.0"
 description = "Reusable constraint types to use with typing.Annotated"
 optional = false
 python-versions = ">=3.8"
+groups = ["main"]
 files = [
     {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
     {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
 ]
 
+[[package]]
+name = "ast-serialize"
+version = "0.5.0"
+description = "Python bindings for mypy AST serialization"
+optional = false
+python-versions = ">=3.7"
+groups = ["dev"]
+files = [
+    {file = "ast_serialize-0.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c"},
+    {file = "ast_serialize-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-win32.whl", hash = "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-win_amd64.whl", hash = "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590"},
+    {file = "ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642"},
+    {file = "ast_serialize-0.5.0.tar.gz", hash = "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6"},
+]
+
 [[package]]
 name = "bcrypt"
 version = "4.2.1"
 description = "Modern password hashing for your software and your servers"
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "bcrypt-4.2.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:1340411a0894b7d3ef562fb233e4b6ed58add185228650942bdc885362f32c17"},
     {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ee315739bc8387aa36ff127afc99120ee452924e0df517a8f3e4c0187a0f5f"},
@@ -67,6 +113,7 @@ version = "1.17.1"
 description = "Foreign Function Interface for Python calling C code."
 optional = false
 python-versions = ">=3.8"
+groups = ["main", "dev"]
 files = [
     {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
     {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
@@ -136,6 +183,7 @@ files = [
     {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"},
     {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
 ]
+markers = {dev = "platform_python_implementation != \"PyPy\""}
 
 [package.dependencies]
 pycparser = "*"
@@ -146,6 +194,7 @@ version = "44.0.0"
 description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
 optional = false
 python-versions = "!=3.9.0,!=3.9.1,>=3.7"
+groups = ["main", "dev"]
 files = [
     {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"},
     {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"},
@@ -180,10 +229,10 @@ files = [
 cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
 
 [package.extras]
-docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"]
+docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""]
 docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"]
-nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"]
-pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
+nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""]
+pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
 sdist = ["build (>=1.0.0)"]
 ssh = ["bcrypt (>=3.1.5)"]
 test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
@@ -195,6 +244,7 @@ version = "5.1.1"
 description = "Decorators for Humans"
 optional = false
 python-versions = ">=3.5"
+groups = ["main"]
 files = [
     {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
     {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
@@ -206,6 +256,7 @@ version = "1.2.18"
 description = "Python @deprecated decorator to deprecate old python classes, functions or methods."
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
+groups = ["main"]
 files = [
     {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"},
     {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"},
@@ -215,7 +266,7 @@ files = [
 wrapt = ">=1.10,<2"
 
 [package.extras]
-dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools", "tox"]
+dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"]
 
 [[package]]
 name = "fabric"
@@ -223,6 +274,7 @@ version = "3.2.2"
 description = "High level SSH command execution"
 optional = false
 python-versions = "*"
+groups = ["main"]
 files = [
     {file = "fabric-3.2.2-py3-none-any.whl", hash = "sha256:91c47c0be68b14936c88b34da8a1f55e5710fd28397dac5d4ff2e21558113a6f"},
     {file = "fabric-3.2.2.tar.gz", hash = "sha256:8783ca42e3b0076f08b26901aac6b9d9b1f19c410074e7accfab902c184ff4a3"},
@@ -243,56 +295,177 @@ version = "2.2.0"
 description = "Pythonic task execution"
 optional = false
 python-versions = ">=3.6"
+groups = ["main"]
 files = [
     {file = "invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820"},
     {file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"},
 ]
 
+[[package]]
+name = "librt"
+version = "0.11.0"
+description = "Mypyc runtime library"
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+markers = "platform_python_implementation != \"PyPy\""
+files = [
+    {file = "librt-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e94ebfcfa2d5e9926d6c3b9aa4617ffc42a845b4321fb84021b872358c82a0f"},
+    {file = "librt-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ae627397a2f351560440d872d6f7c8dbb4072e57868e7b2fc5b8b430fe489d45"},
+    {file = "librt-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc329359321b67d24efdf4bc69012b0597001649544db662c001db5a0184794c"},
+    {file = "librt-0.11.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:7e82e642ab0f7608ce2fe53d76ca2280a9ee33a1b06556142c7c6fe80a86fc33"},
+    {file = "librt-0.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88145c15c67731d54283d135b03244028c750cc9edc334a96a4f5950ebdb2884"},
+    {file = "librt-0.11.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d36a51b3d93320b686588e27123f4995804dbf1bce81df78c02fc3c6eea9280"},
+    {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3ac06a2a8b246327f11e186a53a100a4d5c7ed52346367e5ec751d51586c"},
+    {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:461bbceede621f1ffb8839755f8663e886087ee7af16294cab7fb4d782c62eeb"},
+    {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0cad8a4d6a8ff03c9b76f9414caccd78e7cfbc8a2e12fa334d8e1d9932753783"},
+    {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f37aa505b3cf60701562eddb32df74b12a9e380c207fd8b06dd157a943ac7ea0"},
+    {file = "librt-0.11.0-cp310-cp310-win32.whl", hash = "sha256:94663a21534637f0e787ec2a2a756022df6e5b7b2335a5cdd7d8e33d68a2af89"},
+    {file = "librt-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:dec7db73758c2b54953fd8b7fe348c45188fe26b39ee18446196edd08453a5d4"},
+    {file = "librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29"},
+    {file = "librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9"},
+    {file = "librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5"},
+    {file = "librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b"},
+    {file = "librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89"},
+    {file = "librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc"},
+    {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5"},
+    {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7"},
+    {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d"},
+    {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412"},
+    {file = "librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d"},
+    {file = "librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73"},
+    {file = "librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c"},
+    {file = "librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46"},
+    {file = "librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3"},
+    {file = "librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67"},
+    {file = "librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a"},
+    {file = "librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a"},
+    {file = "librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f"},
+    {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b"},
+    {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766"},
+    {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d"},
+    {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8"},
+    {file = "librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a"},
+    {file = "librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9"},
+    {file = "librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c"},
+    {file = "librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894"},
+    {file = "librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c"},
+    {file = "librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea"},
+    {file = "librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230"},
+    {file = "librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2"},
+    {file = "librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3"},
+    {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21"},
+    {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930"},
+    {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be"},
+    {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e"},
+    {file = "librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e"},
+    {file = "librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47"},
+    {file = "librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44"},
+    {file = "librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd"},
+    {file = "librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4"},
+    {file = "librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8"},
+    {file = "librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b"},
+    {file = "librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175"},
+    {file = "librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03"},
+    {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c"},
+    {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3"},
+    {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96"},
+    {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe"},
+    {file = "librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f"},
+    {file = "librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7"},
+    {file = "librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1"},
+    {file = "librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72"},
+    {file = "librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa"},
+    {file = "librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548"},
+    {file = "librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2"},
+    {file = "librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f"},
+    {file = "librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51"},
+    {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2"},
+    {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085"},
+    {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3"},
+    {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd"},
+    {file = "librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8"},
+    {file = "librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c"},
+    {file = "librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253"},
+    {file = "librt-0.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6bd72d903911d995ab666dbd1871f8b1e80925a699af8063fbf50053329fb05f"},
+    {file = "librt-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ef69ac715f3cd8e5cd252cb2aebfa72c015492aacc339d5d7bf8fef3c62c677"},
+    {file = "librt-0.11.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:624a40c4a4ad7773315c287276cd024509b2c66ff5904f504bfc08d2c70293ab"},
+    {file = "librt-0.11.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:41dc19fe150b69716c8ece4f76773a9e8813fe3e35e032a58b4d46423fb8d7c0"},
+    {file = "librt-0.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e8bd98ea9c47ae90b319a087ab28dac493f1ffbc1ecd1f28fcdbf3b7e1108d1"},
+    {file = "librt-0.11.0-cp39-cp39-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84308fc49423ce6475d1c5d1985cd69a8ca9f0325fc7d5f81bb690a3f3625d4e"},
+    {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ff0fbaf5f44a21beeb0110f2ab64f45135a9536a834b79c0d1ef018f2786bbfa"},
+    {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9c028a9442a18e266955d364ce42259136e79a7ba14d773e0d778d5f70cd56f1"},
+    {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:9f1692105a02bcf853f355032a5fdc5494358ef83d8fd22d16de375c85cec3f5"},
+    {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7a80a71e1fda83cc752a9141e87aae7fef279538597564d670e9ce513f286192"},
+    {file = "librt-0.11.0-cp39-cp39-win32.whl", hash = "sha256:140695816ddf3c86eb972981a26f35efd871c44b0c3aed44c8cd01749386617f"},
+    {file = "librt-0.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:92f7ff819c197fc30473190a12c2856f325ac90aabfccbeb2072d28cc2e234e3"},
+    {file = "librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1"},
+]
+
 [[package]]
 name = "mypy"
-version = "1.13.0"
+version = "2.1.0"
 description = "Optional static typing for Python"
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.10"
+groups = ["dev"]
 files = [
-    {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"},
-    {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"},
-    {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"},
-    {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"},
-    {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"},
-    {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"},
-    {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"},
-    {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"},
-    {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"},
-    {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"},
-    {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"},
-    {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"},
-    {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"},
-    {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"},
-    {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"},
-    {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"},
-    {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"},
-    {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"},
-    {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"},
-    {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"},
-    {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"},
-    {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"},
-    {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"},
-    {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"},
-    {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"},
-    {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"},
-    {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"},
-    {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"},
-    {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"},
-    {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"},
-    {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"},
-    {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"},
+    {file = "mypy-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:11a6beb180257a805961aea9ec591bbd0bd17f1e18d35b8456d57aee5bedfedc"},
+    {file = "mypy-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ef78c1d306bbf9a8a12f526c44902c9c28dffd6c52c52bf6a72641ce18d3849"},
+    {file = "mypy-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c209a90853081ff01d01ee895cafe10f7db1474e0d95beaeef0f6c1db9119bbd"},
+    {file = "mypy-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47cebf61abde7c088a4e27718a8b13a81655686b2e9c251f5c0915a802248166"},
+    {file = "mypy-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d57a90ae5e872138a425ec328edbc9b235d1934c4377881a33ec05b341acc9a8"},
+    {file = "mypy-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:aea7f7a8a55b459c34275fc468ada6ca7c173a5e43a68f5dbe588a563d8a06b8"},
+    {file = "mypy-2.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c989640253f0d76843e9c6c1bbf4bd48c5e85ada61bde4beb37cb3eca035685e"},
+    {file = "mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41"},
+    {file = "mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca"},
+    {file = "mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538"},
+    {file = "mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398"},
+    {file = "mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563"},
+    {file = "mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389"},
+    {file = "mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666"},
+    {file = "mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af"},
+    {file = "mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6"},
+    {file = "mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211"},
+    {file = "mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b"},
+    {file = "mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22"},
+    {file = "mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b"},
+    {file = "mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8"},
+    {file = "mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5"},
+    {file = "mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e"},
+    {file = "mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e"},
+    {file = "mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285"},
+    {file = "mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5"},
+    {file = "mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65"},
+    {file = "mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d"},
+    {file = "mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2"},
+    {file = "mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f"},
+    {file = "mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4"},
+    {file = "mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef"},
+    {file = "mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135"},
+    {file = "mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21"},
+    {file = "mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57"},
+    {file = "mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e"},
+    {file = "mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780"},
+    {file = "mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd"},
+    {file = "mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08"},
+    {file = "mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081"},
+    {file = "mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7"},
+    {file = "mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6"},
+    {file = "mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289"},
+    {file = "mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633"},
 ]
 
 [package.dependencies]
-mypy-extensions = ">=1.0.0"
+ast-serialize = ">=0.3.0,<1.0.0"
+librt = {version = ">=0.11.0", markers = "platform_python_implementation != \"PyPy\""}
+mypy_extensions = ">=1.0.0"
+pathspec = ">=1.0.0"
 tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
-typing-extensions = ">=4.6.0"
+typing_extensions = [
+    {version = ">=4.6.0", markers = "python_version < \"3.15\""},
+    {version = ">=4.14.0", markers = "python_version >= \"3.15\""},
+]
 
 [package.extras]
 dmypy = ["psutil (>=4.0)"]
@@ -307,6 +480,7 @@ version = "1.0.0"
 description = "Type system extensions for programs checked with the mypy type checker."
 optional = false
 python-versions = ">=3.5"
+groups = ["dev"]
 files = [
     {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
     {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
@@ -318,6 +492,7 @@ version = "3.5.0"
 description = "SSH2 protocol library"
 optional = false
 python-versions = ">=3.6"
+groups = ["main"]
 files = [
     {file = "paramiko-3.5.0-py3-none-any.whl", hash = "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9"},
     {file = "paramiko-3.5.0.tar.gz", hash = "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124"},
@@ -329,20 +504,39 @@ cryptography = ">=3.3"
 pynacl = ">=1.5"
 
 [package.extras]
-all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"]
-gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"]
+all = ["gssapi (>=1.4.1) ; platform_system != \"Windows\"", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8) ; platform_system == \"Windows\""]
+gssapi = ["gssapi (>=1.4.1) ; platform_system != \"Windows\"", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8) ; platform_system == \"Windows\""]
 invoke = ["invoke (>=2.0)"]
 
+[[package]]
+name = "pathspec"
+version = "1.1.1"
+description = "Utility library for gitignore style pattern matching of file paths."
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+    {file = "pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189"},
+    {file = "pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a"},
+]
+
+[package.extras]
+hyperscan = ["hyperscan (>=0.7)"]
+optional = ["typing-extensions (>=4)"]
+re2 = ["google-re2 (>=1.1)"]
+
 [[package]]
 name = "pycparser"
 version = "2.22"
 description = "C parser in Python"
 optional = false
 python-versions = ">=3.8"
+groups = ["main", "dev"]
 files = [
     {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
     {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
 ]
+markers = {dev = "platform_python_implementation != \"PyPy\""}
 
 [[package]]
 name = "pydantic"
@@ -350,6 +544,7 @@ version = "2.10.3"
 description = "Data validation using Python type hints"
 optional = false
 python-versions = ">=3.8"
+groups = ["main"]
 files = [
     {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"},
     {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"},
@@ -362,7 +557,7 @@ typing-extensions = ">=4.12.2"
 
 [package.extras]
 email = ["email-validator (>=2.0.0)"]
-timezone = ["tzdata"]
+timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
 
 [[package]]
 name = "pydantic-core"
@@ -370,6 +565,7 @@ version = "2.27.1"
 description = "Core functionality for Pydantic validation and serialization"
 optional = false
 python-versions = ">=3.8"
+groups = ["main"]
 files = [
     {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"},
     {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"},
@@ -482,6 +678,7 @@ version = "1.5.0"
 description = "Python binding to the Networking and Cryptography (NaCl) library"
 optional = false
 python-versions = ">=3.6"
+groups = ["main"]
 files = [
     {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"},
     {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"},
@@ -508,6 +705,7 @@ version = "6.0.2"
 description = "YAML parser and emitter for Python"
 optional = false
 python-versions = ">=3.8"
+groups = ["main"]
 files = [
     {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
     {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
@@ -566,29 +764,30 @@ files = [
 
 [[package]]
 name = "ruff"
-version = "0.8.2"
+version = "0.15.16"
 description = "An extremely fast Python linter and code formatter, written in Rust."
 optional = false
 python-versions = ">=3.7"
+groups = ["dev"]
 files = [
-    {file = "ruff-0.8.2-py3-none-linux_armv6l.whl", hash = "sha256:c49ab4da37e7c457105aadfd2725e24305ff9bc908487a9bf8d548c6dad8bb3d"},
-    {file = "ruff-0.8.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ec016beb69ac16be416c435828be702ee694c0d722505f9c1f35e1b9c0cc1bf5"},
-    {file = "ruff-0.8.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f05cdf8d050b30e2ba55c9b09330b51f9f97d36d4673213679b965d25a785f3c"},
-    {file = "ruff-0.8.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60f578c11feb1d3d257b2fb043ddb47501ab4816e7e221fbb0077f0d5d4e7b6f"},
-    {file = "ruff-0.8.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbd5cf9b0ae8f30eebc7b360171bd50f59ab29d39f06a670b3e4501a36ba5897"},
-    {file = "ruff-0.8.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b402ddee3d777683de60ff76da801fa7e5e8a71038f57ee53e903afbcefdaa58"},
-    {file = "ruff-0.8.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:705832cd7d85605cb7858d8a13d75993c8f3ef1397b0831289109e953d833d29"},
-    {file = "ruff-0.8.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32096b41aaf7a5cc095fa45b4167b890e4c8d3fd217603f3634c92a541de7248"},
-    {file = "ruff-0.8.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e769083da9439508833cfc7c23e351e1809e67f47c50248250ce1ac52c21fb93"},
-    {file = "ruff-0.8.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fe716592ae8a376c2673fdfc1f5c0c193a6d0411f90a496863c99cd9e2ae25d"},
-    {file = "ruff-0.8.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:81c148825277e737493242b44c5388a300584d73d5774defa9245aaef55448b0"},
-    {file = "ruff-0.8.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d261d7850c8367704874847d95febc698a950bf061c9475d4a8b7689adc4f7fa"},
-    {file = "ruff-0.8.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1ca4e3a87496dc07d2427b7dd7ffa88a1e597c28dad65ae6433ecb9f2e4f022f"},
-    {file = "ruff-0.8.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:729850feed82ef2440aa27946ab39c18cb4a8889c1128a6d589ffa028ddcfc22"},
-    {file = "ruff-0.8.2-py3-none-win32.whl", hash = "sha256:ac42caaa0411d6a7d9594363294416e0e48fc1279e1b0e948391695db2b3d5b1"},
-    {file = "ruff-0.8.2-py3-none-win_amd64.whl", hash = "sha256:2aae99ec70abf43372612a838d97bfe77d45146254568d94926e8ed5bbb409ea"},
-    {file = "ruff-0.8.2-py3-none-win_arm64.whl", hash = "sha256:fb88e2a506b70cfbc2de6fae6681c4f944f7dd5f2fe87233a7233d888bad73e8"},
-    {file = "ruff-0.8.2.tar.gz", hash = "sha256:b84f4f414dda8ac7f75075c1fa0b905ac0ff25361f42e6d5da681a465e0f78e5"},
+    {file = "ruff-0.15.16-py3-none-linux_armv6l.whl", hash = "sha256:6ac3c0b3969cc6cf6b158c4e2f8f682acb58e7d700d8a44b65ecdc72d66ab0b2"},
+    {file = "ruff-0.15.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:197c207ed75ffba54a0dec23db4aa939a27a3053073e085e0042433cbdc58e4a"},
+    {file = "ruff-0.15.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a39fec45ab316cc23e7558f23fea4a70403ddb5648ea9a4a3854a16973d0071"},
+    {file = "ruff-0.15.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba93191d79003116b95128c9d306e045200fdbd0bccb782b110f3cd1d4abc5cf"},
+    {file = "ruff-0.15.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6ee4b90520630120ef032aa5cc10db483852dff950e78b1d717e2993a61ac8d"},
+    {file = "ruff-0.15.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e4215bc938bc3c8215c1472c1aa437e310fee20cd427335fec9d7e609563628"},
+    {file = "ruff-0.15.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c8d26be963b090f10e29abc8b3e74a2a321f6fa34e02424e30b5af89350ecbb"},
+    {file = "ruff-0.15.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f198cf4123602a2280ed46c307bcbafe41758d6fee5b456b6b6058ca1514b3b4"},
+    {file = "ruff-0.15.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb27515fa6240fb586ae82b901a59e67d24acff86f2190b433dc542fe0435aeb"},
+    {file = "ruff-0.15.16-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a267c46ba1593fc26b8eecbea050b39d40c0b6bb7781ee11c90a02cd10032951"},
+    {file = "ruff-0.15.16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:528c68f39a91498a8d50e91ff5985df3d105782bab49cc378e73ac26bff083e8"},
+    {file = "ruff-0.15.16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7ed55c58950df60589a9a7a5d2f8fa5f54ebd287163be805adfe6ee95a9de123"},
+    {file = "ruff-0.15.16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d482feaf51512b50f9790ceb417a56a61dd1e9d9bf967662b9ed27c01b34f53a"},
+    {file = "ruff-0.15.16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e15bc8c94513dae2a40cc9ef07c94fdd4ecc9e29dabebeebe170f952322c9e3"},
+    {file = "ruff-0.15.16-py3-none-win32.whl", hash = "sha256:580378f7bd4aa25f72e74aa54948a9622f142b1e509521dd10902e886681cc1e"},
+    {file = "ruff-0.15.16-py3-none-win_amd64.whl", hash = "sha256:408256017284eddf98fff77b29aa4fb30f586042d535b2d9befc6512f400aaec"},
+    {file = "ruff-0.15.16-py3-none-win_arm64.whl", hash = "sha256:8cd61783afb39638a7133ef0d2dfb1e91277593962f81b5a8423eb0b888a6121"},
+    {file = "ruff-0.15.16.tar.gz", hash = "sha256:d05e78d38c78caf020b03789e25106c93017db5a0cb6e2819885018c61343b78"},
 ]
 
 [[package]]
@@ -597,6 +796,7 @@ version = "2.6.1"
 description = "Scapy: interactive packet manipulation tool"
 optional = false
 python-versions = "<4,>=3.7"
+groups = ["main"]
 files = [
     {file = "scapy-2.6.1-py3-none-any.whl", hash = "sha256:88a998572049b511a1f3e44f4aa7c62dd39c6ea2aa1bb58434f503956641789d"},
     {file = "scapy-2.6.1.tar.gz", hash = "sha256:7600d7e2383c853e5c3a6e05d37e17643beebf2b3e10d7914dffcc3bc3c6e6c5"},
@@ -613,6 +813,7 @@ version = "0.10.2"
 description = "Python Library for Tom's Obvious, Minimal Language"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+groups = ["dev"]
 files = [
     {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
     {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
@@ -624,6 +825,8 @@ version = "2.2.1"
 description = "A lil' TOML parser"
 optional = false
 python-versions = ">=3.8"
+groups = ["dev"]
+markers = "python_version == \"3.10\""
 files = [
     {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
     {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
@@ -665,6 +868,7 @@ version = "2.0.0.10"
 description = "Typing stubs for invoke"
 optional = false
 python-versions = ">=3.7"
+groups = ["dev"]
 files = [
     {file = "types-invoke-2.0.0.10.tar.gz", hash = "sha256:a54d7ecdc19e0c22cd2786ef2e64c2631715c78eba8a1bf40b511d0608f33a88"},
     {file = "types_invoke-2.0.0.10-py3-none-any.whl", hash = "sha256:2404e4279601fa96e14ef68321fd10a660a828677aabdcaeef6a5189778084ef"},
@@ -672,13 +876,14 @@ files = [
 
 [[package]]
 name = "types-paramiko"
-version = "3.5.0.20240928"
+version = "4.0.0.20260518"
 description = "Typing stubs for paramiko"
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.10"
+groups = ["dev"]
 files = [
-    {file = "types-paramiko-3.5.0.20240928.tar.gz", hash = "sha256:79dd9b2ee510b76a3b60d8ac1f3f348c45fcecf01347ca79e14db726bbfc442d"},
-    {file = "types_paramiko-3.5.0.20240928-py3-none-any.whl", hash = "sha256:cda0aff4905fe8efe4b5448331a80e943d42a796bd4beb77a3eed3485bc96a85"},
+    {file = "types_paramiko-4.0.0.20260518-py3-none-any.whl", hash = "sha256:0ffaf1a6eb796833a49653cba4c7be13af51c8269d75234972d6239763dda270"},
+    {file = "types_paramiko-4.0.0.20260518.tar.gz", hash = "sha256:286f6830945cba63797eedf375ed87138d93198121253afe66c5d6dbcf91318d"},
 ]
 
 [package.dependencies]
@@ -686,13 +891,14 @@ cryptography = ">=37.0.0"
 
 [[package]]
 name = "types-pyyaml"
-version = "6.0.12.20240917"
+version = "6.0.12.20260518"
 description = "Typing stubs for PyYAML"
 optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.10"
+groups = ["dev"]
 files = [
-    {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"},
-    {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"},
+    {file = "types_pyyaml-6.0.12.20260518-py3-none-any.whl", hash = "sha256:d2150f75a231c9fe9c7463bd29487d93e60bac90400287351384bc2284eba7cd"},
+    {file = "types_pyyaml-6.0.12.20260518.tar.gz", hash = "sha256:d917f83fb38462550338c1297faedd860b3ec83912b96b1e3d73255f7473e466"},
 ]
 
 [[package]]
@@ -701,17 +907,33 @@ version = "4.12.2"
 description = "Backported and Experimental Type Hints for Python 3.8+"
 optional = false
 python-versions = ">=3.8"
+groups = ["main", "dev"]
+markers = "python_version < \"3.15\""
 files = [
     {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
     {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
 ]
 
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+description = "Backported and Experimental Type Hints for Python 3.9+"
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+markers = "python_version >= \"3.15\""
+files = [
+    {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
+    {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
+]
+
 [[package]]
 name = "wrapt"
 version = "1.17.2"
 description = "Module for decorators, wrappers and monkey patching."
 optional = false
 python-versions = ">=3.8"
+groups = ["main"]
 files = [
     {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"},
     {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"},
@@ -795,6 +1017,6 @@ files = [
 ]
 
 [metadata]
-lock-version = "2.0"
+lock-version = "2.1"
 python-versions = "^3.10"
-content-hash = "aa6dff54827602c89ee125019291965de50d7a471ca48add889ea23aa5fd9b2f"
+content-hash = "1ca3cdf5bf98c528845b5e22169322db8912fe437988635f4ebea088b2079463"
diff --git a/dts/pyproject.toml b/dts/pyproject.toml
index 8b061c3cee..b639af65b6 100644
--- a/dts/pyproject.toml
+++ b/dts/pyproject.toml
@@ -28,12 +28,12 @@ aenum = "^3.1.15"
 pydantic = "^2.9.2"
 
 [tool.poetry.group.dev.dependencies]
-mypy = "^1.13.0"
+mypy = "^2.1.0"
 toml = "^0.10.2"
-ruff = "^0.8.1"
-types-paramiko = "^3.5.0.20240928"
+ruff = "^0.15.16"
+types-paramiko = "^4.0.0.20260518"
 types-invoke = "^2.0.0.10"
-types-pyyaml = "^6.0.12.20240917"
+types-pyyaml = "^6.0.12.20260518"
 
 [build-system]
 requires = ["poetry-core>=1.0.0"]
diff --git a/dts/tests/TestSuite_cryptodev_throughput.py b/dts/tests/TestSuite_cryptodev_throughput.py
index af0a5680ab..2fc0d8779a 100644
--- a/dts/tests/TestSuite_cryptodev_throughput.py
+++ b/dts/tests/TestSuite_cryptodev_throughput.py
@@ -101,12 +101,12 @@ def _print_stats(self, test_vals: list[dict[str, int | float | str]]) -> None:
         print(f"{'Throughput Results'.center(border_len)}\n{'=' * border_len}")
         for k, v in test_vals[0].items():
             print(f"|{k.title():<{element_len}}", end="")
-        print(f"|\n{'='*border_len}")
+        print(f"|\n{'=' * border_len}")
 
         for test_val in test_vals:
             for k, v in test_val.items():
                 print(f"|{v:<{element_len}}", end="")
-            print(f"|\n{'='*border_len}")
+            print(f"|\n{'=' * border_len}")
 
     def _verify_throughput(
         self,
diff --git a/dts/tests/TestSuite_port_control.py b/dts/tests/TestSuite_port_control.py
index 6be47838d0..b51fdc2959 100644
--- a/dts/tests/TestSuite_port_control.py
+++ b/dts/tests/TestSuite_port_control.py
@@ -44,8 +44,7 @@ def _send_packets_and_verify(self) -> None:
         recv_pakts = [
             p
             for p in recv_pakts
-            if
-            (
+            if (
                 # Remove padding from the bytes.
                 hasattr(p, "load") and p.load.decode("utf-8").replace("\x00", "") == payload
             )
diff --git a/dts/tests/TestSuite_single_core_forward_perf.py b/dts/tests/TestSuite_single_core_forward_perf.py
index 1e7ab7b036..acdf8ae2f6 100644
--- a/dts/tests/TestSuite_single_core_forward_perf.py
+++ b/dts/tests/TestSuite_single_core_forward_perf.py
@@ -144,7 +144,7 @@ def single_core_forward_perf(self) -> None:
         for params in self.test_parameters:
             verify(
                 params["pass"] is True,
-                f"""Packets forwarded is less than {(1 -self.delta_tolerance)*100}%
+                f"""Packets forwarded is less than {(1 - self.delta_tolerance) * 100}%
                 of the expected baseline.
                 Measured MPPS = {params["measured_mpps"]}
                 Expected MPPS = {params["expected_mpps"]}""",
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 2/2] dts: add build arguments to test run configuration
From: Koushik Bhargav Nimoji @ 2026-06-09 14:36 UTC (permalink / raw)
  To: luca.vizzarro, patrickrobb1997
  Cc: dev, abailey, ahassick, lylavoie, Koushik Bhargav Nimoji
In-Reply-To: <20260609143647.1434076-1-knimoji@iol.unh.edu>

This patch adds the ability to specify build arguments when building DPDK
through DTS. Doing so allows users to build DPDK with the desired build
arguments, which allows for a more configurable DTS run.

Signed-off-by: Koushik Bhargav Nimoji <knimoji@iol.unh.edu>
---
 dts/configurations/test_run.example.yaml | 13 +++++++++++++
 dts/framework/config/test_run.py         |  2 ++
 dts/framework/remote_session/dpdk.py     | 12 ++++++++----
 dts/framework/utils.py                   | 21 ++++++++++++++++++++-
 4 files changed, 43 insertions(+), 5 deletions(-)

diff --git a/dts/configurations/test_run.example.yaml b/dts/configurations/test_run.example.yaml
index ee641f5dce..0bd5151801 100644
--- a/dts/configurations/test_run.example.yaml
+++ b/dts/configurations/test_run.example.yaml
@@ -16,6 +16,8 @@
 #       `precompiled_build_dir` or `build_options` can be defined, but not both.
 #   `compiler_wrapper`:
 #       Optional, adds a compiler wrapper if present.
+#   `build_args`:
+#       The additional build arguments to be used when building DPDK.
 #   `func_traffic_generator` & `perf_traffic_generator`:
 #       Define `func_traffic_generator` when `func` set to true.
 #       Define `perf_traffic_generator` when `perf` set to true.
@@ -40,6 +42,17 @@ dpdk:
       # the combination of the following two makes CC="ccache gcc"
       compiler: gcc
       compiler_wrapper: ccache # see `Optional Fields`
+      # arguments to be used when building DPDK
+      # build_args:
+      #   c_args:
+      #     - O3
+      #     - g
+      #   b_coverage:
+      #     - "true"
+      #   buildtype:
+      #     - release
+      #   flags:
+      #     - strip
 func_traffic_generator:
   type: SCAPY
 # perf_traffic_generator:
diff --git a/dts/framework/config/test_run.py b/dts/framework/config/test_run.py
index 76e24d1785..eab12041fc 100644
--- a/dts/framework/config/test_run.py
+++ b/dts/framework/config/test_run.py
@@ -191,6 +191,8 @@ class DPDKBuildOptionsConfiguration(FrozenModel):
     #: This string will be put in front of the compiler when executing the build. Useful for adding
     #: wrapper commands, such as ``ccache``.
     compiler_wrapper: str = ""
+    #: The build arguments to build dpdk with
+    build_args: dict[str, list[str]] = {}
 
 
 class DPDKUncompiledBuildConfiguration(BaseDPDKBuildConfiguration):
diff --git a/dts/framework/remote_session/dpdk.py b/dts/framework/remote_session/dpdk.py
index 865f97f6ca..4dc0ceeaaf 100644
--- a/dts/framework/remote_session/dpdk.py
+++ b/dts/framework/remote_session/dpdk.py
@@ -100,8 +100,8 @@ def setup(self) -> None:
         match self.config:
             case DPDKPrecompiledBuildConfiguration(precompiled_build_dir=build_dir):
                 self._set_remote_dpdk_build_dir(build_dir)
-            case DPDKUncompiledBuildConfiguration(build_options=build_options):
-                self._configure_dpdk_build(build_options)
+            case DPDKUncompiledBuildConfiguration():
+                self._configure_dpdk_build(self.config.build_options)
                 self._build_dpdk()
 
     def teardown(self) -> None:
@@ -277,16 +277,20 @@ def _build_dpdk(self) -> None:
         `remote_dpdk_tree_path` has already been set on the SUT node.
         """
         ctx = get_ctx()
+        build_options = getattr(self.config, "build_options")
         # If the SUT is an ice driver device, make sure to build with 16B descriptors.
         if (
             ctx.topology.sut_port_ingress
             and ctx.topology.sut_port_ingress.config.os_driver == "ice"
         ):
             meson_args = MesonArgs(
-                default_library="static", libdir="lib", c_args="-DRTE_NET_INTEL_USE_16BYTE_DESC"
+                build_options.build_args,
+                default_library="static",
+                libdir="lib",
+                c_args="-DRTE_NET_INTEL_USE_16BYTE_DESC",
             )
         else:
-            meson_args = MesonArgs(default_library="static", libdir="lib")
+            meson_args = MesonArgs(build_options.build_args, default_library="static", libdir="lib")
 
         if SETTINGS.code_coverage:
             meson_args._add_arg("-Db_coverage=true")
diff --git a/dts/framework/utils.py b/dts/framework/utils.py
index 38da88cd9c..e0ed35066c 100644
--- a/dts/framework/utils.py
+++ b/dts/framework/utils.py
@@ -99,10 +99,16 @@ class MesonArgs:
 
     _default_library: str
 
-    def __init__(self, default_library: str | None = None, **dpdk_args: str | bool):
+    def __init__(
+        self,
+        dpdk_build_args: dict[str, list[str]],
+        default_library: str | None = None,
+        **dpdk_args: str | bool,
+    ):
         """Initialize the meson arguments.
 
         Args:
+            dpdk_build_args: The DPDK build arguments specified in the test run configuration file.
             default_library: The default library type, Meson supports ``shared``, ``static`` and
                 ``both``. Defaults to :data:`None`, in which case the argument won't be used.
             dpdk_args: The arguments found in ``meson_options.txt`` in root DPDK directory.
@@ -121,6 +127,19 @@ def __init__(self, default_library: str | None = None, **dpdk_args: str | bool):
             )
         )
 
+        arguments = []
+        for option, value in dpdk_build_args.items():
+            if option == "c_args":
+                values = " ".join(f"-{val}" for val in value)
+                arguments.append(f'-D{option}="{values}"')
+            elif option == "flags":
+                values = " ".join(f"--{val}" for val in value)
+                arguments.append(values)
+            else:
+                arguments.append(f" -D{option}={value[0]}")
+
+        self._dpdk_args = " ".join(arguments)
+
     def __str__(self) -> str:
         """The actual args."""
         return " ".join(f"{self._default_library} {self._dpdk_args}".split())
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2 1/2] dts: add code coverage reporting to DTS
From: Koushik Bhargav Nimoji @ 2026-06-09 14:36 UTC (permalink / raw)
  To: luca.vizzarro, patrickrobb1997
  Cc: dev, abailey, ahassick, lylavoie, Koushik Bhargav Nimoji
In-Reply-To: <20260522154637.952588-1-knimoji@iol.unh.edu>

Previously, DTS had no code coverage. This patch adds a command line
argument in order to build DPDK with code coverage enabled. This allows
users to create and view code coverage reports of what code and functions
were called during a DTS run.

Signed-off-by: Koushik Bhargav Nimoji <knimoji@iol.unh.edu>
---
 .mailmap                                      |  1 +
 doc/guides/tools/dts.rst                      | 15 +++++++++++++
 dts/README.md                                 |  5 +++++
 dts/framework/remote_session/dpdk.py          | 19 ++++++++++++++++
 .../remote_session/remote_session.py          |  5 ++++-
 dts/framework/settings.py                     | 10 +++++++++
 dts/framework/testbed_model/os_session.py     |  8 +++++++
 dts/framework/testbed_model/posix_session.py  | 22 +++++++++++++++++++
 dts/framework/utils.py                        |  8 +++++++
 9 files changed, 92 insertions(+), 1 deletion(-)

diff --git a/.mailmap b/.mailmap
index e052b85213..a1209150ad 100644
--- a/.mailmap
+++ b/.mailmap
@@ -877,6 +877,7 @@ Klaus Degner <kd@allegro-packets.com>
 Kommula Shiva Shankar <kshankar@marvell.com>
 Konstantin Ananyev <konstantin.ananyev@huawei.com> <konstantin.v.ananyev@yandex.ru>
 Konstantin Ananyev <konstantin.ananyev@huawei.com> <konstantin.ananyev@intel.com>
+Koushik Bhargav Nimoji <knimoji@iol.unh.edu>
 Krishna Murthy <krishna.j.murthy@intel.com>
 Krzysztof Galazka <krzysztof.galazka@intel.com>
 Krzysztof Kanas <kkanas@marvell.com> <krzysztof.kanas@caviumnetworks.com>
diff --git a/doc/guides/tools/dts.rst b/doc/guides/tools/dts.rst
index 5b9a348016..a838a317ee 100644
--- a/doc/guides/tools/dts.rst
+++ b/doc/guides/tools/dts.rst
@@ -352,6 +352,10 @@ DTS is run with ``main.py`` located in the ``dts`` directory using the ``poetry
      --precompiled-build-dir DIR_NAME
                            [DTS_PRECOMPILED_BUILD_DIR] Define the subdirectory under the DPDK tree root directory or tarball where the pre-
                            compiled binaries are located. (default: None)
+     --code-coverage       Builds DPDK on the SUT node with code coverage enabled. Generates a code coverage report which can be found on
+                           the local filesystem at dts/output/coverage_reports/meson-logs/coveragereport/index.html, or the specified output
+                           directory. To use code coverage, please ensure lcov v1.15 and gcov v8.0 or higher (included in gcc package) are
+                           installed on the SUT node.
 
 
 The brackets contain the names of environment variables that set the same thing.
@@ -367,6 +371,17 @@ Results are stored in the output dir by default
 which be changed with the ``--output-dir`` command line argument.
 The results contain basic statistics of passed/failed test cases and DPDK version.
 
+Code Coverage
+~~~~~~~~~~~~~
+
+DTS has the ablilty to track code usage during test runs, and generate an HTML
+coverage report with that data. This can be done by using the "--code-coverage"
+CLI parameter when running DTS.
+
+To use code coverage, please make sure the following dependencies are available
+on the SUT node:
+- lcov v1.15
+- gcov v8.0 or greater (included in gcc package)
 
 Contributing to DTS
 -------------------
diff --git a/dts/README.md b/dts/README.md
index d257b7a167..51f824e077 100644
--- a/dts/README.md
+++ b/dts/README.md
@@ -64,6 +64,11 @@ $ poetry run ./main.py
 These commands will give you a bash shell inside a docker container
 with all DTS Python dependencies installed.
 
+# Code Coverage
+
+To generate code coverage reports, ensure the SUT has lcov v1.15 and gcov v8.0 or greater
+installed, and that DTS is run using the '--code-coverage' argument.
+
 ## Visual Studio Code
 
 Usage of VScode devcontainers is NOT required for developing on DTS and running DTS,
diff --git a/dts/framework/remote_session/dpdk.py b/dts/framework/remote_session/dpdk.py
index c3575cfcaf..865f97f6ca 100644
--- a/dts/framework/remote_session/dpdk.py
+++ b/dts/framework/remote_session/dpdk.py
@@ -29,6 +29,7 @@
 from framework.logger import DTSLogger, get_dts_logger
 from framework.params.eal import EalParams
 from framework.remote_session.remote_session import CommandResult
+from framework.settings import SETTINGS
 from framework.testbed_model.cpu import LogicalCore, LogicalCoreCount, LogicalCoreList, lcore_filter
 from framework.testbed_model.node import Node
 from framework.testbed_model.os_session import OSSession
@@ -107,7 +108,22 @@ def teardown(self) -> None:
         """Teardown the DPDK build on the target node.
 
         Removes the DPDK tree and/or build directory/tarball depending on the configuration.
+        If code coverage is enabled, the coverage report and .info file are generated and
+        copied onto the local filesystem before teardown.
         """
+        if SETTINGS.code_coverage:
+            report_folder = PurePath(self.remote_dpdk_build_dir / "meson-logs")
+            output_dir = SETTINGS.output_dir
+            Path(output_dir).mkdir(parents=True, exist_ok=True)
+
+            coverage_status = self._session.generate_coverage_report(self.remote_dpdk_build_dir)
+            if coverage_status:
+                self._session.copy_dir_from(report_folder, output_dir)
+                self._logger.info(
+                    "Coverage HTML report generated, "
+                    f"available at {output_dir}/meson-logs/coveragereports/index.html"
+                )
+
         match self.config.dpdk_location:
             case LocalDPDKTreeLocation():
                 self._node.main_session.remove_remote_dir(self.remote_dpdk_tree_path)
@@ -272,6 +288,9 @@ def _build_dpdk(self) -> None:
         else:
             meson_args = MesonArgs(default_library="static", libdir="lib")
 
+        if SETTINGS.code_coverage:
+            meson_args._add_arg("-Db_coverage=true")
+
         self._session.build_dpdk(
             self._env_vars,
             meson_args,
diff --git a/dts/framework/remote_session/remote_session.py b/dts/framework/remote_session/remote_session.py
index 158325bb7f..d2440dc2d8 100644
--- a/dts/framework/remote_session/remote_session.py
+++ b/dts/framework/remote_session/remote_session.py
@@ -252,7 +252,10 @@ def copy_from(self, source_file: str | PurePath, destination_dir: str | Path) ->
             destination_dir: The directory path on the local filesystem where the `source_file`
                 will be saved.
         """
-        self.session.get(str(source_file), str(destination_dir))
+        source_file = PurePath(source_file)
+        destination_dir = Path(destination_dir)
+        local_path = destination_dir / source_file.name
+        self.session.get(str(source_file), str(local_path))
 
     def copy_to(self, source_file: str | Path, destination_dir: str | PurePath) -> None:
         """Copy a file from local filesystem to the remote Node.
diff --git a/dts/framework/settings.py b/dts/framework/settings.py
index b08373b7ea..7df535bd84 100644
--- a/dts/framework/settings.py
+++ b/dts/framework/settings.py
@@ -159,6 +159,8 @@ class Settings:
     re_run: int = 0
     #:
     random_seed: int | None = None
+    #:
+    code_coverage: bool = False
 
 
 SETTINGS: Settings = Settings()
@@ -489,6 +491,14 @@ def _get_parser() -> _DTSArgumentParser:
     )
     _add_env_var_to_action(action)
 
+    action = parser.add_argument(
+        "--code-coverage",
+        action="store_true",
+        default=False,
+        help="Used to build DPDK with code coverage enabled.",
+    )
+    _add_env_var_to_action(action)
+
     return parser
 
 
diff --git a/dts/framework/testbed_model/os_session.py b/dts/framework/testbed_model/os_session.py
index 2c267afed1..a48383d1f1 100644
--- a/dts/framework/testbed_model/os_session.py
+++ b/dts/framework/testbed_model/os_session.py
@@ -480,6 +480,14 @@ def build_dpdk(
             timeout: Wait at most this long in seconds for the build execution to complete.
         """
 
+    @abstractmethod
+    def generate_coverage_report(self, remote_build_dir: PurePath | None) -> int:
+        """Generates a code coverage report for a DTS run.
+
+        Args:
+            remote_build_dir: The remote DPDK build directory
+        """
+
     @abstractmethod
     def get_dpdk_version(self, version_path: str | PurePath) -> str:
         """Inspect the DPDK version on the remote node.
diff --git a/dts/framework/testbed_model/posix_session.py b/dts/framework/testbed_model/posix_session.py
index dec952685a..36ac9be7cf 100644
--- a/dts/framework/testbed_model/posix_session.py
+++ b/dts/framework/testbed_model/posix_session.py
@@ -295,6 +295,28 @@ def build_dpdk(
         except RemoteCommandExecutionError as e:
             raise DPDKBuildError(f"DPDK build failed when doing '{e.command}'.")
 
+    def generate_coverage_report(self, remote_build_dir: PurePath | None):
+        """Overrides :meth:`~.os_session.OSSession.generate_coverage_report`."""
+        command_result = self.send_command(r"lcov --version | grep -oP '\d+\.\d+'")
+        lcov_version = float(
+            command_result.stdout if command_result.return_code == 0 and command_result else -1
+        )
+        command_result = self.send_command(
+            r"gcov --version | head -n 1 | grep -oP '\d+\.\d+' | tail -n 1"
+        )
+        gcov_version = float(
+            command_result.stdout if command_result.return_code == 0 and command_result else -1
+        )
+
+        if lcov_version == 1.15 and gcov_version >= 8.0:
+            self.send_command(f"ninja -C {remote_build_dir} coverage-html", timeout=600)
+            return True
+        else:
+            self._logger.info(
+                "Unable to generate code coverage report, ensure lcov v1.5 and at least gcov v8.0"
+            )
+            return False
+
     def get_dpdk_version(self, build_dir: str | PurePath) -> str:
         """Overrides :meth:`~.os_session.OSSession.get_dpdk_version`."""
         out = self.send_command(f"cat {self.join_remote_path(build_dir, 'VERSION')}", verify=True)
diff --git a/dts/framework/utils.py b/dts/framework/utils.py
index 9917ffbfaa..38da88cd9c 100644
--- a/dts/framework/utils.py
+++ b/dts/framework/utils.py
@@ -125,6 +125,14 @@ def __str__(self) -> str:
         """The actual args."""
         return " ".join(f"{self._default_library} {self._dpdk_args}".split())
 
+    def _add_arg(self, arg: str):
+        """Used to add a meson build argument to the DPDK build.
+
+        Args:
+            arg: The meson build argument to be added.
+        """
+        self._dpdk_args = self._dpdk_args + " " + arg
+
 
 class TarCompressionFormat(StrEnum):
     """Compression formats that tar can use.
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH v4 5/5] eal: avoid deadlock in async IPC alarm callback
From: Stephen Hemminger @ 2026-06-09 14:32 UTC (permalink / raw)
  To: Burakov, Anatoly; +Cc: dev, Jianfeng Tan
In-Reply-To: <9f0b651c-13dd-44a1-bf72-860202f8cd99@intel.com>

On Tue, 9 Jun 2026 10:04:22 +0200
"Burakov, Anatoly" <anatoly.burakov@intel.com> wrote:

> On 6/5/2026 4:29 PM, Anatoly Burakov wrote:
> > async_reply_handle_thread_unsafe() can run while holding
> > pending_requests.lock and currently calls rte_eal_alarm_cancel().
> > 
> > rte_eal_alarm_cancel() may spin-wait for an executing callback, which can
> > deadlock if that callback is blocked on the same lock.
> > 
> > Remove callback-side alarm cancellation. It is safe to do so, because any
> > callback triggered without a pending request becomes a noop.
> > 
> > Fixes: daf9bfca717e ("ipc: remove thread for async requests")
> > Cc: stable@dpdk.org
> > 
> > Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> > ---  
> 
> Okay, the AI review seems to keep flagging issues that are technically 
> true in the patches, but are intentional and do get better once the 
> complete patchset is applied.
> 
> Looks like I need to merge some of the patches or rethink the order in 
> which the fixes are applied to avoid these issues.

The automated AI review has limited scope; it never looks at patch set in total,
and doesn't have tools to read source. That is why for complex things I tend
to start a new session and give it everything. Does much better job then.

^ permalink raw reply

* Re: [PATCH 1/3] net/iavf: downgrade opcode 0 ARQ log to debug
From: Bruce Richardson @ 2026-06-09 14:28 UTC (permalink / raw)
  To: Ciara Loftus; +Cc: dev, Talluri Chaitanyababu
In-Reply-To: <20260608145518.1705524-2-ciara.loftus@intel.com>

On Mon, Jun 08, 2026 at 02:55:16PM +0000, Ciara Loftus wrote:
> From: Talluri Chaitanyababu <chaitanyababux.talluri@intel.com>
> 
> After admin queue reinitialisation, completions from uninitialised
> ARQ ring descriptor memory may arrive before any real PF response.
> These carry opcode 0 (`VIRTCHNL_OP_UNKNOWN`) and trigger a WARNING
> log on every poll iteration, flooding the log during reset recovery.
> 
> Treat opcode 0 as a distinct case and log it at DEBUG level, while
> retaining WARNING for genuine opcode mismatches.
> 
> Signed-off-by: Talluri Chaitanyababu <chaitanyababux.talluri@intel.com>
> ---
>  drivers/net/intel/iavf/iavf_vchnl.c | 11 +++++++++--
>  1 file changed, 9 insertions(+), 2 deletions(-)
> 
Should this be backported as a bugfix?

^ permalink raw reply

* Fwd: [PATCH v1 2/2] dts: add build arguments to test run configuration
From: Koushik Bhargav Nimoji @ 2026-06-09 14:27 UTC (permalink / raw)
  To: dev
In-Reply-To: <20260608132303.1099012-2-knimoji@iol.unh.edu>

[-- Attachment #1: Type: text/plain, Size: 6433 bytes --]

---------- Forwarded message ---------
From: Koushik Bhargav Nimoji <knimoji@iol.unh.edu>
Date: Mon, Jun 8, 2026 at 9:23 AM
Subject: [PATCH v1 2/2] dts: add build arguments to test run configuration
To: <luca.vizzarro@arm.com>, <patrickrobb1997@gmail.com>
Cc: <abailey@iol.unh.edu>, <ahassick@iol.unh.edu>, <lylavoie@iol.unh.edu>,
Koushik Bhargav Nimoji <knimoji@iol.unh.edu>


This patch adds the ability to specify build arguments when building DPDK
through DTS. Doing so allows users to build DPDK with the desired build
arguments, which allows for a more configurable DTS run.

Signed-off-by: Koushik Bhargav Nimoji <knimoji@iol.unh.edu>
---
 dts/configurations/test_run.example.yaml | 13 +++++++++++++
 dts/framework/config/test_run.py         |  2 ++
 dts/framework/remote_session/dpdk.py     | 12 ++++++++----
 dts/framework/utils.py                   | 21 ++++++++++++++++++++-
 4 files changed, 43 insertions(+), 5 deletions(-)

diff --git a/dts/configurations/test_run.example.yaml
b/dts/configurations/test_run.example.yaml
index ee641f5dce..0bd5151801 100644
--- a/dts/configurations/test_run.example.yaml
+++ b/dts/configurations/test_run.example.yaml
@@ -16,6 +16,8 @@
 #       `precompiled_build_dir` or `build_options` can be defined, but not
both.
 #   `compiler_wrapper`:
 #       Optional, adds a compiler wrapper if present.
+#   `build_args`:
+#       The additional build arguments to be used when building DPDK.
 #   `func_traffic_generator` & `perf_traffic_generator`:
 #       Define `func_traffic_generator` when `func` set to true.
 #       Define `perf_traffic_generator` when `perf` set to true.
@@ -40,6 +42,17 @@ dpdk:
       # the combination of the following two makes CC="ccache gcc"
       compiler: gcc
       compiler_wrapper: ccache # see `Optional Fields`
+      # arguments to be used when building DPDK
+      # build_args:
+      #   c_args:
+      #     - O3
+      #     - g
+      #   b_coverage:
+      #     - "true"
+      #   buildtype:
+      #     - release
+      #   flags:
+      #     - strip
 func_traffic_generator:
   type: SCAPY
 # perf_traffic_generator:
diff --git a/dts/framework/config/test_run.py
b/dts/framework/config/test_run.py
index 76e24d1785..eab12041fc 100644
--- a/dts/framework/config/test_run.py
+++ b/dts/framework/config/test_run.py
@@ -191,6 +191,8 @@ class DPDKBuildOptionsConfiguration(FrozenModel):
     #: This string will be put in front of the compiler when executing the
build. Useful for adding
     #: wrapper commands, such as ``ccache``.
     compiler_wrapper: str = ""
+    #: The build arguments to build dpdk with
+    build_args: dict[str, list[str]] = {}


 class DPDKUncompiledBuildConfiguration(BaseDPDKBuildConfiguration):
diff --git a/dts/framework/remote_session/dpdk.py
b/dts/framework/remote_session/dpdk.py
index 865f97f6ca..4dc0ceeaaf 100644
--- a/dts/framework/remote_session/dpdk.py
+++ b/dts/framework/remote_session/dpdk.py
@@ -100,8 +100,8 @@ def setup(self) -> None:
         match self.config:
             case
DPDKPrecompiledBuildConfiguration(precompiled_build_dir=build_dir):
                 self._set_remote_dpdk_build_dir(build_dir)
-            case
DPDKUncompiledBuildConfiguration(build_options=build_options):
-                self._configure_dpdk_build(build_options)
+            case DPDKUncompiledBuildConfiguration():
+                self._configure_dpdk_build(self.config.build_options)
                 self._build_dpdk()

     def teardown(self) -> None:
@@ -277,16 +277,20 @@ def _build_dpdk(self) -> None:
         `remote_dpdk_tree_path` has already been set on the SUT node.
         """
         ctx = get_ctx()
+        build_options = getattr(self.config, "build_options")
         # If the SUT is an ice driver device, make sure to build with 16B
descriptors.
         if (
             ctx.topology.sut_port_ingress
             and ctx.topology.sut_port_ingress.config.os_driver == "ice"
         ):
             meson_args = MesonArgs(
-                default_library="static", libdir="lib",
c_args="-DRTE_NET_INTEL_USE_16BYTE_DESC"
+                build_options.build_args,
+                default_library="static",
+                libdir="lib",
+                c_args="-DRTE_NET_INTEL_USE_16BYTE_DESC",
             )
         else:
-            meson_args = MesonArgs(default_library="static", libdir="lib")
+            meson_args = MesonArgs(build_options.build_args,
default_library="static", libdir="lib")

         if SETTINGS.code_coverage:
             meson_args._add_arg("-Db_coverage=true")
diff --git a/dts/framework/utils.py b/dts/framework/utils.py
index 38da88cd9c..e0ed35066c 100644
--- a/dts/framework/utils.py
+++ b/dts/framework/utils.py
@@ -99,10 +99,16 @@ class MesonArgs:

     _default_library: str

-    def __init__(self, default_library: str | None = None, **dpdk_args:
str | bool):
+    def __init__(
+        self,
+        dpdk_build_args: dict[str, list[str]],
+        default_library: str | None = None,
+        **dpdk_args: str | bool,
+    ):
         """Initialize the meson arguments.

         Args:
+            dpdk_build_args: The DPDK build arguments specified in the
test run configuration file.
             default_library: The default library type, Meson supports
``shared``, ``static`` and
                 ``both``. Defaults to :data:`None`, in which case the
argument won't be used.
             dpdk_args: The arguments found in ``meson_options.txt`` in
root DPDK directory.
@@ -121,6 +127,19 @@ def __init__(self, default_library: str | None = None,
**dpdk_args: str | bool):
             )
         )

+        arguments = []
+        for option, value in dpdk_build_args.items():
+            if option == "c_args":
+                values = " ".join(f"-{val}" for val in value)
+                arguments.append(f'-D{option}="{values}"')
+            elif option == "flags":
+                values = " ".join(f"--{val}" for val in value)
+                arguments.append(values)
+            else:
+                arguments.append(f" -D{option}={value[0]}")
+
+        self._dpdk_args = " ".join(arguments)
+
     def __str__(self) -> str:
         """The actual args."""
         return " ".join(f"{self._default_library}
{self._dpdk_args}".split())
-- 
2.54.0

[-- Attachment #2: Type: text/html, Size: 8448 bytes --]

^ permalink raw reply related

* [PATCH v2] eal: add destructor to unregister tailq on unload
From: Stephen Hemminger @ 2026-06-09 14:26 UTC (permalink / raw)
  To: dev
  Cc: Stephen Hemminger, stable, Bruce Richardson, Neil Horman,
	David Marchand
In-Reply-To: <20260607150418.30885-1-stephen@networkplumber.org>

EAL_REGISTER_TAILQ registers a static rte_tailq_elem from a
constructor but provides no destructor. If a library using the
macro is loaded with dlopen() and later unloaded with dlclose(),
the process-local list keeps a dangling pointer to the unmapped
elem, and the next dlopen() crashes in rte_eal_tailq_local_register()
while walking the list.

Add a new RTE_FINI destructor that is paired with the constructor
in the macro. rte_eal_tailq_unregister() drops the local entry on
unload. The shared mcfg->tailq_head[] slot is left reserved since
it is keyed by name and shared between processes;
rte_eal_tailq_update() now reattaches to that slot on re-register
instead of failing.

Bugzilla ID: 1081
Fixes: 873a61c7526b ("tailq: introduce dynamic register system")
Cc: stable@dpdk.org

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
Acked-by: Bruce Richardson <bruce.richardson@intel.com>
---
v2 - cover the case where name still is reserved

 lib/eal/common/eal_common_tailqs.c | 13 +++++++++++++
 lib/eal/include/rte_tailq.h        | 16 ++++++++++++++++
 2 files changed, 29 insertions(+)

diff --git a/lib/eal/common/eal_common_tailqs.c b/lib/eal/common/eal_common_tailqs.c
index c581f43b6f..9355c108f2 100644
--- a/lib/eal/common/eal_common_tailqs.c
+++ b/lib/eal/common/eal_common_tailqs.c
@@ -113,6 +113,11 @@ rte_eal_tailq_update(struct rte_tailq_elem *t)
 	if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
 		/* primary process is the only one that creates */
 		t->head = rte_eal_tailq_create(t->name);
+
+		if (t->head == NULL) {
+			/* slot reserved by an earlier load -- reuse it */
+			t->head = rte_eal_tailq_lookup(t->name);
+		}
 	} else {
 		t->head = rte_eal_tailq_lookup(t->name);
 	}
@@ -148,6 +153,14 @@ rte_eal_tailq_register(struct rte_tailq_elem *t)
 	return -1;
 }
 
+RTE_EXPORT_SYMBOL(rte_eal_tailq_unregister)
+void
+rte_eal_tailq_unregister(struct rte_tailq_elem *t)
+{
+	TAILQ_REMOVE(&rte_tailq_elem_head, t, next);
+	t->head = NULL;
+}
+
 int
 rte_eal_tailqs_init(void)
 {
diff --git a/lib/eal/include/rte_tailq.h b/lib/eal/include/rte_tailq.h
index e7caed6812..d4d8bfd6d4 100644
--- a/lib/eal/include/rte_tailq.h
+++ b/lib/eal/include/rte_tailq.h
@@ -117,11 +117,27 @@ struct rte_tailq_head *rte_eal_tailq_lookup(const char *name);
  */
 int rte_eal_tailq_register(struct rte_tailq_elem *t);
 
+/**
+ * Remove a tail queue element from the local list.
+ * This function is mainly used for EAL_REGISTER_TAILQ macro which pairs
+ * an RTE_FINI destructor with the existing RTE_INIT constructor.
+ * The destructor calls this function during dlclose() to prevent
+ * dangling pointers to unmapped library data.
+ *
+ * @param t
+ *   The tailq element to remove from the EAL tailq list.
+ */
+void rte_eal_tailq_unregister(struct rte_tailq_elem *t);
+
 #define EAL_REGISTER_TAILQ(t) \
 RTE_INIT(tailqinitfn_ ##t) \
 { \
 	if (rte_eal_tailq_register(&t) < 0) \
 		rte_panic("Cannot initialize tailq: %s\n", t.name); \
+} \
+RTE_FINI(tailqfinifn_ ##t) \
+{ \
+	rte_eal_tailq_unregister(&t); \
 }
 
 /* This macro permits both remove and free var within the loop safely.*/
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH v2] net/iavf: fix misleading AQ failure logging
From: Bruce Richardson @ 2026-06-09 14:05 UTC (permalink / raw)
  To: Loftus, Ciara
  Cc: Mandal, Anurag, dev@dpdk.org, Medvedkin, Vladimir,
	stable@dpdk.org
In-Reply-To: <IA4PR11MB92781E302F372E466C89501C8E1D2@IA4PR11MB9278.namprd11.prod.outlook.com>

On Tue, Jun 09, 2026 at 01:30:47PM +0100, Loftus, Ciara wrote:
> > Subject: [PATCH v2] net/iavf: fix misleading AQ failure logging
> > 
> > iavf_handle_virtchnl_msg() drains the admin receive queue in a loop
> > until iavf_clean_arq_element() reports that no descriptors are
> > pending. When the queue becomes empty, the base driver returns
> > IAVF_ERR_ADMIN_QUEUE_NO_WORK (-57), which is the documented
> > terminator for the drain loop, and is not an error.
> > 
> > The current loop treats every non-IAVF_SUCCESS return as a failure
> > and logs it as follows:
> > 
> > "Failed to read msg from AdminQ, ret: -57"
> > 
> > This message floods the logs on every interrupt cycle and misleads
> > the triage during VF reset by chasing a real virtchnl problem
> > seeing these spurious -57 AQ failure lines in logs and assumes
> > the admin queue is broken, when in fact it has just been drained.
> > 
> > This patch fixes the aforesaid issue by treating
> > IAVF_ERR_ADMIN_QUEUE_NO_WORK in virtchnl message drain as a normal
> > loop exit empty-queue condition and avoid logging it as an misleading
> > AQ failure.
> > 
> > Fixes: 02d212ca3125 ("net/iavf: rename remaining avf strings")

Actually, I think the proper offending commit is earlier:
Fixes: 22b123a36d07 ("net/avf: initialize PMD")

> > Cc: stable@dpdk.org
> > 
> > Signed-off-by: Anurag Mandal <anurag.mandal@intel.com>
> 
> Thanks Anurag.
> 
> Acked-by: Ciara Loftus <ciara.loftus@intel.com>
> 
Patch applied to dpdk-next-net-intel.
Thanks,
/Bruce

^ permalink raw reply

* [PATCH] net/iavf: reject oversized frames in prep callback
From: Ciara Loftus @ 2026-06-09 14:03 UTC (permalink / raw)
  To: dev; +Cc: Ciara Loftus, stable

Currently, only the segment count is checked for non-TSO packets. There
is no upper bound placed on the packet length, so packets larger than
`IAVF_FRAME_SIZE_MAX` pass the prepare callback unchallenged and are
submitted to the hardware. Sending such frames can trigger Malicious
Driver Detection (MDD) on the PF. Fix this by adding a packet length
size check on the non-TSO path.

Fixes: 19ee91c6bd9a ("net/iavf: check illegal packet sizes")
Cc: stable@dpdk.org

Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
---
 drivers/net/intel/iavf/iavf_rxtx.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/intel/iavf/iavf_rxtx.c b/drivers/net/intel/iavf/iavf_rxtx.c
index decbc75142..a57af7faed 100644
--- a/drivers/net/intel/iavf/iavf_rxtx.c
+++ b/drivers/net/intel/iavf/iavf_rxtx.c
@@ -3227,9 +3227,9 @@ iavf_prep_pkts(__rte_unused void *tx_queue, struct rte_mbuf **tx_pkts,
 		m = tx_pkts[i];
 		ol_flags = m->ol_flags;
 
-		/* Check condition for nb_segs > IAVF_TX_MAX_MTU_SEG. */
+		/* Validate segment count and packet length. */
 		if (!(ol_flags & (RTE_MBUF_F_TX_TCP_SEG | RTE_MBUF_F_TX_UDP_SEG))) {
-			if (m->nb_segs > IAVF_TX_MAX_MTU_SEG) {
+			if (m->nb_segs > IAVF_TX_MAX_MTU_SEG || m->pkt_len > IAVF_FRAME_SIZE_MAX) {
 				rte_errno = EINVAL;
 				return i;
 			}
-- 
2.43.0


^ permalink raw reply related

* [PATCH v2] eal: fix function versioning with LTO
From: Stephen Hemminger @ 2026-06-09 13:54 UTC (permalink / raw)
  To: dev; +Cc: Stephen Hemminger
In-Reply-To: <20260602225722.950617-1-stephen@networkplumber.org>

When using function versioning and building with LTO,
GCC gets confused by the symbol versioning using __asm__.
There are no uses of function versioning in upstream repo.
This was found when adding additional parameter to
rte_eth_dev_get_name_by_port.

Assembler messages:
Error: invalid attempt to declare external version name as default in symbol `rte_eth_dev_get_name_by_port@@DPDK_27'

The workaround GCC 10 introduced was an additional function attribute;
clang doesn't have or need this attribute. No need to backport this to
LTS since there is no function versioning in those releases.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
v2 - reword commit message


 lib/eal/common/eal_export.h | 46 +++++++++++++++++++++++++++----------
 1 file changed, 34 insertions(+), 12 deletions(-)

diff --git a/lib/eal/common/eal_export.h b/lib/eal/common/eal_export.h
index 7e47d34064..1318e6278d 100644
--- a/lib/eal/common/eal_export.h
+++ b/lib/eal/common/eal_export.h
@@ -30,39 +30,61 @@
  * new functionality, behavior, etc.  When that occurs, it is desirable to
  * allow for backwards compatibility for a time with older binaries that are
  * dynamically linked to the dpdk.
+ *
+ * RTE_VERSION_SYMBOL
+ * Create a symbol version table entry binding symbol <name>@DPDK_<ver> to the internal
+ * function name <name>_v<ver>.
+ *
+ * RTE_VERSION_EXPERIMENTAL_SYMBOL similar to RTE_VERSION_SYMBOL but for experimental API symbols.
+ * This is mainly used for keeping compatibility for symbols that get promoted to stable ABI.
+ *
+ * RTE_DEFAULT_SYMBOL
+ * Create a symbol version entry instructing the linker to bind references to
+ * symbol <name> to the internal symbol <name>_v<ver>.
  */
 
 #ifdef RTE_BUILD_SHARED_LIB
 
-/*
- * Create a symbol version table entry binding symbol <name>@DPDK_<ver> to the internal
- * function name <name>_v<ver>.
- */
+/* Prefer the compiler method of versioning which uses attributes */
+#if  __has_attribute(symver)
+
+#define RTE_VERSION_SYMBOL(ver, type, name, args) VERSIONING_WARN	\
+	__attribute__((__symver__(RTE_STR(name) "@DPDK_" RTE_STR(ver)))) \
+	type name ## _v ## ver args;					\
+	type name ## _v ## ver args
+
+#define RTE_VERSION_EXPERIMENTAL_SYMBOL(type, name, args) VERSIONING_WARN \
+	__attribute__((__symver__(RTE_STR(name) "@EXPERIMENTAL")))	\
+	type name ## _exp args;						\
+	type name ## _exp args
+
+#define RTE_DEFAULT_SYMBOL(ver, type, name, args) VERSIONING_WARN	\
+	__attribute__((__symver__(RTE_STR(name) "@@DPDK_" RTE_STR(ver)))) \
+	type name ## _v ## ver args;					\
+	type name ## _v ## ver args
+#else /* !__has_attribute(symver) */
+
+/* Use asm tag to create symbol table entry */
 #define RTE_VERSION_SYMBOL(ver, type, name, args) VERSIONING_WARN \
 __asm__(".symver " RTE_STR(name) "_v" RTE_STR(ver) ", " RTE_STR(name) "@DPDK_" RTE_STR(ver)); \
 __rte_used type name ## _v ## ver args; \
 type name ## _v ## ver args
 
-/*
- * Similar to RTE_VERSION_SYMBOL but for experimental API symbols.
- * This is mainly used for keeping compatibility for symbols that get promoted to stable ABI.
- */
 #define RTE_VERSION_EXPERIMENTAL_SYMBOL(type, name, args) VERSIONING_WARN \
 __asm__(".symver " RTE_STR(name) "_exp, " RTE_STR(name) "@EXPERIMENTAL") \
 __rte_used type name ## _exp args; \
 type name ## _exp args
 
-/*
- * Create a symbol version entry instructing the linker to bind references to
- * symbol <name> to the internal symbol <name>_v<ver>.
- */
 #define RTE_DEFAULT_SYMBOL(ver, type, name, args) VERSIONING_WARN \
 __asm__(".symver " RTE_STR(name) "_v" RTE_STR(ver) ", " RTE_STR(name) "@@DPDK_" RTE_STR(ver)); \
 __rte_used type name ## _v ## ver args; \
 type name ## _v ## ver args
 
+#endif /* __has_attribute(symver) */
+
 #else /* !RTE_BUILD_SHARED_LIB */
 
+/* static library does not have versioned symbols */
 #define RTE_VERSION_SYMBOL(ver, type, name, args) VERSIONING_WARN \
 type name ## _v ## ver args; \
 type name ## _v ## ver args
-- 
2.53.0


^ permalink raw reply related

* [PATCH v4] dts: add retry loop to trex traffic generation
From: Andrew Bailey @ 2026-06-09 13:33 UTC (permalink / raw)
  To: patrickrobb1997, luca.vizzarro
  Cc: dev, knimoji, ahassick, lylavoie, Andrew Bailey
In-Reply-To: <20260518175424.253600-1-abailey@iol.unh.edu>

There was an issue where the single core forward test would report zero
MPPS intermittently. This was due to TREX reporting that the link was
down when the client was called to start generating traffic. The links
were being reported down by TREX on the tg even when testpmd was
reporting them up on the SUT. Adding a retry loop to the generate
traffic method of TREX gives the tg enough time to set up and send
traffic.

Bugzilla ID: 1946
Fixes: d77d7f04f24c ("dts: add single-core performance test suite")

Signed-off-by: Andrew Bailey <abailey@iol.unh.edu>
---
 .../testbed_model/traffic_generator/trex.py   | 35 ++++++++++++++-----
 1 file changed, 26 insertions(+), 9 deletions(-)

diff --git a/dts/framework/testbed_model/traffic_generator/trex.py b/dts/framework/testbed_model/traffic_generator/trex.py
index 22cd20dea9..f21892e396 100644
--- a/dts/framework/testbed_model/traffic_generator/trex.py
+++ b/dts/framework/testbed_model/traffic_generator/trex.py
@@ -220,7 +220,9 @@ def _create_packet_stream(self, packet: Packet) -> None:
         ]
         self._shell.send_command("\n".join(packet_stream))
 
-    def _send_traffic_and_get_stats(self, duration: float, send_mpps: float | None = None) -> str:
+    def _send_traffic_and_get_stats(
+        self, duration: float, send_mpps: float | None = None, retry_attempts: int = 5
+    ) -> str:
         """Send traffic and get TG Rx stats.
 
         Sends traffic from the TRex client's ports for the given duration.
@@ -230,15 +232,30 @@ def _send_traffic_and_get_stats(self, duration: float, send_mpps: float | None =
         Args:
             duration: The traffic generation duration.
             send_mpps: The millions of packets per second for TRex to send from each port.
+            retry_attempts: The number of times to retry this command on failure.
+
+        Raises:
+            SSHTimeoutError: If TRex fails to send traffic in the allotted attempts.
         """
-        if send_mpps:
-            self._shell.send_command(f"""{self.stl_client_name}.start(ports=[0, 1],
-                mult = '{send_mpps}mpps',
-                duration = {duration})""")
-        else:
-            self._shell.send_command(f"""{self.stl_client_name}.start(ports=[0, 1],
-                mult = '100%',
-                duration = {duration})""")
+        link_up = False
+        attempt = 0
+
+        while not link_up and attempt < retry_attempts:
+            if send_mpps:
+                result = self._shell.send_command(f"""{self.stl_client_name}.start(ports=[0, 1],
+                    mult = '{send_mpps}mpps',
+                    duration = {duration})""")
+            else:
+                result = self._shell.send_command(f"""{self.stl_client_name}.start(ports=[0, 1],
+                    mult = '100%',
+                    duration = {duration})""")
+            link_up = "link is DOWN" not in result
+            if not link_up:
+                self._logger.info(
+                    f"Generate traffic command failed (attempt {attempt + 1} of {retry_attempts})"
+                )
+                time.sleep(0.25)
+            attempt += 1
 
         time.sleep(duration)
 
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH 0/2] net/intel: fix blocking link wait on device start
From: Bruce Richardson @ 2026-06-09 13:13 UTC (permalink / raw)
  To: Ciara Loftus; +Cc: dev
In-Reply-To: <20260603143407.1108527-1-ciara.loftus@intel.com>

On Wed, Jun 03, 2026 at 02:34:05PM +0000, Ciara Loftus wrote:
> Device start in both ice and i40e has historically blocked for up to
> two seconds waiting for link to come up before returning. This causes
> noticeable startup delay when a link partner is slow to come up or
> absent entirely. This series reverts the wait in ice, and removes the
> wait in i40e while ensuring that the MAC config is applied when the
> link is up which is a requirement for some devices.
> 
> Ciara Loftus (2):
>   net/ice: revert fix link up when starting device
>   net/i40e: fix blocking link wait on device start
> 
>  drivers/net/intel/i40e/i40e_ethdev.c | 25 +++++++++++++++++++++----
>  drivers/net/intel/i40e/i40e_ethdev.h |  2 ++
>  drivers/net/intel/ice/ice_ethdev.c   |  2 +-
>  3 files changed, 24 insertions(+), 5 deletions(-)
> 
> -- 
Series applied to dpdk-next-net-intel.

Thanks,
/Bruce

^ permalink raw reply

* [PATCH v3] dts: add support for no link topology
From: Andrew Bailey @ 2026-06-09 13:11 UTC (permalink / raw)
  To: luca.vizzarro
  Cc: patrickrobb1997, dev, ahassick, knimoji, lylavoie, Andrew Bailey
In-Reply-To: <20260220192510.37163-1-abailey@iol.unh.edu>

Add support for running DTS with no traffic generator node and no ethdev
interfaces. Some applications, like dpdk-test-crypto-perf run without
ethdev interfaces and no traffic generator. In these cases, it is
beneficial to remove the overhead of creating a node and ports that are
not used. The specified build option for ice devices is removed since
the query to sut port ingress causes python to crash when there are no
ports. Notably, since this is only the case in which there are no ports,
traffic will not be sent and therefore the build argument is not
required. For these reasons it is skipped when running a no-link
topology.

Signed-off-by: Andrew Bailey <abailey@iol.unh.edu>
---
 dts/api/test.py                          |  8 +++++---
 dts/configurations/test_run.example.yaml |  2 +-
 dts/framework/config/__init__.py         | 16 ++++++++-------
 dts/framework/config/test_run.py         |  4 ++--
 dts/framework/context.py                 |  2 +-
 dts/framework/remote_session/dpdk.py     |  4 +++-
 dts/framework/test_run.py                | 25 ++++++++++++++----------
 dts/framework/testbed_model/topology.py  | 17 +++++++++++-----
 8 files changed, 48 insertions(+), 30 deletions(-)

diff --git a/dts/api/test.py b/dts/api/test.py
index e17babe0ca..046f1778a5 100644
--- a/dts/api/test.py
+++ b/dts/api/test.py
@@ -109,12 +109,14 @@ def fail(failure_description: str) -> None:
     Raises:
         TestCaseVerifyError: Always raised to indicate the test case failed.
     """
+    ctx = get_ctx()
     get_logger().debug("A test case failed, showing the last 10 commands executed on SUT:")
-    for command_res in get_ctx().sut_node.main_session.remote_session.history[-10:]:
+    for command_res in ctx.sut_node.main_session.remote_session.history[-10:]:
         get_logger().debug(command_res.command)
     get_logger().debug("A test case failed, showing the last 10 commands executed on TG:")
-    for command_res in get_ctx().tg_node.main_session.remote_session.history[-10:]:
-        get_logger().debug(command_res.command)
+    if ctx.tg_node:
+        for command_res in ctx.tg_node.main_session.remote_session.history[-10:]:
+            get_logger().debug(command_res.command)
     raise TestCaseVerifyError(failure_description)
 
 
diff --git a/dts/configurations/test_run.example.yaml b/dts/configurations/test_run.example.yaml
index ee641f5dce..af5f72d305 100644
--- a/dts/configurations/test_run.example.yaml
+++ b/dts/configurations/test_run.example.yaml
@@ -59,7 +59,7 @@ test_suites: # see `Optional Fields`; the following test suites will be run in t
 system_under_test_node: "SUT 1"
 # Traffic generator node to use for this execution environment
 traffic_generator_node: "TG 1"
-port_topology:
+port_topology: # provide empty list for no-link environment
   - sut.port-0 <-> tg.port-0  # explicit link. `sut` and `tg` are special identifiers that refer
                               # to the respective test run's configured nodes.
   - port-1 <-> port-1         # implicit link, left side is always SUT, right side is always TG.
diff --git a/dts/framework/config/__init__.py b/dts/framework/config/__init__.py
index d2f0138e4a..59d566aa0b 100644
--- a/dts/framework/config/__init__.py
+++ b/dts/framework/config/__init__.py
@@ -94,9 +94,10 @@ def validate_port_links(self) -> Self:
                 f"already linked to port {sut_node_port_peer[0]}.{sut_node_port_peer[1]}."
             )
 
-            tg_node_port_peer = existing_port_links.get(
-                (self.test_run.traffic_generator_node, link.tg_port), None
-            )
+            if self.test_run.traffic_generator_node:
+                tg_node_port_peer = existing_port_links.get(
+                    (self.test_run.traffic_generator_node, link.tg_port), None
+                )
             assert (
                 tg_node_port_peer is not None
             ), f"Invalid TG node port specified for link port_topology.{link_idx}."
@@ -122,11 +123,12 @@ def validate_test_run_against_nodes(self) -> Self:
         ), f"The system_under_test_node {sut_node_name} is not a valid node name."
 
         tg_node_name = self.test_run.traffic_generator_node
-        tg_node = next((n for n in self.nodes if n.name == tg_node_name), None)
+        if self.test_run.port_topology:
+            tg_node = next((n for n in self.nodes if n.name == tg_node_name), None)
 
-        assert (
-            tg_node is not None
-        ), f"The traffic_generator_name {tg_node_name} is not a valid node name."
+            assert (
+                tg_node is not None
+            ), f"The traffic_generator_name {tg_node_name} is not a valid node name."
 
         return self
 
diff --git a/dts/framework/config/test_run.py b/dts/framework/config/test_run.py
index 76e24d1785..f9143dfc4e 100644
--- a/dts/framework/config/test_run.py
+++ b/dts/framework/config/test_run.py
@@ -492,11 +492,11 @@ class TestRunConfiguration(FrozenModel):
     #: The SUT node name to use in this test run.
     system_under_test_node: str
     #: The TG node name to use in this test run.
-    traffic_generator_node: str
+    traffic_generator_node: str | None = Field(default=None)
     #: The seed to use for pseudo-random generation.
     random_seed: int | None = None
     #: The port links between the specified nodes to use.
-    port_topology: list[PortLinkConfig] = Field(max_length=2)
+    port_topology: list[PortLinkConfig] = Field(default=[], max_length=2)
 
     fields_from_settings = model_validator(mode="before")(
         load_fields_from_settings("test_suites", "random_seed")
diff --git a/dts/framework/context.py b/dts/framework/context.py
index 8f1021dc96..371473f61c 100644
--- a/dts/framework/context.py
+++ b/dts/framework/context.py
@@ -72,7 +72,7 @@ class Context:
     """Runtime context."""
 
     sut_node: Node
-    tg_node: Node
+    tg_node: Node | None
     topology: Topology
     dpdk_build: "DPDKBuildEnvironment"
     dpdk: "DPDKRuntimeEnvironment"
diff --git a/dts/framework/remote_session/dpdk.py b/dts/framework/remote_session/dpdk.py
index c3575cfcaf..e43e1f2123 100644
--- a/dts/framework/remote_session/dpdk.py
+++ b/dts/framework/remote_session/dpdk.py
@@ -13,6 +13,7 @@
 from pathlib import Path, PurePath
 from typing import ClassVar, Final
 
+from api.capabilities import LinkTopology
 from framework.config.test_run import (
     DPDKBuildConfiguration,
     DPDKBuildOptionsConfiguration,
@@ -263,7 +264,8 @@ def _build_dpdk(self) -> None:
         ctx = get_ctx()
         # If the SUT is an ice driver device, make sure to build with 16B descriptors.
         if (
-            ctx.topology.sut_port_ingress
+            ctx.topology.type is not LinkTopology.NO_LINK
+            and ctx.topology.sut_port_ingress
             and ctx.topology.sut_port_ingress.config.os_driver == "ice"
         ):
             meson_args = MesonArgs(
diff --git a/dts/framework/test_run.py b/dts/framework/test_run.py
index 94dc6023a7..93fc5ee93f 100644
--- a/dts/framework/test_run.py
+++ b/dts/framework/test_run.py
@@ -189,25 +189,28 @@ def __init__(
         self.config = config
         self.logger = get_dts_logger()
 
+        tg_node = None
         sut_node = next(n for n in nodes if n.name == config.system_under_test_node)
-        tg_node = next(n for n in nodes if n.name == config.traffic_generator_node)
-
-        topology = Topology.from_port_links(
-            PortLink(sut_node.ports_by_name[link.sut_port], tg_node.ports_by_name[link.tg_port])
-            for link in self.config.port_topology
-        )
+        if config.traffic_generator_node:
+            tg_node = next(n for n in nodes if n.name == config.traffic_generator_node)
+            topology = Topology.from_port_links(
+                PortLink(sut_node.ports_by_name[link.sut_port], tg_node.ports_by_name[link.tg_port])
+                for link in self.config.port_topology
+            )
+        else:
+            topology = Topology.from_port_links(iter([]))
 
         dpdk_build_env = DPDKBuildEnvironment(config.dpdk.build, sut_node)
         dpdk_runtime_env = DPDKRuntimeEnvironment(config.dpdk, sut_node, dpdk_build_env)
 
         func_traffic_generator = (
             create_traffic_generator(config.func_traffic_generator, tg_node)
-            if config.func and config.func_traffic_generator
+            if config.func and config.func_traffic_generator and tg_node
             else None
         )
         perf_traffic_generator = (
             create_traffic_generator(config.perf_traffic_generator, tg_node)
-            if config.perf and config.perf_traffic_generator
+            if config.perf and config.perf_traffic_generator and tg_node
             else None
         )
 
@@ -343,7 +346,8 @@ def next(self) -> State | None:
         test_run.remaining_tests = deque(test_run.selected_tests)
 
         test_run.ctx.sut_node.setup()
-        test_run.ctx.tg_node.setup()
+        if test_run.ctx.tg_node:
+            test_run.ctx.tg_node.setup()
         test_run.ctx.dpdk.setup()
         test_run.ctx.topology.setup()
 
@@ -455,7 +459,8 @@ def next(self) -> State | None:
             self.test_run.ctx.perf_tg.teardown()
         self.test_run.ctx.topology.teardown()
         self.test_run.ctx.dpdk.teardown()
-        self.test_run.ctx.tg_node.teardown()
+        if self.test_run.ctx.tg_node:
+            self.test_run.ctx.tg_node.teardown()
         self.test_run.ctx.sut_node.teardown()
         return None
 
diff --git a/dts/framework/testbed_model/topology.py b/dts/framework/testbed_model/topology.py
index 34862c4d2e..01333da716 100644
--- a/dts/framework/testbed_model/topology.py
+++ b/dts/framework/testbed_model/topology.py
@@ -73,6 +73,8 @@ def from_port_links(cls, port_links: Iterator[PortLink]) -> Self:
             ConfigurationError: If an unsupported link topology is supplied.
         """
         type = LinkTopology.NO_LINK
+        sut_ports = []
+        tg_ports = []
 
         if port_link := next(port_links, None):
             type = LinkTopology.ONE_LINK
@@ -103,10 +105,10 @@ def node_and_ports_from_id(self, node_identifier: NodeIdentifier) -> tuple[Node,
             case "sut":
                 return ctx.sut_node, self.sut_ports
             case "tg":
-                return ctx.tg_node, self.tg_ports
-            case _:
-                msg = f"Invalid node `{node_identifier}` given."
-                raise InternalError(msg)
+                if ctx.tg_node:
+                    return ctx.tg_node, self.tg_ports
+        msg = f"Invalid node `{node_identifier}` given."
+        raise InternalError(msg)
 
     def get_crypto_vfs(self, num_vfs: int) -> list[Port]:
         """Retrieve virtual functions from active crypto vfs.
@@ -139,6 +141,8 @@ def setup(self) -> None:
 
         Binds all the ports to the right kernel driver to retrieve MAC addresses and logical names.
         """
+        if self.type is LinkTopology.NO_LINK:
+            return
         self._prepare_devbind_script()
         self._setup_ports("sut")
         self._setup_ports("tg")
@@ -148,6 +152,8 @@ def teardown(self) -> None:
 
         Restores all the ports to their original drivers before the test run.
         """
+        if self.type is LinkTopology.NO_LINK:
+            return
         self._restore_ports_original_drivers("sut")
         self._restore_ports_original_drivers("tg")
 
@@ -338,7 +344,8 @@ def prepare_node(node: Node) -> None:
             node.main_session.devbind_script_path = devbind_script_path
 
         ctx = get_ctx()
-        prepare_node(ctx.tg_node)
+        if ctx.tg_node:
+            prepare_node(ctx.tg_node)
         prepare_node(ctx.sut_node)
 
     @property
-- 
2.54.0


^ permalink raw reply related

* RE: [PATCH 1/2] net/ice: revert fix link up when starting device
From: Loftus, Ciara @ 2026-06-09 12:53 UTC (permalink / raw)
  To: Richardson, Bruce; +Cc: dev@dpdk.org
In-Reply-To: <aif_MR9pNjdT9S21@bricha3-mobl1.ger.corp.intel.com>

> Subject: Re: [PATCH 1/2] net/ice: revert fix link up when starting device
> 
> On Wed, Jun 03, 2026 at 02:34:06PM +0000, Ciara Loftus wrote:
> > This reverts commit 6c76b76dc64183eb2f24a52b90d4ff9feb4872f4.
> >
> > The reverted commit worked around a potential timing issue where the
> > link could be reported down immediately after the link was enabled
> > during device start. The commit introduced a blocking wait which gave
> > the driver a better chance to read the correct link state before
> > returning from device start. However, since the auto link update flag is
> > set when setting the link up in device start, an adminq notification
> > should arrive once the link is up, which will be handled and correctly
> > set the link status. This means we can remove the delay from device
> > start as it does not need to guarantee link up before returning.
> >
> > Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
> > ---
> >  drivers/net/intel/ice/ice_ethdev.c | 2 +-
> >  1 file changed, 1 insertion(+), 1 deletion(-)
> >
> Should this revert be marked for backport, or is it safer to keep the LTS's
> as they are?

I think it shouldn't be marked for backport.
With this patch we now rely on the adminq link status notification to
correct an incorrect link status read at dev_start. That requires commit
e01265e4a9bf ("net/ice: get link status updates via adminq message")
which was only introduced this year and not present in any LTS.

^ permalink raw reply

* [PATCH v3] dts: add retry loop to trex traffic generation
From: Andrew Bailey @ 2026-06-09 12:41 UTC (permalink / raw)
  To: luca.vizzarro, patrickrobb1997
  Cc: dev, ahassick, knimoji, lylavoie, Andrew Bailey
In-Reply-To: <20260518175424.253600-1-abailey@iol.unh.edu>

There was an issue where the single core forward test would report zero
MPPS intermittently. This was due to TREX reporting that the link was
down when the client was called to start generating traffic. The links
were being reported down by TREX on the tg even when testpmd was
reporting them up on the SUT. Adding a retry loop to the generate
traffic method of TREX gives the tg enough time to set up and send
traffic.

Bugzilla ID: 1946
Fixes: d77d7f04f24c ("dts: add single-core performance test suite")

Signed-off-by: Andrew Bailey <abailey@iol.unh.edu>
---
 .../testbed_model/traffic_generator/trex.py   | 36 ++++++++++++++-----
 1 file changed, 27 insertions(+), 9 deletions(-)

diff --git a/dts/framework/testbed_model/traffic_generator/trex.py b/dts/framework/testbed_model/traffic_generator/trex.py
index 22cd20dea9..b1a3a6ceac 100644
--- a/dts/framework/testbed_model/traffic_generator/trex.py
+++ b/dts/framework/testbed_model/traffic_generator/trex.py
@@ -13,6 +13,7 @@
 
 from framework.config.node import OS, NodeConfiguration
 from framework.config.test_run import TrexTrafficGeneratorConfig
+from framework.exception import SSHTimeoutError
 from framework.parser import TextParser
 from framework.remote_session.blocking_app import BlockingApp
 from framework.remote_session.python_shell import PythonShell
@@ -220,7 +221,9 @@ def _create_packet_stream(self, packet: Packet) -> None:
         ]
         self._shell.send_command("\n".join(packet_stream))
 
-    def _send_traffic_and_get_stats(self, duration: float, send_mpps: float | None = None) -> str:
+    def _send_traffic_and_get_stats(
+        self, duration: float, send_mpps: float | None = None, retry_attempts: int = 5
+    ) -> str:
         """Send traffic and get TG Rx stats.
 
         Sends traffic from the TRex client's ports for the given duration.
@@ -230,15 +233,30 @@ def _send_traffic_and_get_stats(self, duration: float, send_mpps: float | None =
         Args:
             duration: The traffic generation duration.
             send_mpps: The millions of packets per second for TRex to send from each port.
+            retry_attempts: The number of times to retry this command on failure.
+
+        Raises:
+            SSHTimeoutError: If TRex fails to send traffic in the allotted attempts.
         """
-        if send_mpps:
-            self._shell.send_command(f"""{self.stl_client_name}.start(ports=[0, 1],
-                mult = '{send_mpps}mpps',
-                duration = {duration})""")
-        else:
-            self._shell.send_command(f"""{self.stl_client_name}.start(ports=[0, 1],
-                mult = '100%',
-                duration = {duration})""")
+        link_up = False
+        attempt = 0
+
+        while not link_up and attempt < retry_attempts:
+            if send_mpps:
+                result = self._shell.send_command(f"""{self.stl_client_name}.start(ports=[0, 1],
+                    mult = '{send_mpps}mpps',
+                    duration = {duration})""")
+            else:
+                result = self._shell.send_command(f"""{self.stl_client_name}.start(ports=[0, 1],
+                    mult = '100%',
+                    duration = {duration})""")
+            link_up = "link is DOWN" not in result
+            if not link_up:
+                self._logger.info(
+                    f"Generate traffic command failed (attempt {attempt + 1} of {retry_attempts})"
+                )
+                time.sleep(0.25)
+            attempt += 1
 
         time.sleep(duration)
 
-- 
2.54.0


^ permalink raw reply related

* RE: [PATCH v2] net/iavf: fix misleading AQ failure logging
From: Loftus, Ciara @ 2026-06-09 12:30 UTC (permalink / raw)
  To: Mandal, Anurag, dev@dpdk.org
  Cc: Richardson, Bruce, Medvedkin, Vladimir, stable@dpdk.org
In-Reply-To: <20260609120050.360907-1-anurag.mandal@intel.com>

> Subject: [PATCH v2] net/iavf: fix misleading AQ failure logging
> 
> iavf_handle_virtchnl_msg() drains the admin receive queue in a loop
> until iavf_clean_arq_element() reports that no descriptors are
> pending. When the queue becomes empty, the base driver returns
> IAVF_ERR_ADMIN_QUEUE_NO_WORK (-57), which is the documented
> terminator for the drain loop, and is not an error.
> 
> The current loop treats every non-IAVF_SUCCESS return as a failure
> and logs it as follows:
> 
> "Failed to read msg from AdminQ, ret: -57"
> 
> This message floods the logs on every interrupt cycle and misleads
> the triage during VF reset by chasing a real virtchnl problem
> seeing these spurious -57 AQ failure lines in logs and assumes
> the admin queue is broken, when in fact it has just been drained.
> 
> This patch fixes the aforesaid issue by treating
> IAVF_ERR_ADMIN_QUEUE_NO_WORK in virtchnl message drain as a normal
> loop exit empty-queue condition and avoid logging it as an misleading
> AQ failure.
> 
> Fixes: 02d212ca3125 ("net/iavf: rename remaining avf strings")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Anurag Mandal <anurag.mandal@intel.com>

Thanks Anurag.

Acked-by: Ciara Loftus <ciara.loftus@intel.com>

> ---
> V2: Addressed Ciara Loftus's comment
>  -  kept 'break" for ret == IAVF_ERR_ADMIN_QUEUE_NO_WORK case
> 
>  drivers/net/intel/iavf/iavf_vchnl.c | 12 ++++++++++--
>  1 file changed, 10 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/net/intel/iavf/iavf_vchnl.c
> b/drivers/net/intel/iavf/iavf_vchnl.c
> index 94ccfb5d6e..39ebddff31 100644
> --- a/drivers/net/intel/iavf/iavf_vchnl.c
> +++ b/drivers/net/intel/iavf/iavf_vchnl.c
> @@ -571,8 +571,16 @@ iavf_handle_virtchnl_msg(struct rte_eth_dev *dev)
>  		ret = iavf_clean_arq_element(hw, &info, &pending);
> 
>  		if (ret != IAVF_SUCCESS) {
> -			PMD_DRV_LOG(INFO, "Failed to read msg from
> AdminQ,"
> -				    "ret: %d", ret);
> +			/*
> +			 * IAVF_ERR_ADMIN_QUEUE_NO_WORK (-57) means
> AQ is empty
> +			 * and is a normal way to terminate the drain loop.
> +			 * Log error only for genuine other failure codes.
> +			 * Incorrect logging like this during VF resets might
> +			 * mislead into chasing a non-existent AQ failure.
> +			 */
> +			if (ret != IAVF_ERR_ADMIN_QUEUE_NO_WORK)
> +				PMD_DRV_LOG(INFO, "Failed to read msg
> from AdminQ,"
> +					    "ret: %d", ret);
>  			break;
>  		}
>  		aq_opc = rte_le_to_cpu_16(info.desc.opcode);
> --
> 2.34.1


^ permalink raw reply

* RE: [PATCH] net/iavf: fix misleading AQ failure logging
From: Mandal, Anurag @ 2026-06-09 12:04 UTC (permalink / raw)
  To: Loftus, Ciara, dev@dpdk.org
  Cc: Richardson, Bruce, Medvedkin, Vladimir, stable@dpdk.org
In-Reply-To: <IA4PR11MB9278A4729FF823D93BE849988E1D2@IA4PR11MB9278.namprd11.prod.outlook.com>

> -----Original Message-----
> From: Loftus, Ciara <ciara.loftus@intel.com>
> Sent: 09 June 2026 13:44
> To: Mandal, Anurag <anurag.mandal@intel.com>; dev@dpdk.org
> Cc: Richardson, Bruce <bruce.richardson@intel.com>; Medvedkin, Vladimir
> <vladimir.medvedkin@intel.com>; stable@dpdk.org
> Subject: RE: [PATCH] net/iavf: fix misleading AQ failure logging
> 
> > Subject: [PATCH] net/iavf: fix misleading AQ failure logging
> >
> > iavf_handle_virtchnl_msg() drains the admin receive queue in a loop
> > until iavf_clean_arq_element() reports that no descriptors are
> > pending. When the queue becomes empty, the base driver returns
> > IAVF_ERR_ADMIN_QUEUE_NO_WORK (-57), which is the documented
> terminator
> > for the drain loop, and is not an error.
> >
> > The current loop treats every non-IAVF_SUCCESS return as a failure and
> > logs it as follows:
> >
> > "Failed to read msg from AdminQ, ret: -57"
> >
> > This message floods the logs on every interrupt cycle and misleads the
> > triage during VF reset by chasing a real virtchnl problem seeing these
> > spurious -57 AQ failure lines in logs and assumes the admin queue is
> > broken, when in fact it has just been drained.
> >
> > This patch fixes the aforesaid issue by treating
> > IAVF_ERR_ADMIN_QUEUE_NO_WORK in virtchnl message drain as a normal
> > loop exit empty-queue condition and avoid logging it as an misleading
> > AQ failure.
> >
> > Fixes: 02d212ca3125 ("net/iavf: rename remaining avf strings")
> > Cc: stable@dpdk.org
> >
> > Signed-off-by: Anurag Mandal <anurag.mandal@intel.com>
> > ---
> >  drivers/net/intel/iavf/iavf_vchnl.c | 10 +++++++++-
> >  1 file changed, 9 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/net/intel/iavf/iavf_vchnl.c
> > b/drivers/net/intel/iavf/iavf_vchnl.c
> > index 94ccfb5d6e..870d5c1820 100644
> > --- a/drivers/net/intel/iavf/iavf_vchnl.c
> > +++ b/drivers/net/intel/iavf/iavf_vchnl.c
> > @@ -570,7 +570,15 @@ iavf_handle_virtchnl_msg(struct rte_eth_dev *dev)
> >  	while (pending) {
> >  		ret = iavf_clean_arq_element(hw, &info, &pending);
> >
> > -		if (ret != IAVF_SUCCESS) {
> > +		/*
> > +		 * IAVF_ERR_ADMIN_QUEUE_NO_WORK (-57) means AQ is
> > empty
> > +		 * and is a normal way to terminate the drain loop.
> > +		 * Log error only for genuine other failure codes.
> > +		 * Incorrect logging like this during VF resets might
> > +		 * mislead into chasing a non-existent AQ failure.
> > +		 */
> > +		if (ret != IAVF_SUCCESS &&
> > +		    ret != IAVF_ERR_ADMIN_QUEUE_NO_WORK) {
> >  			PMD_DRV_LOG(INFO, "Failed to read msg from
> AdminQ,"
> >  				    "ret: %d", ret);
> >  			break;
> > --
> 
> I agree we should suppress the log on ret=NO_WORK but I think we should still
> break instead of proceeding with the message handling code. With your
> changes, in place of the
> log:
> 	IAVF_DRIVER: iavf_handle_virtchnl_msg(): Failed to read msg from
> AdminQ,ret: -57 I now see:
> 	IAVF_DRIVER: iavf_handle_virtchnl_msg(): Request 0 is not supported
> yet
> 
> I think that's because we enter the message handling code and arrive at the
> default case in the switch statement.

Hi Ciara,

Thanks for the review. I checked. You are correct.
Sent v2 with proper fix.
Please review. Thanks!

Thanks,
Anurag


^ permalink raw reply

* [PATCH v2] net/iavf: fix misleading AQ failure logging
From: Anurag Mandal @ 2026-06-09 12:00 UTC (permalink / raw)
  To: dev
  Cc: bruce.richardson, vladimir.medvedkin, ciara.loftus, Anurag Mandal,
	stable
In-Reply-To: <20260608171559.355521-1-anurag.mandal@intel.com>

iavf_handle_virtchnl_msg() drains the admin receive queue in a loop
until iavf_clean_arq_element() reports that no descriptors are
pending. When the queue becomes empty, the base driver returns
IAVF_ERR_ADMIN_QUEUE_NO_WORK (-57), which is the documented
terminator for the drain loop, and is not an error.

The current loop treats every non-IAVF_SUCCESS return as a failure
and logs it as follows:

"Failed to read msg from AdminQ, ret: -57"

This message floods the logs on every interrupt cycle and misleads
the triage during VF reset by chasing a real virtchnl problem
seeing these spurious -57 AQ failure lines in logs and assumes
the admin queue is broken, when in fact it has just been drained.

This patch fixes the aforesaid issue by treating
IAVF_ERR_ADMIN_QUEUE_NO_WORK in virtchnl message drain as a normal
loop exit empty-queue condition and avoid logging it as an misleading
AQ failure.

Fixes: 02d212ca3125 ("net/iavf: rename remaining avf strings")
Cc: stable@dpdk.org

Signed-off-by: Anurag Mandal <anurag.mandal@intel.com>
---
V2: Addressed Ciara Loftus's comment
 -  kept 'break" for ret == IAVF_ERR_ADMIN_QUEUE_NO_WORK case

 drivers/net/intel/iavf/iavf_vchnl.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/drivers/net/intel/iavf/iavf_vchnl.c b/drivers/net/intel/iavf/iavf_vchnl.c
index 94ccfb5d6e..39ebddff31 100644
--- a/drivers/net/intel/iavf/iavf_vchnl.c
+++ b/drivers/net/intel/iavf/iavf_vchnl.c
@@ -571,8 +571,16 @@ iavf_handle_virtchnl_msg(struct rte_eth_dev *dev)
 		ret = iavf_clean_arq_element(hw, &info, &pending);
 
 		if (ret != IAVF_SUCCESS) {
-			PMD_DRV_LOG(INFO, "Failed to read msg from AdminQ,"
-				    "ret: %d", ret);
+			/*
+			 * IAVF_ERR_ADMIN_QUEUE_NO_WORK (-57) means AQ is empty
+			 * and is a normal way to terminate the drain loop.
+			 * Log error only for genuine other failure codes.
+			 * Incorrect logging like this during VF resets might
+			 * mislead into chasing a non-existent AQ failure.
+			 */
+			if (ret != IAVF_ERR_ADMIN_QUEUE_NO_WORK)
+				PMD_DRV_LOG(INFO, "Failed to read msg from AdminQ,"
+					    "ret: %d", ret);
 			break;
 		}
 		aq_opc = rte_le_to_cpu_16(info.desc.opcode);
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH 1/2] net/ice: revert fix link up when starting device
From: Bruce Richardson @ 2026-06-09 11:55 UTC (permalink / raw)
  To: Ciara Loftus; +Cc: dev
In-Reply-To: <20260603143407.1108527-2-ciara.loftus@intel.com>

On Wed, Jun 03, 2026 at 02:34:06PM +0000, Ciara Loftus wrote:
> This reverts commit 6c76b76dc64183eb2f24a52b90d4ff9feb4872f4.
> 
> The reverted commit worked around a potential timing issue where the
> link could be reported down immediately after the link was enabled
> during device start. The commit introduced a blocking wait which gave
> the driver a better chance to read the correct link state before
> returning from device start. However, since the auto link update flag is
> set when setting the link up in device start, an adminq notification
> should arrive once the link is up, which will be handled and correctly
> set the link status. This means we can remove the delay from device
> start as it does not need to guarantee link up before returning.
> 
> Signed-off-by: Ciara Loftus <ciara.loftus@intel.com>
> ---
>  drivers/net/intel/ice/ice_ethdev.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
Should this revert be marked for backport, or is it safer to keep the LTS's
as they are?

^ permalink raw reply

* Re: Re: [PATCH v14 00/20] net/sxe2: added Linkdata sxe2 ethernet driver
From: liujie5 @ 2026-06-09 11:10 UTC (permalink / raw)
  To: dev
In-Reply-To: <T4eYjXumTvWFDg9HUrMwEA@monjalon.net>

[-- Attachment #1: Type: text/plain, Size: 2481 bytes --]

Hi Thomas Monjalon,

Thank you for your review and for guiding me on the mailing list workflow.

> Please could you send your new series as a reply of the v1 cover letter?
> It would avoid having a large indent in mail clients.

Got it. I will configure git send-email with "--in-reply-to" bound to the 
v1 cover letter Message-ID for the next version of this patch set. 
Apologies for the inconvenience caused by the deep threading.

> Also I'm still waiting for a reply to my question about the need
> for drivers/common/sxe2/.
> I'm not going to pull any sxe2 patch into main until you explain.

Regarding the necessity of the "drivers/common/sxe2/" directory, the sxe2 
hardware architecture is designed to support both standard NIC and vDPA 
(vHost Data Path Acceleration) functionalities. 

The code currently placed in "drivers/common/sxe2/" contains the foundational 
Hardware Abstraction Layer (HAL), firmware interfaces (AdminQ), and common 
register definitions. 

Apart from the "net/sxe2" PMD in this series, we are actively implementing the 
"vdpa/sxe2" driver, which will be submitted in a subsequent patch set. Both the 
"net/sxe2" and "vdpa/sxe2" drivers strictly depend on this shared common HAL 
layer to manage the same hardware core. Separating this code into "drivers/common/" 
is essential to prevent massive code duplication between the net and vdpa 
subsystems in the near future.

Please let me know if this clarifies your concern. Once confirmed, I will 
send out the updated v15 series as a reply to the v1 cover letter immediately.

Best regards,
Jie Liu (liujie5@linkdatatechnology.com)
 


liujie5@linkdatatechnology.com
 
From: Thomas Monjalon
Date: 2026-06-09 16:42
To: Jie Liu
CC: stephen; dev
Subject: Re: [PATCH v14 00/20] net/sxe2: added Linkdata sxe2 ethernet driver
09/06/2026 03:39, liujie5@linkdatatechnology.com: 
> From: Jie Liu <liujie5@linkdatatechnology.com> 
> 
> This patch set implements core functionality for the SXE2 PMD, 
> including basic driver framework, data path setup, and advanced 
> offload features (VLAN, RSS,TM, PTP etc.). 
> 
> V14: 
>  - Addressed AI comments 

Please could you send your new series as a reply of the v1 cover letter? 
It would avoid having a large indent in mail clients. 

Also I'm still waiting for a reply to my question about the need 
for drivers/common/sxe2/. 
I'm not going to pull any sxe2 patch into main until you explain. 


[-- Attachment #2: Type: text/html, Size: 4440 bytes --]

^ permalink raw reply

* RE: [PATCH] net/iavf: fix to consolidate link change event handling
From: Mandal, Anurag @ 2026-06-09 10:53 UTC (permalink / raw)
  To: Loftus, Ciara, dev@dpdk.org
  Cc: Richardson, Bruce, Medvedkin, Vladimir, stable@dpdk.org
In-Reply-To: <IA4PR11MB92782B6A7F4F58953DE954DE8E1D2@IA4PR11MB9278.namprd11.prod.outlook.com>


> -----Original Message-----
> From: Loftus, Ciara <ciara.loftus@intel.com>
> Sent: 09 June 2026 15:32
> To: Mandal, Anurag <anurag.mandal@intel.com>; dev@dpdk.org
> Cc: Richardson, Bruce <bruce.richardson@intel.com>; Medvedkin, Vladimir
> <vladimir.medvedkin@intel.com>; stable@dpdk.org
> Subject: RE: [PATCH] net/iavf: fix to consolidate link change event handling
> 
> > Subject: [PATCH] net/iavf: fix to consolidate link change event
> > handling
> >
> > Handled link-change events through a common static function that reads
> > the correct advanced & legacy link fields properly and updates
> > no-poll/watchdog/LSC state consistently.
> >
> > Fixes: 5e03e316c753 ("net/iavf: handle virtchnl event message without
> > interrupt")
> > Fixes: 48de41ca11f0 ("net/avf: enable link status update")
> > Cc: stable@dpdk.org
> >
> > Signed-off-by: Anurag Mandal <anurag.mandal@intel.com>
> > ---
> >  drivers/net/intel/iavf/iavf_vchnl.c | 133
> > +++++++++++++++++-----------
> >  1 file changed, 81 insertions(+), 52 deletions(-)
> >
> > diff --git a/drivers/net/intel/iavf/iavf_vchnl.c
> > b/drivers/net/intel/iavf/iavf_vchnl.c
> > index 94ccfb5d6e..6454632541 100644
> > --- a/drivers/net/intel/iavf/iavf_vchnl.c
> > +++ b/drivers/net/intel/iavf/iavf_vchnl.c
> > @@ -216,6 +216,75 @@ iavf_convert_link_speed(enum virtchnl_link_speed
> > virt_link_speed)
> >  	return speed;
> >  }
> >
> > +/*
> > + * iavf_handle_link_change_event: common handler for VIRTCHNL link
> > change events
> > + *
> > + * @dev: pointer to rte_eth_dev for this VF
> > + * @vpe: pointer to the virtchnl_pf_event payload received from the
> > +PF
> > + *
> > + * Handle PF link-change event: decode adv/legacy link info, update
> > +VF
> > + * link state, sync no-poll/watchdog behavior & notify app via LSC event.
> > + */
> > +static void
> > +iavf_handle_link_change_event(struct rte_eth_dev *dev,
> > +			      struct virtchnl_pf_event *vpe) {
> > +	struct iavf_adapter *adapter;
> > +	struct iavf_info *vf;
> > +	bool adv_link_speed;
> > +
> > +	if (dev == NULL || dev->data == NULL ||
> > +	    dev->data->dev_private == NULL || vpe == NULL) {
> > +		PMD_DRV_LOG(ERR, "Invalid device pointer in link change
> > handler");
> > +		return;
> > +	}
> 
> Thanks for splitting this out into a standalone patch.
> I'm not sure if all of the above NULL checking is necessary. Especially vpe ==
> NULL considering you've checked that for NULL in both iavf_read_msg_from_pf
> and iavf_handle_pf_event_msg before entering this function. It's recommended
> to avoid overly defensive checks that can never trigger so I suggest taking a look
> at this again. Other than that the changes looks good to me.

Hi Ciara,

Thanks for the review. Initially I also did not add. Then I added it as per suggestions from ai-code-review run by DPDK. Funny thing is now, it is saying " overly defensive checks". So not sure, which suggestion of ai-cdoe-review to follow.
Please guide. Will act accordingly.

Thanks,
Anuarg

> 
> > +
> > +	adapter = IAVF_DEV_PRIVATE_TO_ADAPTER(dev->data-
> > >dev_private);
> > +	vf = &adapter->vf;
> > +
> > +	adv_link_speed = (vf->vf_res != NULL) &&
> > +		((vf->vf_res->vf_cap_flags &
> > VIRTCHNL_VF_CAP_ADV_LINK_SPEED) != 0);
> > +


^ permalink raw reply

* RE: [PATCH v4] net/iavf: fix duplicate VF reset during PF reset recovery
From: Loftus, Ciara @ 2026-06-09 10:52 UTC (permalink / raw)
  To: Mandal, Anurag, dev@dpdk.org
  Cc: Richardson, Bruce, Medvedkin, Vladimir, stable@dpdk.org
In-Reply-To: <20260608162936.354248-1-anurag.mandal@intel.com>

> Subject: [PATCH v4] net/iavf: fix duplicate VF reset during PF reset recovery
> 
> During PF initiated reset recovery, iavf_dev_close() sending
> an extra VIRTCHNL_OP_RESET_VF while recovery is already in progress.
> That second reset can leave PF/VF virtchnl state inconsistent and
> cause VIRTCHNL_OP_CONFIG_VSI_QUEUES to fail with ERR_PARAM after
> ToR link flap/power-cycle, leaving the VF unable to recover.
> This results in connection loss.
> 
> This patch skipped close-time VF reset and related close-time
> virtchnl operations when PF triggered reset recovery is set.
> This is done to avoid a duplicate VF reset, and keep normal
> behavior for application-driven close.
> 
> Fixes: 675a104e2e94 ("net/iavf: fix abnormal disable HW interrupt")
> Fixes: b34fe66ea893 ("net/iavf: delay VF reset command")
> Fixes: 5e03e316c753 ("net/iavf: handle virtchnl event message without
> interrupt")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Anurag Mandal <anurag.mandal@intel.com>
> ---
> V4: Addressed Ciara Loftus comments
>   - split VF reset from other code changes
> V3: Addressed latest ai-code-review comments
> V2: Addressed ai-code-review comments
> 
>  doc/guides/rel_notes/release_26_07.rst |  3 +++
>  drivers/net/intel/iavf/iavf_ethdev.c   | 37 +++++++++++++++-----------
>  drivers/net/intel/iavf/iavf_vchnl.c    | 18 ++++++++++---
>  3 files changed, 39 insertions(+), 19 deletions(-)
> 
> diff --git a/doc/guides/rel_notes/release_26_07.rst
> b/doc/guides/rel_notes/release_26_07.rst
> index d2563ac503..f6899a78c3 100644
> --- a/doc/guides/rel_notes/release_26_07.rst
> +++ b/doc/guides/rel_notes/release_26_07.rst
> @@ -95,6 +95,9 @@ New Features
> 
>    * Added support for transmitting LLDP packets based on mbuf packet type.
>    * Implemented AVX2 context descriptor transmit paths.
> +  * Prevented duplicate 'VIRTCHNL_OP_RESET_VF' during a PF-initiated
> +    reset recovery, which earlier caused virtchnl state corruption
> +    and connection loss after a top-of-rack (ToR) link flap/power-cycle.
> 
>  * **Updated PCAP ethernet driver.**
> 
> diff --git a/drivers/net/intel/iavf/iavf_ethdev.c
> b/drivers/net/intel/iavf/iavf_ethdev.c
> index a8031e23a5..99457ae510 100644
> --- a/drivers/net/intel/iavf/iavf_ethdev.c
> +++ b/drivers/net/intel/iavf/iavf_ethdev.c
> @@ -3166,24 +3166,27 @@ iavf_dev_close(struct rte_eth_dev *dev)
> 
>  	ret = iavf_dev_stop(dev);
> 
> -	/*
> -	 * Release redundant queue resource when close the dev
> -	 * so that other vfs can re-use the queues.
> -	 */
> -	if (vf->lv_enabled) {
> -		ret = iavf_request_queues(dev,
> IAVF_MAX_NUM_QUEUES_DFLT);
> -		if (ret)
> -			PMD_DRV_LOG(ERR, "Reset the num of queues
> failed");
> +	/* Skip RESET_VF on a PF-initiated reset */
> +	if (!adapter->closed && !vf->in_reset_recovery) {

adapter->closed will always be false here so no need to check it.

vf->in_reset_recovery is set for a VF initiated reset as well which does
require sending VIRTCHNL_OP_RESET_VF because that kicks off the reset.
(rte_pmd_iavf_reinit -> handle_hw_reset is the vf initiated reset path.)

We need some way to know if we are currently handling a PF initiated
reset here. Another adapter flag, or else make in_reset_recovery
tri-state eg. 0 (no reset) 1 (pf initiated) 2 (vf initiated)
Then skip the OP_RESET_VF if pf initiated.

> +		/*
> +		 * Release redundant queue resource when close the dev
> +		 * so that other vfs can re-use the queues.
> +		 */
> +		if (vf->lv_enabled) {
> +			ret = iavf_request_queues(dev,
> IAVF_MAX_NUM_QUEUES_DFLT);
> +			if (ret)
> +				PMD_DRV_LOG(ERR, "Reset the num of
> queues failed");
> +			vf->max_rss_qregion =
> IAVF_MAX_NUM_QUEUES_DFLT;
> +		}
> 
> -		vf->max_rss_qregion = IAVF_MAX_NUM_QUEUES_DFLT;
> +		/*
> +		 * Disable promiscuous mode before resetting the VF. This is to
> avoid
> +		 * potential issues when the PF is bound to the kernel driver.
> +		 */
> +		if (vf->promisc_unicast_enabled || vf-
> >promisc_multicast_enabled)
> +			iavf_config_promisc(adapter, false, false);
>  	}
> 
> -	/* Disable promiscuous mode before resetting the VF. This is to avoid
> -	 * potential issues when the PF is bound to the kernel driver.
> -	 */
> -	if (vf->promisc_unicast_enabled || vf->promisc_multicast_enabled)
> -		iavf_config_promisc(adapter, false, false);
> -
>  	adapter->closed = true;
> 
>  	/* free iAVF security device context all related resources */
> @@ -3195,7 +3198,9 @@ iavf_dev_close(struct rte_eth_dev *dev)
>  	iavf_flow_flush(dev, NULL);
>  	iavf_flow_uninit(adapter);
> 
> -	iavf_vf_reset(hw);
> +	/* Skip RESET_VF on a PF-initiated reset */
> +	if (!vf->in_reset_recovery)
> +		iavf_vf_reset(hw);
>  	vf->aq_intr_enabled = false;
>  	iavf_shutdown_adminq(hw);
>  	if (vf->vf_res->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_WB_ON_ITR)
> {
> diff --git a/drivers/net/intel/iavf/iavf_vchnl.c
> b/drivers/net/intel/iavf/iavf_vchnl.c
> index 94ccfb5d6e..cf3513ef94 100644
> --- a/drivers/net/intel/iavf/iavf_vchnl.c
> +++ b/drivers/net/intel/iavf/iavf_vchnl.c
> @@ -283,9 +283,21 @@ iavf_read_msg_from_pf(struct iavf_adapter
> *adapter, uint16_t buf_len,
>  					vf->link_up ? "up" : "down");
>  			break;
>  		case VIRTCHNL_EVENT_RESET_IMPENDING:
> -			vf->vf_reset = true;
> -			iavf_set_no_poll(adapter, false);
> -			PMD_DRV_LOG(INFO, "VF is resetting");
> +			/*
> +			 * Force link down on impending reset to drop
> +			 * the cached link-up state; a fresh LSC up
> +			 * event will be re-issued by the PF once the
> +			 * VF is reinitialised.
> +			 */
> +			vf->link_up = false;
> +			if (!vf->vf_reset) {
> +				vf->vf_reset = true;
> +				iavf_set_no_poll(adapter, false);
> +				iavf_dev_event_post(vf->eth_dev,
> +					RTE_ETH_EVENT_INTR_RESET,
> +					NULL, 0);
> +			}
> +			PMD_DRV_LOG(DEBUG, "VF is resetting");
>  			break;
>  		case VIRTCHNL_EVENT_PF_DRIVER_CLOSE:
>  			vf->dev_closed = true;
> --
> 2.34.1


^ permalink raw reply

* Re: 回复:[PATCH] gpu/metax: add new driver for Metax GPU
From: Thomas Monjalon @ 2026-06-09 10:43 UTC (permalink / raw)
  To: 许玲燕; +Cc: dev, eagostini
In-Reply-To: <fbe7f8ce-f680-411a-a7a6-82eda60300de.lingyan.xu@metax-tech.com>

Thank you for the detailed answer and your understanding.

One more question: are the lib and module upstreamed already?


09/06/2026 12:22, 许玲燕:
> Hi,
> Thank you for the detailed feedback and for reviewing the proposal for the Metax GPU driver.
> Based on the questions raised and the analysis of the code implementation, here are the clarifications and my action plan:
> 1. Regarding GPU Access Method
> The driver interfaces with the Metax GPU hardware through a combination of the vendor-provided MC Runtime (Metax Compute Runtime) library and GDRCopy (GPU Direct RDMA) technology.
> 
>  * 
> User-space Library: As seen in the maca.c code, the driver dynamically loads (dlopen) the libmcruntime.so library. It uses mc_runtime_api.h to manage GPU contexts, memory allocation, and device attributes.
> 
>  * 
> Kernel Module: The driver relies on the underlying Metax kernel driver (for PCI probing and basic device access) and the gdrapi (GDRCopy) kernel module to facilitate zero-copy data transfer between CPU and GPU memory.
> 
>  * 
> Dependency: The build log confirms the detection of headers like mc_runtime_api.h and gdrapi.h, which are essential for this integration.
> 2. Clarification on "Rendering" Functionality
> I apologize for the confusion caused by the term "Rendering" in the initial description. Upon reviewing the code and your feedback, I realize this was an inaccurate choice of words.
> 
>  * 
> Correction: The intended functionality is purely "Compute/Data Processing" and "Memory Management".
> 
>  * 
> Explanation: The driver's core logic (as shown in the patch) focuses on memory registration, allocation, and CPU/GPU data synchronization (via maca_mem_cpu_map and gdrcopy_pin), which are essential for network data processing acceleration rather than graphical rendering. I will correct this terminology in the documentation to avoid further confusion.
> 3. Action Plan: Following the Contribution Guide
> I have reviewed the <"Adding a New Driver"> guide you linked.
> 
>  * 
> Patch Splitting: I understand that the current monolithic patch is not suitable. I will rework the submission and split it into a logical patch series:
>  * 
> Patch 1: Add the basic infrastructure (Meson files, maintainers, configuration).
> 
>  * 
> Patch 2: Implement core device functionality (PCI probing, initialization, context management).
> 
>  * 
> Patch 3: Add memory management and data path features (allocation, registration, and CPU mapping).
> Thank you again for your guidance. I will resubmit the revised patch series shortly.
> Best regards,
> Lingyan Xu
> ------------------------------------------------------------------
> 发件人:Thomas Monjalon <thomas@monjalon.net>
> 发送时间:2026年6月2日(周二) 18:01
> 收件人:"许玲燕"<lingyan.xu@metax-tech.com>
> 抄 送:dev<dev@dpdk.org>; eagostini<eagostini@nvidia.com>
> 主 题:Re: [PATCH] gpu/metax: add new driver for Metax GPU
> Hello,
> 01/06/2026 07:47, 许玲燕:
> > I am writing to propose a new driver for the Metax GPU,
> How do you access the GPU?
> Are you using a specific library or kernel module?
> > which I believe will significantly enhance our support
> > and performance for this hardware.
> > The patch attached includes the initial implementation of the driver,
> > with key features such as:
> > 
> > * Basic initialization and configuration 
> > * Memory management and allocation 
> > * Core functionality for rendering and compute tasks 
> I am familiar with connecting compute tasks of a GPU
> with DPDK networking, but I'm surprised by the rendering functionality.
> Do you mean graphical rendering of data coming from the network?
> > Please review the code and let me know if you have any feedback or suggestions.
> > I am more than happy to make any necessary adjustments and improvements.
> Thank you for working on this.
> I recommend following this guide to introduce a new driver:
> https://doc.dpdk.org/guides/contributing/new_driver.html <https://doc.dpdk.org/guides/contributing/new_driver.html >
> 
> 
>     超大附件列表            dpdk-build-test-log.txt  [48KB]
>         进入下载页面  https://qiye.aliyun.com/alimail/openLinks/downloadMimeMetaDiskBigAttach?id=netdiskid%3Av001%3Afile%3ADzzzzzzNqZx%3BJYiJwCficINAoHh55iyjKdydQzW5hDE%2FGjddF2Xp4ghl2ujmlGlWdfhgNCLOb5s3BZAHvDXTdZhtzGA3q8HJ%2Fv%2FPGnrPJfO1Xc%2BWnHr%2FKRwIkHzWFe5Iwm1IZrurr9hW    
> 






^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox