|SUCCESS| [v5,10/10] dts: add selective Rx tests
dpdklab at iol.unh.edu
dpdklab at iol.unh.edu
Wed Jun 3 02:59:34 CEST 2026
Test-Label: ai-code-review
Test-Status: SUCCESS
https://dpdk.org/patch/164834
_AI Code Review_
=== Patch Review: series.patch (via Claude) ===
Review date: 2026-06-03
# Review of Selective Rx patch series
Based on the patch series introducing selective Rx feature to DPDK, here are my
findings:
commit 39606359 (partial)
Author: Thomas Monjalon <thomas at monjalon.net>
app/testpmd: print Rx split capabilities
This commit introduces new capability printing in testpmd.
> --- a/app/test-pmd/config.c
> +++ b/app/test-pmd/config.c
> @@ -790,6 +790,12 @@ rss_offload_types_display(uint64_t offload_types, uint16_t char_num_per_line)
> printf("\n");
> }
>
> +static void
> +print_bool_capa(const char *label, int value)
> +{
> + printf("%s: %s\n", label, value ? "supported" : "not supported");
> +}
Does this function follow the implicit comparison rule? The coding style guide
requires explicit comparison with 0 for integer types. Should this be "value !=
0" instead?
> @@ -911,6 +917,16 @@ port_infos_display(portid_t port_id)
> dev_info.max_rx_pktlen);
> printf("Maximum configurable size of LRO aggregated packet: %u\n",
> dev_info.max_lro_pkt_size);
> +
> + printf("Rx split:\n");
> + printf("\tMax segments: %hu\n", dev_info.rx_seg_capa.max_nseg);
> + if (dev_info.rx_seg_capa.max_nseg > 0) {
> + print_bool_capa("\tMulti-pool", dev_info.rx_seg_capa.multi_pools);
> + print_bool_capa("\tBuffer offset", dev_info.rx_seg_capa.offset_allowed);
> + printf("\tOffset alignment: %u\n",
> + RTE_BIT32(dev_info.rx_seg_capa.offset_align_log2));
> + }
Does this alignment check handle the edge case where offset_align_log2 >= 32?
Would RTE_BIT32() overflow in that scenario?
---
commit 39606360 (partial)
Author: Gregory Etelson <getelson at nvidia.com>
ethdev: introduce selective Rx
This commit adds the core selective Rx capability.
> --- a/lib/ethdev/rte_ethdev.c
> +++ b/lib/ethdev/rte_ethdev.c
> @@ -2129,7 +2129,7 @@ rte_eth_rx_queue_check_split(uint16_t port_id,
> const struct rte_eth_dev_info *dev_info)
> {
> const struct rte_eth_rxseg_capa *seg_capa = &dev_info->rx_seg_capa;
> - struct rte_mempool *mp_first;
> + struct rte_mempool *mp_first = NULL;
Does this initialization create a scenario where mp_first might remain NULL
through the entire function if all rx_seg[].mp are NULL? The function later
compares against mp_first without NULL checking. Can this cause a
NULL-pointer-based comparison issue?
> @@ -2148,13 +2148,17 @@ rte_eth_rx_queue_check_split(uint16_t port_id,
> * Check the sizes and offsets against buffer sizes
> * for each segment specified in extended configuration.
> */
> - mp_first = rx_seg[0].mp;
> offset_mask = RTE_BIT32(seg_capa->offset_align_log2) - 1;
>
> ptypes = NULL;
> @@ -2160,13 +2159,17 @@ rte_eth_rx_queue_check_split(uint16_t port_id,
> uint32_t offset = rx_seg[seg_idx].offset;
> uint32_t proto_hdr = rx_seg[seg_idx].proto_hdr;
>
> - if (mpl == NULL) {
> - RTE_ETHDEV_LOG_LINE(ERR, "null mempool pointer");
> - ret = -EINVAL;
> - goto out;
> + if (mpl == NULL) { /* discarded segment */
> + if (seg_capa->selective_rx == 0) { /* not supported */
> + RTE_ETHDEV_LOG_LINE(ERR, "null mempool pointer");
> + ret = -EINVAL;
> + goto out;
> + }
> + continue; /* next checks are not relevant if no mempool */
> }
Does this logic protect against an all-NULL configuration slipping through? The
validation happens inside the loop, so if all segments have NULL mempools and
selective_rx is supported, would the function succeed?
> + if (mp_first == NULL) {
> + RTE_ETHDEV_LOG_LINE(ERR, "At least one Rx segment must have a mempool");
> + ret = -EINVAL;
> + goto out;
> + }
Good catch - this check addresses the concern above by ensuring at least one
valid mempool exists.
---
commit 39606361 (partial)
Author: Gregory Etelson <getelson at nvidia.com>
app/testpmd: support selective Rx
This commit adds testpmd support for selective Rx.
> --- a/app/test-pmd/testpmd.c
> +++ b/app/test-pmd/testpmd.c
> @@ -2731,6 +2731,16 @@ port_is_started(portid_t port_id)
> return 1;
> }
>
> +static struct rte_eth_rxseg_split *
> +next_rx_seg(union rte_eth_rxseg *segs, uint16_t *idx)
> +{
> + if (*idx >= MAX_SEGS_BUFFER_SPLIT) {
> + fprintf(stderr, "Too many segments (max %u)\n", MAX_SEGS_BUFFER_SPLIT);
> + return NULL;
> + }
> + return &segs[(*idx)++].split;
> +}
Does this function handle the case where segs is NULL? While the callers
currently pass valid pointers, should there be a defensive check?
> @@ -2744,31 +2754,70 @@ rx_queue_setup(uint16_t port_id, uint16_t rx_queue_id,
> uint32_t prev_hdrs = 0;
> int ret;
>
> - if ((rx_pkt_nb_segs > 1) &&
> - (rx_conf->offloads & RTE_ETH_RX_OFFLOAD_BUFFER_SPLIT)) {
> + if (rx_pkt_nb_segs > 1 || rx_pkt_nb_offs > 0) {
[ ... ]
Does this block correctly initialize new_mpr when all segments have NULL
mempools? The code finds mp_first but also needs last_mp for determining buffer
sizes. Can last_mp remain NULL in some edge cases?
---
commit 39606365 (partial)
Author: Thomas Monjalon <thomas at monjalon.net>
net/mlx5: support selective Rx
This commit adds selective Rx support to mlx5.
> --- a/drivers/net/mlx5/mlx5_rx.c
> +++ b/drivers/net/mlx5/mlx5_rx.c
> @@ -486,7 +486,7 @@ mlx5_rxq_initialize(struct mlx5_rxq_data *rxq)
> rxq->wqes)[i];
> addr = rte_pktmbuf_mtod(buf, uintptr_t);
> byte_count = DATA_LEN(buf);
> - lkey = mlx5_rx_mb2mr(rxq, buf);
> + lkey = buf->pool ? mlx5_rx_mb2mr(rxq, buf) : rxq->sh->null_mr->lkey;
Does this code safely access null_mr->lkey? If null_mr allocation failed during
initialization, does rxq->sh->null_mr potentially contain a NULL pointer that
would cause a crash here?
> @@ -1044,11 +1044,14 @@ mlx5_rx_burst(void *dpdk_rxq, struct rte_mbuf **pkts, uint16_t pkts_n)
> const unsigned int sges_n = rxq->sges_n;
> struct rte_mbuf *pkt = NULL;
> struct rte_mbuf *seg = NULL;
> + struct rte_mbuf *tail = NULL;
Does the addition of the tail pointer correctly handle all edge cases,
particularly when mixing real and discarded segments? The chaining logic seems
complex - can tail ever point to a discarded segment?
> @@ -1088,12 +1095,15 @@ mlx5_rx_burst(void *dpdk_rxq, struct rte_mbuf **pkts, uint16_t pkts_n)
> rq_ci <<= sges_n;
> break;
> }
> + }
> if (!pkt) {
> + if (len == 0) { /* no CQE polled yet */
Does this nested condition structure maintain proper cleanup on all error
paths? When errors occur with both seg->pool checks and CQE polling, are all
allocated resources properly freed?
> + if (seg->pool) { /* first real segment */
> + pkt = seg;
> + tail = seg;
Does this initialization handle the case where the first segment is discarded
but later segments are real? The logic seems to rely on seg->pool checks, but
the flow control with multiple nested conditions makes verification difficult.
---
commit 39606367 (partial)
Author: Thomas Monjalon <thomas at monjalon.net>
common/mlx5: remove callbacks for MR registration
This refactoring removes function pointers for MR registration.
> --- a/drivers/common/mlx5/mlx5_common_mr.c
> +++ b/drivers/common/mlx5/mlx5_common_mr.c
> @@ -492,12 +492,12 @@ mlx5_mr_lookup_cache(struct mlx5_mr_share_cache *share_cache,
> * Pointer to MR to free.
> */
> void
> -mlx5_mr_free(struct mlx5_mr *mr, mlx5_dereg_mr_t dereg_mr_cb)
> +mlx5_mr_free(struct mlx5_mr *mr)
> {
> if (mr == NULL)
> return;
> DRV_LOG(DEBUG, "freeing MR(%p):", (void *)mr);
> - dereg_mr_cb(&mr->pmd_mr);
> + mlx5_os_dereg_mr(&mr->pmd_mr);
Does this direct call to mlx5_os_dereg_mr maintain the same error handling
semantics as the callback? Are there any edge cases where the callback allowed
for custom cleanup that the direct call might miss?
---
commit 39606368 (partial)
Author: Thomas Monjalon <thomas at monjalon.net>
dts: fix topology capability comparison
This fixes comparison operators in TopologyCapability.
> --- a/dts/framework/testbed_model/capability.py
> +++ b/dts/framework/testbed_model/capability.py
> @@ -574,7 +574,9 @@ def __gt__(self, other: Any) -> bool:
> Returns:
> :data:`True` if the instance's topology type is more complex than the compared object's.
> """
> - return other < self
> + if not self.is_comparable_with(other):
> + return False
> + return self.topology_type > other.topology_type
Good fix. Does is_comparable_with() check for None? The original infinite
recursion bug is fixed, but verifying that is_comparable_with handles all edge
cases would strengthen this.
> @@ -586,7 +588,9 @@ def __le__(self, other: Any) -> bool:
> :data:`True` if the instance's topology type is less complex or equal than
> the compared object's.
> """
> - return not self > other
> + if not self.is_comparable_with(other):
> + return False
> + return self.topology_type <= other.topology_type
Consistent with __gt__ fix. Does Python's comparison protocol guarantee that
all six comparison operators are now properly consistent?
---
General observations:
1. The series introduces selective Rx as an overlay on buffer split, reusing
the same configuration structures with NULL mempool as a sentinel value.
Does this design create any ambiguity in error messages or debugging
scenarios?
2. The mlx5 implementation only supports selective Rx in the scalar SPRQ path
mlx5_rx_burst(). Does the documentation clearly warn users that enabling
selective Rx will force a potentially slower Rx path?
3. The testpmd changes automatically insert discard segments to fill gaps.
Does this behavior align with user expectations, or should it require
explicit opt-in?
4. Resource management: Are all null_mbuf allocations properly freed in error
paths? The code paths in mlx5_rxq.c seem complex with multiple potential
failure points.
5. The DTS test suite adds good coverage but focuses on positive cases and a
few error cases. Does it test the interaction between selective Rx and
other features like VLAN stripping, checksum offload, or RSS?
More information about the test-report
mailing list