Observed on ACC while running NVMe traffic tests: under sustained
high-throughput split-queue Tx, payload bytes on the wire did not
match what the application submitted, while headers and checksums
looked valid.

The RS-completion path freed mbufs by walking sw_ring[] slots between
first_id and the EOP sw_id. RE reclaims and reuses those slots before
RS lands, so the walker freed mbufs still in flight for later packets,
whose buffers were then recycled and overwritten mid-DMA.

Track completion ownership in a shadow ring indexed by a software-
defined compl_tag written to all data descriptors of a packet (per the
IDPF spec) and echoed back by HW on RS. The shadow ring is the sole
owner of in-flight mbufs, so sw_ring[].mbuf is no longer touched and
queue stop/release drain only the shadow ring. Size it to nb_tx_desc
and stop Tx when the next compl_tag slot is still occupied, since RE
runs ahead of RS and nb_tx_free does not bound pending completions.
Allocate it at queue setup (failing on -ENOMEM) and free it on all
setup-error and release paths.

Remove the now unused ci_tx_entry.first_id field.

Fixes: 96cc9b6ea60c ("net/idpf: fix multi-segment mbuf leak in split Tx path")
Cc: [email protected]

Signed-off-by: Shaiq Wani <[email protected]>
---
v3: shrink ci_tx_queue and trim the fast path per review:
* Drop the redundant tx_pending_size field (always nb_tx_desc).
* Reuse the now-dead sw_tail slot as tx_next_compl_tag, so the shadow
  ring adds only one pointer to ci_tx_queue.
* Remove the dead sw_ring[]/next_id walk from idpf_dp_splitq_xmit_pkts.
Kept a dedicated shadow ring rather than sw_ring[tag].mbuf: sw_ring is
indexed by descriptor and drained per-segment, so it cannot track
per-packet compl_tag ownership of multi-seg chains.

v2: no mbufs in sw_ring[] (shadow ring is sole owner); stop Tx on a
pending compl_tag slot; -ENOMEM propagation and cleanup on all paths;
same compl_tag on all data descs; shadow ring sized to nb_tx_desc.
---
 drivers/net/intel/common/tx.h             |   8 +-
 drivers/net/intel/cpfl/cpfl_rxtx.c        |   6 ++
 drivers/net/intel/idpf/idpf_common_rxtx.c | 119 ++++++++++++++--------
 drivers/net/intel/idpf/idpf_common_rxtx.h |   4 +
 drivers/net/intel/idpf/idpf_rxtx.c        |   5 +
 5 files changed, 99 insertions(+), 43 deletions(-)

diff --git a/drivers/net/intel/common/tx.h b/drivers/net/intel/common/tx.h
index 2a63cd4330..65e8f3fc59 100644
--- a/drivers/net/intel/common/tx.h
+++ b/drivers/net/intel/common/tx.h
@@ -166,7 +166,6 @@ struct ci_tx_queue;
 struct ci_tx_entry {
        struct rte_mbuf *mbuf; /* mbuf associated with TX desc, if any. */
        uint16_t next_id; /* Index of next descriptor in ring. */
-       uint16_t first_id; /* Split-queue: first sw_id of packet at EOP entry. 
*/
 };
 
 /**
@@ -260,8 +259,13 @@ struct ci_tx_queue {
                                struct idpf_complq *complq;
                                uint32_t latch_idx; /* Tx timestamp latch index 
*/
                                uint16_t sw_nb_desc;
-                               uint16_t sw_tail;
                                uint16_t rs_compl_count;
+                               /* Split-Tx completion tracking: shadow ring 
indexed by a
+                                * rolling compl_tag decouples RS-completion 
mbuf lifetime
+                                * from sw_ring[] slot reuse. Sized nb_tx_desc.
+                                */
+                               struct rte_mbuf **tx_pending_pkts;
+                               uint16_t tx_next_compl_tag;
                };
        };
 };
