1. Rework the Tx completion flush to walk descriptors one at a time and<br />   free each completed segment with rte_pktmbuf_free_seg(), add prefetch<br />   hints on the next descriptor cache line, switch the ring-held mbufs<br />   in zxdh_dev_free_mbufs() to rte_pktmbuf_free_seg().<br /> <br />2. The flush relies on `desc[k].id == k` already being set by the<br />   enqueue paths (`zxdh_xmit_enqueue_push` writes `dp->id = id` and<br />   `zxdh_xmit_enqueue_append` writes `start_dp[idx].id = idx`) and<br />   preserved by the device.<br />   To match the commercial branch the flush also rewrites<br />   `desc[used_idx].id = used_idx` and keeps the inner<br />   `do { ... } while (curr_id != id)`; under the invariant the inner<br />   loop runs once, and the curr_id-anchored walk backstops the case<br />   where the device overwrites the id.<br /> <br />3. Guard the walk with a `budget` counter initialised to `vq_nentries`,<br />   break on `id >= size`, and mask the prefetch index with `(size - 1)`<br />   so the walk and `free_cnt` stay bounded even if the device writes<br />   an out-of-range or stale id.<br /> <br />4. Document the Tx xmit changes in the 26.11 release notes.<br /> <br />Signed-off-by: Junlong Wang <wang.junlong1@zte.com.cn> <br />---<br /> doc/guides/rel_notes/release_26_11.rst |   2 +<br /> drivers/net/zxdh/zxdh_ethdev.c         |   4 +-<br /> drivers/net/zxdh/zxdh_queue.h          |   1 -<br /> drivers/net/zxdh/zxdh_rxtx.c           | 136 ++++++++++---------------<br /> 4 files changed, 57 insertions(+), 86 deletions(-)<br /> <br />diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst<br />index bf1f8d7334..2d6d06d228 100644<br />--- a/doc/guides/rel_notes/release_26_11.rst<br />+++ b/doc/guides/rel_notes/release_26_11.rst<br />@@ -71,6 +71,8 @@ New Features<br />     is selected when the MTU fits in a single buffer.<br />   * Optimized the packed-ring Rx recv path.<br />   * Changed the set of per-queue xstats counters.<br />+  * Optimized the packed-ring Tx xmit path with per-descriptor mbuf<br />+    free (``rte_pktmbuf_free_seg``) and prefetch hints.<br />  <br /> Removed Items<br /> -------------<br />diff --git a/drivers/net/zxdh/zxdh_ethdev.c b/drivers/net/zxdh/zxdh_ethdev.c<br />index 493d25a766..7c2f894642 100644<br />--- a/drivers/net/zxdh/zxdh_ethdev.c<br />+++ b/drivers/net/zxdh/zxdh_ethdev.c<br />@@ -489,7 +489,7 @@ zxdh_dev_free_mbufs(struct rte_eth_dev *dev)<br />         if (!vq)<br />             continue;<br />         while ((buf = zxdh_queue_detach_unused(vq)) != NULL)<br />-            rte_pktmbuf_free(buf);<br />+            rte_pktmbuf_free_seg(buf);<br />         PMD_DRV_LOG(DEBUG, "freeing %s[%d] used and unused buf",<br />         "rxq", i * 2);<br />     }<br />@@ -498,7 +498,7 @@ zxdh_dev_free_mbufs(struct rte_eth_dev *dev)<br />         if (!vq)<br />             continue;<br />         while ((buf = zxdh_queue_detach_unused(vq)) != NULL)<br />-            rte_pktmbuf_free(buf);<br />+            rte_pktmbuf_free_seg(buf);<br />         PMD_DRV_LOG(DEBUG, "freeing %s[%d] used and unused buf",<br />         "txq", i * 2 + 1);<br />     }<br />diff --git a/drivers/net/zxdh/zxdh_queue.h b/drivers/net/zxdh/zxdh_queue.h<br />index 49970fbef2..47048f6926 100644<br />--- a/drivers/net/zxdh/zxdh_queue.h<br />+++ b/drivers/net/zxdh/zxdh_queue.h<br />@@ -124,7 +124,6 @@ struct zxdh_vring_packed {<br />  <br /> struct zxdh_vq_desc_extra {<br />     void *cookie;<br />-    uint16_t ndescs;<br />     uint16_t next;<br /> };<br />  <br />diff --git a/drivers/net/zxdh/zxdh_rxtx.c b/drivers/net/zxdh/zxdh_rxtx.c<br />index 004e528adc..8e2fae7c6c 100644<br />--- a/drivers/net/zxdh/zxdh_rxtx.c<br />+++ b/drivers/net/zxdh/zxdh_rxtx.c<br />@@ -114,6 +114,8 @@<br />         RTE_MBUF_F_TX_SEC_OFFLOAD |     \<br />         RTE_MBUF_F_TX_UDP_SEG)<br />  <br />+#define ZXDH_NEXT_CACHELINE_OFF_16B  (RTE_CACHE_LINE_SIZE / 16)<br />+<br /> uint32_t zxdh_outer_l2_type[16] = {<br />     0,<br />     RTE_PTYPE_L2_ETHER,<br />@@ -201,43 +203,6 @@ uint32_t zxdh_inner_l4_type[16] = {<br />     0,<br /> };<br />  <br />-static void<br />-zxdh_xmit_cleanup_inorder_packed(struct zxdh_virtqueue *vq, int32_t num)<br />-{<br />-    uint16_t used_idx = 0;<br />-    uint16_t id       = 0;<br />-    uint16_t curr_id  = 0;<br />-    uint16_t free_cnt = 0;<br />-    uint16_t size     = vq->vq_nentries;<br />-    struct zxdh_vring_packed_desc *desc = vq->vq_packed.ring.desc;<br />-    struct zxdh_vq_desc_extra     *dxp  = NULL;<br />-<br />-    used_idx = vq->vq_used_cons_idx;<br />-    /* desc_is_used has a load-acquire or rte_io_rmb inside<br />-     * and wait for used desc in virtqueue.<br />-     */<br />-    while (num > 0 && desc_is_used(&desc[used_idx], vq)) {<br />-        id = desc[used_idx].id;<br />-        do {<br />-            curr_id = used_idx;<br />-            dxp = &vq->vq_descx[used_idx];<br />-            used_idx += dxp->ndescs;<br />-            free_cnt += dxp->ndescs;<br />-            num -= dxp->ndescs;<br />-            if (used_idx >= size) {<br />-                used_idx -= size;<br />-                vq->used_wrap_counter ^= 1;<br />-            }<br />-            if (dxp->cookie != NULL) {<br />-                rte_pktmbuf_free(dxp->cookie);<br />-                dxp->cookie = NULL;<br />-            }<br />-        } while (curr_id != id);<br />-    }<br />-    vq->vq_used_cons_idx = used_idx;<br />-    vq->vq_free_cnt += free_cnt;<br />-}<br />-<br /> static inline uint16_t<br /> zxdh_get_mtu(struct zxdh_virtqueue *vq)<br /> {<br />@@ -334,7 +299,7 @@ zxdh_xmit_fill_net_hdr(struct zxdh_virtqueue *vq, struct rte_mbuf *cookie,<br /> }<br />  <br /> static inline void<br />-zxdh_enqueue_xmit_packed_fast(struct zxdh_virtnet_tx *txvq,<br />+zxdh_xmit_enqueue_push(struct zxdh_virtnet_tx *txvq,<br />                         struct rte_mbuf *cookie)<br /> {<br />     struct zxdh_virtqueue *vq = txvq->vq;<br />@@ -345,7 +310,6 @@ zxdh_enqueue_xmit_packed_fast(struct zxdh_virtnet_tx *txvq,<br />     uint8_t hdr_len = vq->hw->dl_net_hdr_len;<br />     struct zxdh_vring_packed_desc *dp = &vq->vq_packed.ring.desc[id];<br />  <br />-    dxp->ndescs = 1;<br />     dxp->cookie = cookie;<br />     hdr = rte_pktmbuf_mtod_offset(cookie, struct zxdh_net_hdr_dl *, -hdr_len);<br />     zxdh_xmit_fill_net_hdr(vq, cookie, hdr);<br />@@ -362,51 +326,56 @@ zxdh_enqueue_xmit_packed_fast(struct zxdh_virtnet_tx *txvq,<br /> }<br />  <br /> static inline void<br />-zxdh_enqueue_xmit_packed(struct zxdh_virtnet_tx *txvq,<br />+zxdh_xmit_enqueue_append(struct zxdh_virtnet_tx *txvq,<br />                         struct rte_mbuf *cookie,<br />                         uint16_t needed)<br /> {<br />     struct zxdh_tx_region *txr = txvq->zxdh_net_hdr_mz->addr;<br />     struct zxdh_virtqueue *vq = txvq->vq;<br />-    uint16_t id = vq->vq_avail_idx;<br />-    struct zxdh_vq_desc_extra *dxp = &vq->vq_descx[id];<br />     uint16_t head_idx = vq->vq_avail_idx;<br />     uint16_t idx = head_idx;<br />     struct zxdh_vring_packed_desc *start_dp = vq->vq_packed.ring.desc;<br />     struct zxdh_vring_packed_desc *head_dp = &vq->vq_packed.ring.desc[idx];<br />     struct zxdh_net_hdr_dl *hdr = NULL;<br />  <br />-    uint16_t head_flags = cookie->next ? ZXDH_VRING_DESC_F_NEXT : 0;<br />+    uint16_t id = vq->vq_avail_idx;<br />+    struct zxdh_vq_desc_extra *dxp = &vq->vq_descx[id];<br />     uint8_t hdr_len = vq->hw->dl_net_hdr_len;<br />+    uint16_t head_flags = 0;<br />  <br />-    dxp->ndescs = needed;<br />-    dxp->cookie = cookie;<br />-    head_flags |= vq->cached_flags;<br />+    dxp->cookie = NULL;<br />+    /*<br />+     * Head descriptor has no mbuf cookie. Per-segment cookies are<br />+     * stored on the segment descs so zxdh_xmit_fast_flush() can free<br />+     * each via rte_pktmbuf_free_seg(). zxdh_queue_detach_unused() walks<br />+     * the cookies on queue teardown and is the only reader that would<br />+     * otherwise observe a stray head cookie.<br />+     */<br />  <br />+    /* setup first tx ring slot to point to header stored in reserved region. */<br />     start_dp[idx].addr = txvq->zxdh_net_hdr_mem + RTE_PTR_DIFF(&txr[idx].tx_hdr, txr);<br />     start_dp[idx].len  = hdr_len;<br />-    head_flags |= ZXDH_VRING_DESC_F_NEXT;<br />+    start_dp[idx].id = idx;<br />+    head_flags |= vq->cached_flags | ZXDH_VRING_DESC_F_NEXT;<br />     hdr = (void *)&txr[idx].tx_hdr;<br />  <br />-    rte_prefetch1(hdr);<br />+    zxdh_xmit_fill_net_hdr(vq, cookie, hdr);<br />+<br />     idx++;<br />     if (idx >= vq->vq_nentries) {<br />         idx -= vq->vq_nentries;<br />         vq->cached_flags ^= ZXDH_VRING_PACKED_DESC_F_AVAIL_USED;<br />     }<br />  <br />-    zxdh_xmit_fill_net_hdr(vq, cookie, hdr);<br />-<br />     do {<br />         start_dp[idx].addr = rte_pktmbuf_iova(cookie);<br />         start_dp[idx].len  = cookie->data_len;<br />-        start_dp[idx].id = id;<br />-        if (likely(idx != head_idx)) {<br />-            uint16_t flags = cookie->next ? ZXDH_VRING_DESC_F_NEXT : 0;<br />+        start_dp[idx].id = idx;<br />  <br />-            flags |= vq->cached_flags;<br />-            start_dp[idx].flags = flags;<br />-        }<br />+        vq->vq_descx[idx].cookie = cookie;<br />+        uint16_t flags = cookie->next ? ZXDH_VRING_DESC_F_NEXT : 0;<br />+        flags |= vq->cached_flags;<br />+        start_dp[idx].flags = flags;<br />  <br />         idx++;<br />         if (idx >= vq->vq_nentries) {<br />@@ -430,7 +399,7 @@ zxdh_update_packet_stats(struct zxdh_virtnet_stats *stats, struct rte_mbuf *mbuf<br /> }<br />  <br /> static void<br />-zxdh_xmit_flush(struct zxdh_virtqueue *vq)<br />+zxdh_xmit_fast_flush(struct zxdh_virtqueue *vq)<br /> {<br />     uint16_t id       = 0;<br />     uint16_t curr_id  = 0;<br />@@ -439,27 +408,40 @@ zxdh_xmit_flush(struct zxdh_virtqueue *vq)<br />     struct zxdh_vring_packed_desc *desc = vq->vq_packed.ring.desc;<br />     struct zxdh_vq_desc_extra     *dxp  = NULL;<br />     uint16_t used_idx = vq->vq_used_cons_idx;<br />+    uint16_t budget = size;<br />  <br />     /*<br />      * The function desc_is_used performs a load-acquire operation<br />      * or calls rte_io_rmb to ensure memory consistency. It waits<br />      * for a used descriptor in the virtqueue.<br />      */<br />-    while (desc_is_used(&desc[used_idx], vq)) {<br />+    while (budget > 0 && desc_is_used(&desc[used_idx], vq)) {<br />+        /*<br />+         * vq_nentries is validated as power-of-two in<br />+         * zxdh_queue_desc_pre_setup(), so mask the prefetch index to keep<br />+         * it inside desc[] when used_idx is near the end of the ring.<br />+         */<br />+        rte_prefetch0(&desc[(used_idx + ZXDH_NEXT_CACHELINE_OFF_16B) & (size - 1)]);<br />         id = desc[used_idx].id;<br />+        if (unlikely(id >= size))<br />+            break;<br />         do {<br />+            desc[used_idx].id = used_idx;<br />             curr_id = used_idx;<br />             dxp = &vq->vq_descx[used_idx];<br />-            used_idx += dxp->ndescs;<br />-            free_cnt += dxp->ndescs;<br />-            if (used_idx >= size) {<br />-                used_idx -= size;<br />-                vq->used_wrap_counter ^= 1;<br />-            }<br />             if (dxp->cookie != NULL) {<br />-                rte_pktmbuf_free(dxp->cookie);<br />+                rte_pktmbuf_free_seg(dxp->cookie);<br />                 dxp->cookie = NULL;<br />             }<br />+            used_idx += 1;<br />+            free_cnt += 1;<br />+            budget -= 1;<br />+            if (unlikely(used_idx == size)) {<br />+                used_idx = 0;<br />+                vq->used_wrap_counter ^= 1;<br />+            }<br />+            if (unlikely(budget == 0))<br />+                break;<br />         } while (curr_id != id);<br />     }<br />     vq->vq_used_cons_idx = used_idx;<br />@@ -473,13 +455,12 @@ zxdh_xmit_pkts_packed(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkt<br />     struct zxdh_virtqueue  *vq   = txvq->vq;<br />     uint16_t nb_tx = 0;<br />  <br />-    zxdh_xmit_flush(vq);<br />+    zxdh_xmit_fast_flush(vq);<br />  <br />     for (nb_tx = 0; nb_tx < nb_pkts; nb_tx++) {<br />         struct rte_mbuf *txm = tx_pkts[nb_tx];<br />         int32_t can_push     = 0;<br />         int32_t slots        = 0;<br />-        int32_t need         = 0;<br />  <br />         rte_prefetch0(txm);<br />         /* optimize ring usage */<br />@@ -496,26 +477,15 @@ zxdh_xmit_pkts_packed(void *tx_queue, struct rte_mbuf **tx_pkts, uint16_t nb_pkt<br />          * default    => number of segments + 1<br />          **/<br />         slots = txm->nb_segs + !can_push;<br />-        need = slots - vq->vq_free_cnt;<br />-        /* Positive value indicates it need free vring descriptors */<br />-        if (unlikely(need > 0)) {<br />-            zxdh_xmit_cleanup_inorder_packed(vq, need);<br />-            need = slots - vq->vq_free_cnt;<br />-            if (unlikely(need > 0)) {<br />-                PMD_TX_LOG(ERR,<br />-                        " No enough %d free tx descriptors to transmit." <br />-                        "freecnt %d",<br />-                        need,<br />-                        vq->vq_free_cnt);<br />-                break;<br />-            }<br />-        }<br />+<br />+        if (unlikely(slots > vq->vq_free_cnt))<br />+            break;<br />  <br />         /* Enqueue Packet buffers */<br />         if (can_push)<br />-            zxdh_enqueue_xmit_packed_fast(txvq, txm);<br />+            zxdh_xmit_enqueue_push(txvq, txm);<br />         else<br />-            zxdh_enqueue_xmit_packed(txvq, txm, slots);<br />+            zxdh_xmit_enqueue_append(txvq, txm, slots);<br />         zxdh_update_packet_stats(&txvq->stats, txm);<br />     }<br />     txvq->stats.packets += nb_tx;<br />--  <br />2.27.0<br />