diff --git a/drivers/net/intel/cpfl/cpfl_rxtx.c 
b/drivers/net/intel/cpfl/cpfl_rxtx.c
index aba3a5e916..0dbe47d336 100644
--- a/drivers/net/intel/cpfl/cpfl_rxtx.c
+++ b/drivers/net/intel/cpfl/cpfl_rxtx.c
@@ -329,6 +329,7 @@ cpfl_tx_queue_release(void *txq)
                rte_free(q->complq);
        }
 
+       idpf_qc_split_tx_pending_free(q);
        ci_txq_release_all_mbufs(q, q->use_vec_entry);
        rte_free(q->sw_ring);
        rte_free(q->rs_last_id);
@@ -624,6 +625,9 @@ cpfl_tx_queue_setup(struct rte_eth_dev *dev, uint16_t 
queue_idx,
                idpf_qc_single_tx_queue_reset(txq);
        } else {
                txq->desc_ring = mz->addr;
+               ret = idpf_qc_split_tx_pending_alloc(txq, socket_id);
+               if (ret != 0)
+                       goto err_pending_alloc;
                idpf_qc_split_tx_descq_reset(txq);
 
                /* Setup tx completion queue if split model */
@@ -642,6 +646,8 @@ cpfl_tx_queue_setup(struct rte_eth_dev *dev, uint16_t 
queue_idx,
        return 0;
 
 err_complq_setup:
+       idpf_qc_split_tx_pending_free(txq);
+err_pending_alloc:
        rte_free(txq->rs_last_id);
 err_rs_last_id_alloc:
        rte_free(txq->sw_ring);
diff --git a/drivers/net/intel/idpf/idpf_common_rxtx.c 
b/drivers/net/intel/idpf/idpf_common_rxtx.c
index 649b5d1f99..496900b697 100644
--- a/drivers/net/intel/idpf/idpf_common_rxtx.c
+++ b/drivers/net/intel/idpf/idpf_common_rxtx.c
@@ -227,12 +227,55 @@ idpf_qc_split_tx_descq_reset(struct ci_tx_queue *txq)
 
        /* Use this as next to clean for split desc queue */
        txq->last_desc_cleaned = 0;
-       txq->sw_tail = 0;
        txq->nb_tx_free = txq->nb_tx_desc - 1;
 
        txq->rs_compl_count = 0;
        txq->tx_next_dd = txq->tx_rs_thresh - 1;
        txq->tx_next_rs = txq->tx_rs_thresh - 1;
+
+       if (txq->tx_pending_pkts != NULL) {
+               for (i = 0; i < txq->nb_tx_desc; i++) {
+                       if (txq->tx_pending_pkts[i] != NULL) {
+                               rte_pktmbuf_free(txq->tx_pending_pkts[i]);
+                               txq->tx_pending_pkts[i] = NULL;
+                       }
+               }
+       }
+       txq->tx_next_compl_tag = 0;
+}
+
+RTE_EXPORT_INTERNAL_SYMBOL(idpf_qc_split_tx_pending_alloc)
+int
+idpf_qc_split_tx_pending_alloc(struct ci_tx_queue *txq, unsigned int socket_id)
+{
+       /* Bounding pending RS to nb_tx_desc keeps the 2 * nb_tx_desc complq 
from overflowing. */
+       txq->tx_pending_pkts = rte_zmalloc_socket("idpf_tx_pending",
+                       sizeof(struct rte_mbuf *) * txq->nb_tx_desc,
+                       RTE_CACHE_LINE_SIZE, socket_id);
+       if (txq->tx_pending_pkts == NULL) {
+               DRV_LOG(ERR, "Failed to alloc idpf tx_pending shadow ring");
+               return -ENOMEM;
+       }
+       txq->tx_next_compl_tag = 0;
+
+       return 0;
+}
+
+RTE_EXPORT_INTERNAL_SYMBOL(idpf_qc_split_tx_pending_free)
+void
+idpf_qc_split_tx_pending_free(struct ci_tx_queue *txq)
+{
+       uint32_t i;
+
+       if (txq->tx_pending_pkts == NULL)
+               return;
+
+       for (i = 0; i < txq->nb_tx_desc; i++) {
+               if (txq->tx_pending_pkts[i] != NULL)
+                       rte_pktmbuf_free(txq->tx_pending_pkts[i]);
+       }
+       rte_free(txq->tx_pending_pkts);
+       txq->tx_pending_pkts = NULL;
 }
 
 RTE_EXPORT_INTERNAL_SYMBOL(idpf_qc_split_tx_complq_reset)
@@ -387,6 +430,7 @@ idpf_qc_tx_queue_release(void *txq)
                rte_free(q->complq);
        }
 
+       idpf_qc_split_tx_pending_free(q);
        ci_txq_release_all_mbufs(q, false);
        rte_free(q->rs_last_id);
        rte_free(q->sw_ring);
@@ -787,7 +831,6 @@ idpf_split_tx_free(struct idpf_complq *cq)
        volatile struct idpf_splitq_tx_compl_desc *compl_ring = cq->compl_ring;
        volatile struct idpf_splitq_tx_compl_desc *txd;
        uint16_t next = cq->tx_tail;
-       struct ci_tx_entry *txe;
        struct ci_tx_queue *txq;
        uint16_t gen, qid, q_head;
        uint16_t nb_desc_clean;
@@ -824,26 +867,21 @@ idpf_split_tx_free(struct idpf_complq *cq)
                txq->nb_tx_free += nb_desc_clean;
                txq->last_desc_cleaned = q_head;
                break;
-       case IDPF_TXD_COMPLT_RS:
-               /* Walk from first segment to EOP, freeing each segment. */
-               txe = &txq->sw_ring[q_head];
-               if (txe->mbuf != NULL) {
-                       uint16_t first = txe->first_id;
-                       uint16_t idx = first;
-                       uint16_t end = (q_head + 1 == txq->sw_nb_desc) ?
-                                       0 : q_head + 1;
-
-                       do {
-                               txe = &txq->sw_ring[idx];
-                               if (txe->mbuf != NULL) {
-                                       rte_pktmbuf_free_seg(txe->mbuf);
-                                       txe->mbuf = NULL;
-                               }
-                               idx = (idx + 1 == txq->sw_nb_desc) ?
-                                       0 : idx + 1;
-                       } while (idx != end);
+       case IDPF_TXD_COMPLT_RS: {
+               /* Shadow ring indexed by the software-defined compl_tag is
+                * the sole source of truth for RS-time mbuf ownership; free
+                * the whole multi-seg chain via mbuf->next in one call.
+                */
+               uint16_t tag = q_head;
+
+               if (unlikely(tag >= txq->nb_tx_desc)) {
+                       TX_LOG(ERR, "invalid completion tag %u.", tag);
+               } else if (txq->tx_pending_pkts[tag] != NULL) {
+                       rte_pktmbuf_free(txq->tx_pending_pkts[tag]);
+                       txq->tx_pending_pkts[tag] = NULL;
                }
                break;
+       }
        default:
                TX_LOG(ERR, "unknown completion type.");
                return;
@@ -915,10 +953,8 @@ idpf_dp_splitq_xmit_pkts(void *tx_queue, struct rte_mbuf 
**tx_pkts,
        struct ci_tx_queue *txq = (struct ci_tx_queue *)tx_queue;
        volatile struct idpf_flex_tx_sched_desc *txr;
        volatile struct idpf_flex_tx_sched_desc *txd;
-       struct ci_tx_entry *sw_ring;
        union ci_tx_offload tx_offload = {0};
-       struct ci_tx_entry *txe, *txn;
-       uint16_t nb_used, tx_id, sw_id;
+       uint16_t nb_used, tx_id;
        struct rte_mbuf *tx_pkt;
        uint16_t nb_to_clean;
        uint16_t nb_tx = 0;
@@ -930,10 +966,7 @@ idpf_dp_splitq_xmit_pkts(void *tx_queue, struct rte_mbuf 
**tx_pkts,
                return nb_tx;
 
        txr = txq->desc_ring;
-       sw_ring = txq->sw_ring;
        tx_id = txq->tx_tail;
-       sw_id = txq->sw_tail;
-       txe = &sw_ring[sw_id];
 
        for (nb_tx = 0; nb_tx < nb_pkts; nb_tx++) {
                tx_pkt = tx_pkts[nb_tx];
@@ -976,6 +1009,19 @@ idpf_dp_splitq_xmit_pkts(void *tx_queue, struct rte_mbuf 
**tx_pkts,
                if (txq->nb_tx_free < nb_used)
                        break;
 
+               /* RE reclaims descriptors before RS, so tag occupancy is the
+                * only bound on packets awaiting RS completion.
+                */
+               uint16_t tag = txq->tx_next_compl_tag;
+
+               if (unlikely(txq->tx_pending_pkts[tag] != NULL)) {
+                       nb_to_clean = 2 * txq->tx_rs_thresh;
+                       while (nb_to_clean--)
+                               idpf_split_tx_free(txq->complq);
+                       if (txq->tx_pending_pkts[tag] != NULL)
+                               break;
+               }
+
                if (ol_flags & CI_TX_CKSUM_OFFLOAD_MASK)
                        cmd_dtype = IDPF_TXD_FLEX_FLOW_CMD_CS_EN;
 
@@ -991,8 +1037,6 @@ idpf_dp_splitq_xmit_pkts(void *tx_queue, struct rte_mbuf 
**tx_pkts,
                                tx_id = 0;
                }
 
-               uint16_t first_sw_id = sw_id;
-
                do {
                        uint16_t slen = tx_pkt->data_len;
                        rte_iova_t buf_dma_addr = rte_mbuf_data_iova(tx_pkt);
@@ -1003,14 +1047,12 @@ idpf_dp_splitq_xmit_pkts(void *tx_queue, struct 
rte_mbuf **tx_pkts,
                        while ((ol_flags & RTE_MBUF_F_TX_TCP_SEG) &&
                                        unlikely(slen > CI_MAX_DATA_PER_TXD)) {
                                txd = &txr[tx_id];
-                               txn = &sw_ring[txe->next_id];
-                               txe->mbuf = NULL;
 
                                txd->buf_addr = rte_cpu_to_le_64(buf_dma_addr);
                                txd->qw1.cmd_dtype = cmd_dtype |
                                        IDPF_TX_DESC_DTYPE_FLEX_FLOW_SCHE;
                                txd->qw1.rxr_bufsize = CI_MAX_DATA_PER_TXD;
-                               txd->qw1.compl_tag = sw_id;
+                               txd->qw1.compl_tag = tag;
 
                                buf_dma_addr += CI_MAX_DATA_PER_TXD;
                                slen -= CI_MAX_DATA_PER_TXD;
@@ -1018,33 +1060,29 @@ idpf_dp_splitq_xmit_pkts(void *tx_queue, struct 
rte_mbuf **tx_pkts,
                                tx_id++;
                                if (tx_id == txq->nb_tx_desc)
                                        tx_id = 0;
-                               sw_id = txe->next_id;
-                               txe = txn;
                        }
 
                        txd = &txr[tx_id];
-                       txn = &sw_ring[txe->next_id];
-                       txe->mbuf = tx_pkt;
 
                        /* Setup TX descriptor */
                        txd->buf_addr = rte_cpu_to_le_64(buf_dma_addr);
                        txd->qw1.cmd_dtype = cmd_dtype |
                                IDPF_TX_DESC_DTYPE_FLEX_FLOW_SCHE;
                        txd->qw1.rxr_bufsize = slen;
-                       txd->qw1.compl_tag = sw_id;
+                       /* Spec: COMPLETION_TAG must be identical on all data 
descs of a packet. */
+                       txd->qw1.compl_tag = tag;
                        tx_id++;
                        if (tx_id == txq->nb_tx_desc)
                                tx_id = 0;
-                       sw_id = txe->next_id;
-                       txe = txn;
                        tx_pkt = tx_pkt->next;
                } while (tx_pkt);
 
                /* fill the last descriptor with End of Packet (EOP) bit */
                txd->qw1.cmd_dtype |= IDPF_TXD_FLEX_FLOW_CMD_EOP;
 
-               /* Record first sw_id at EOP so completion can walk forward. */
-               sw_ring[txd->qw1.compl_tag].first_id = first_sw_id;
+               txq->tx_pending_pkts[tag] = tx_pkts[nb_tx];
+               if (++txq->tx_next_compl_tag == txq->nb_tx_desc)
+                       txq->tx_next_compl_tag = 0;
 
                txq->nb_tx_free = (uint16_t)(txq->nb_tx_free - nb_used);
                txq->rs_compl_count += nb_used;
@@ -1060,7 +1098,6 @@ idpf_dp_splitq_xmit_pkts(void *tx_queue, struct rte_mbuf 
**tx_pkts,
        if (likely(nb_tx > 0)) {
                IDPF_PCI_REG_WRITE(txq->qtx_tail, tx_id);
                txq->tx_tail = tx_id;
-               txq->sw_tail = sw_id;
        }
 
        return nb_tx;
diff --git a/drivers/net/intel/idpf/idpf_common_rxtx.h 
b/drivers/net/intel/idpf/idpf_common_rxtx.h
index b2d33287df..3365631d80 100644
--- a/drivers/net/intel/idpf/idpf_common_rxtx.h
+++ b/drivers/net/intel/idpf/idpf_common_rxtx.h
@@ -216,6 +216,10 @@ void idpf_qc_single_rx_queue_reset(struct idpf_rx_queue 
*rxq);
 __rte_internal
 void idpf_qc_split_tx_descq_reset(struct ci_tx_queue *txq);
 __rte_internal
+int idpf_qc_split_tx_pending_alloc(struct ci_tx_queue *txq, unsigned int 
socket_id);
+__rte_internal
+void idpf_qc_split_tx_pending_free(struct ci_tx_queue *txq);
+__rte_internal
 void idpf_qc_split_tx_complq_reset(struct idpf_complq *cq);
 __rte_internal
 void idpf_splitq_rearm_common(struct idpf_rx_queue *rx_bufq);
diff --git a/drivers/net/intel/idpf/idpf_rxtx.c 
b/drivers/net/intel/idpf/idpf_rxtx.c
index 077a92a8a9..0d41cd87eb 100644
--- a/drivers/net/intel/idpf/idpf_rxtx.c
+++ b/drivers/net/intel/idpf/idpf_rxtx.c
@@ -495,6 +495,9 @@ idpf_tx_queue_setup(struct rte_eth_dev *dev, uint16_t 
queue_idx,
                idpf_qc_single_tx_queue_reset(txq);
        } else {
                txq->desc_ring = mz->addr;
+               ret = idpf_qc_split_tx_pending_alloc(txq, socket_id);
+               if (ret != 0)
+                       goto err_pending_alloc;
                idpf_qc_split_tx_descq_reset(txq);
 
                /* Setup tx completion queue if split model */
@@ -512,6 +515,8 @@ idpf_tx_queue_setup(struct rte_eth_dev *dev, uint16_t 
queue_idx,
        return 0;
 
 err_complq_setup:
+       idpf_qc_split_tx_pending_free(txq);
+err_pending_alloc:
        rte_free(txq->rs_last_id);
 err_rs_last_id_alloc:
        rte_free(txq->sw_ring);
-- 
2.34.1

Reply via email to