common/mlx5: introduce common library
[dpdk.git] / drivers / net / mlx5 / mlx5_rxtx.c
index 1ea5960..d8f6671 100644 (file)
 #include <rte_cycles.h>
 #include <rte_flow.h>
 
+#include <mlx5_devx_cmds.h>
+#include <mlx5_prm.h>
+
+#include "mlx5_defs.h"
 #include "mlx5.h"
 #include "mlx5_utils.h"
 #include "mlx5_rxtx.h"
 #include "mlx5_autoconf.h"
-#include "mlx5_defs.h"
-#include "mlx5_prm.h"
 
 /* TX burst subroutines return codes. */
 enum mlx5_txcmp_code {
@@ -62,6 +64,7 @@ enum mlx5_txcmp_code {
 #define MLX5_TXOFF_CONFIG_VLAN (1u << 5) /* VLAN insertion supported.*/
 #define MLX5_TXOFF_CONFIG_METADATA (1u << 6) /* Flow metadata. */
 #define MLX5_TXOFF_CONFIG_EMPW (1u << 8) /* Enhanced MPW supported.*/
+#define MLX5_TXOFF_CONFIG_MPW (1u << 9) /* Legacy MPW supported.*/
 
 /* The most common offloads groups. */
 #define MLX5_TXOFF_CONFIG_NONE 0
@@ -653,10 +656,10 @@ check_err_cqe_seen(volatile struct mlx5_err_cqe *err_cqe)
  *   Pointer to the error CQE.
  *
  * @return
- *   Negative value if queue recovery failed,
- *   the last Tx buffer element to free otherwise.
+ *   Negative value if queue recovery failed, otherwise
+ *   the error completion entry is handled successfully.
  */
-int
+static int
 mlx5_tx_error_cqe_handle(struct mlx5_txq_data *restrict txq,
                         volatile struct mlx5_err_cqe *err_cqe)
 {
@@ -700,18 +703,14 @@ mlx5_tx_error_cqe_handle(struct mlx5_txq_data *restrict txq,
                         */
                        txq->stats.oerrors += ((txq->wqe_ci & wqe_m) -
                                                new_wqe_pi) & wqe_m;
-               if (tx_recover_qp(txq_ctrl) == 0) {
-                       txq->cq_ci++;
-                       /* Release all the remaining buffers. */
-                       return txq->elts_head;
+               if (tx_recover_qp(txq_ctrl)) {
+                       /* Recovering failed - retry later on the same WQE. */
+                       return -1;
                }
-               /* Recovering failed - try again later on the same WQE. */
-               return -1;
-       } else {
-               txq->cq_ci++;
+               /* Release all the remaining buffers. */
+               txq_free_elts(txq_ctrl);
        }
-       /* Do not release buffers. */
-       return txq->elts_tail;
+       return 0;
 }
 
 /**
@@ -1340,7 +1339,7 @@ mlx5_rx_burst(void *dpdk_rxq, struct rte_mbuf **pkts, uint16_t pkts_n)
                        }
                        pkt = seg;
                        assert(len >= (rxq->crc_present << 2));
-                       pkt->ol_flags = 0;
+                       pkt->ol_flags &= EXT_ATTACHED_MBUF;
                        /* If compressed, take hash result from mini-CQE. */
                        rss_hash_res = rte_be_to_cpu_32(mcqe == NULL ?
                                                        cqe->rx_hash_res :
@@ -2033,8 +2032,6 @@ mlx5_tx_copy_elts(struct mlx5_txq_data *restrict txq,
  *   Pointer to TX queue structure.
  * @param valid CQE pointer
  *   if not NULL update txq->wqe_pi and flush the buffers
- * @param itail
- *   if not negative - flush the buffers till this index.
  * @param olx
  *   Configured Tx offloads mask. It is fully defined at
  *   compile time and may be used for optimization.
@@ -2042,25 +2039,17 @@ mlx5_tx_copy_elts(struct mlx5_txq_data *restrict txq,
 static __rte_always_inline void
 mlx5_tx_comp_flush(struct mlx5_txq_data *restrict txq,
                   volatile struct mlx5_cqe *last_cqe,
-                  int itail,
                   unsigned int olx __rte_unused)
 {
-       uint16_t tail;
-
        if (likely(last_cqe != NULL)) {
+               uint16_t tail;
+
                txq->wqe_pi = rte_be_to_cpu_16(last_cqe->wqe_counter);
-               tail = ((volatile struct mlx5_wqe_cseg *)
-                       (txq->wqes + (txq->wqe_pi & txq->wqe_m)))->misc;
-       } else if (itail >= 0) {
-               tail = (uint16_t)itail;
-       } else {
-               return;
-       }
-       rte_compiler_barrier();
-       *txq->cq_db = rte_cpu_to_be_32(txq->cq_ci);
-       if (likely(tail != txq->elts_tail)) {
-               mlx5_tx_free_elts(txq, tail, olx);
-               assert(tail == txq->elts_tail);
+               tail = txq->fcqs[(txq->cq_ci - 1) & txq->cqe_m];
+               if (likely(tail != txq->elts_tail)) {
+                       mlx5_tx_free_elts(txq, tail, olx);
+                       assert(tail == txq->elts_tail);
+               }
        }
 }
 
@@ -2084,6 +2073,7 @@ mlx5_tx_handle_completion(struct mlx5_txq_data *restrict txq,
 {
        unsigned int count = MLX5_TX_COMP_MAX_CQE;
        volatile struct mlx5_cqe *last_cqe = NULL;
+       uint16_t ci = txq->cq_ci;
        int ret;
 
        static_assert(MLX5_CQE_STATUS_HW_OWN < 0, "Must be negative value");
@@ -2091,8 +2081,8 @@ mlx5_tx_handle_completion(struct mlx5_txq_data *restrict txq,
        do {
                volatile struct mlx5_cqe *cqe;
 
-               cqe = &txq->cqes[txq->cq_ci & txq->cqe_m];
-               ret = check_cqe(cqe, txq->cqe_s, txq->cq_ci);
+               cqe = &txq->cqes[ci & txq->cqe_m];
+               ret = check_cqe(cqe, txq->cqe_s, ci);
                if (unlikely(ret != MLX5_CQE_STATUS_SW_OWN)) {
                        if (likely(ret != MLX5_CQE_STATUS_ERR)) {
                                /* No new CQEs in completion queue. */
@@ -2106,33 +2096,53 @@ mlx5_tx_handle_completion(struct mlx5_txq_data *restrict txq,
                         * here, before we might perform SQ reset.
                         */
                        rte_wmb();
+                       txq->cq_ci = ci;
                        ret = mlx5_tx_error_cqe_handle
                                (txq, (volatile struct mlx5_err_cqe *)cqe);
+                       if (unlikely(ret < 0)) {
+                               /*
+                                * Some error occurred on queue error
+                                * handling, we do not advance the index
+                                * here, allowing to retry on next call.
+                                */
+                               return;
+                       }
                        /*
-                        * Flush buffers, update consuming index
-                        * if recovery succeeded. Otherwise
-                        * just try to recover later.
+                        * We are going to fetch all entries with
+                        * MLX5_CQE_SYNDROME_WR_FLUSH_ERR status.
+                        * The send queue is supposed to be empty.
                         */
+                       ++ci;
+                       txq->cq_pi = ci;
                        last_cqe = NULL;
-                       break;
+                       continue;
                }
                /* Normal transmit completion. */
-               ++txq->cq_ci;
+               assert(ci != txq->cq_pi);
+               assert((txq->fcqs[ci & txq->cqe_m] >> 16) == cqe->wqe_counter);
+               ++ci;
                last_cqe = cqe;
-#ifndef NDEBUG
-               if (txq->cq_pi)
-                       --txq->cq_pi;
-#endif
-       /*
-        * We have to restrict the amount of processed CQEs
-        * in one tx_burst routine call. The CQ may be large
-        * and many CQEs may be updated by the NIC in one
-        * transaction. Buffers freeing is time consuming,
-        * multiple iterations may introduce significant
-        * latency.
-        */
-       } while (--count);
-       mlx5_tx_comp_flush(txq, last_cqe, ret, olx);
+               /*
+                * We have to restrict the amount of processed CQEs
+                * in one tx_burst routine call. The CQ may be large
+                * and many CQEs may be updated by the NIC in one
+                * transaction. Buffers freeing is time consuming,
+                * multiple iterations may introduce significant
+                * latency.
+                */
+               if (likely(--count == 0))
+                       break;
+       } while (true);
+       if (likely(ci != txq->cq_ci)) {
+               /*
+                * Update completion queue consuming index
+                * and ring doorbell to notify hardware.
+                */
+               rte_compiler_barrier();
+               txq->cq_ci = ci;
+               *txq->cq_db = rte_cpu_to_be_32(ci);
+               mlx5_tx_comp_flush(txq, last_cqe, olx);
+       }
 }
 
 /**
@@ -2144,9 +2154,6 @@ mlx5_tx_handle_completion(struct mlx5_txq_data *restrict txq,
  *   Pointer to TX queue structure.
  * @param loc
  *   Pointer to burst routine local context.
- * @param multi,
- *   Routine is called from multi-segment sending loop,
- *   do not correct the elts_head according to the pkts_copy.
  * @param olx
  *   Configured Tx offloads mask. It is fully defined at
  *   compile time and may be used for optimization.
@@ -2154,13 +2161,12 @@ mlx5_tx_handle_completion(struct mlx5_txq_data *restrict txq,
 static __rte_always_inline void
 mlx5_tx_request_completion(struct mlx5_txq_data *restrict txq,
                           struct mlx5_txq_local *restrict loc,
-                          bool multi,
                           unsigned int olx)
 {
        uint16_t head = txq->elts_head;
        unsigned int part;
 
-       part = (MLX5_TXOFF_CONFIG(INLINE) || multi) ?
+       part = MLX5_TXOFF_CONFIG(INLINE) ?
               0 : loc->pkts_sent - loc->pkts_copy;
        head += part;
        if ((uint16_t)(head - txq->elts_comp) >= MLX5_TX_COMP_THRESH ||
@@ -2174,15 +2180,15 @@ mlx5_tx_request_completion(struct mlx5_txq_data *restrict txq,
                /* Request unconditional completion on last WQE. */
                last->cseg.flags = RTE_BE32(MLX5_COMP_ALWAYS <<
                                            MLX5_COMP_MODE_OFFSET);
-               /* Save elts_head in unused "immediate" field of WQE. */
-               last->cseg.misc = head;
-               /*
-                * A CQE slot must always be available. Count the
-                * issued CEQ "always" request instead of production
-                * index due to here can be CQE with errors and
-                * difference with ci may become inconsistent.
-                */
-               assert(txq->cqe_s > ++txq->cq_pi);
+               /* Save elts_head in dedicated free on completion queue. */
+#ifdef NDEBUG
+               txq->fcqs[txq->cq_pi++ & txq->cqe_m] = head;
+#else
+               txq->fcqs[txq->cq_pi++ & txq->cqe_m] = head |
+                                       (last->cseg.opcode >> 8) << 16;
+#endif
+               /* A CQE slot must always be available. */
+               assert((txq->cq_pi - txq->cq_ci) <= txq->cqe_s);
        }
 }
 
@@ -2240,6 +2246,9 @@ mlx5_tx_cseg_init(struct mlx5_txq_data *restrict txq,
 {
        struct mlx5_wqe_cseg *restrict cs = &wqe->cseg;
 
+       /* For legacy MPW replace the EMPW by TSO with modifier. */
+       if (MLX5_TXOFF_CONFIG(MPW) && opcode == MLX5_OPCODE_ENHANCED_MPSW)
+               opcode = MLX5_OPCODE_TSO | MLX5_OPC_MOD_MPW << 24;
        cs->opcode = rte_cpu_to_be_32((txq->wqe_ci << 8) | opcode);
        cs->sq_ds = rte_cpu_to_be_32(txq->qp_num_8s | ds);
        cs->flags = RTE_BE32(MLX5_COMP_ONLY_FIRST_ERR <<
@@ -3116,8 +3125,6 @@ mlx5_tx_packet_multi_tso(struct mlx5_txq_data *restrict txq,
        wqe->cseg.sq_ds = rte_cpu_to_be_32(txq->qp_num_8s | ds);
        txq->wqe_ci += (ds + 3) / 4;
        loc->wqe_free -= (ds + 3) / 4;
-       /* Request CQE generation if limits are reached. */
-       mlx5_tx_request_completion(txq, loc, true, olx);
        return MLX5_TXCMP_CODE_MULTI;
 }
 
@@ -3226,8 +3233,6 @@ mlx5_tx_packet_multi_send(struct mlx5_txq_data *restrict txq,
        } while (true);
        txq->wqe_ci += (ds + 3) / 4;
        loc->wqe_free -= (ds + 3) / 4;
-       /* Request CQE generation if limits are reached. */
-       mlx5_tx_request_completion(txq, loc, true, olx);
        return MLX5_TXCMP_CODE_MULTI;
 }
 
@@ -3384,8 +3389,6 @@ do_align:
        wqe->cseg.sq_ds = rte_cpu_to_be_32(txq->qp_num_8s | ds);
        txq->wqe_ci += (ds + 3) / 4;
        loc->wqe_free -= (ds + 3) / 4;
-       /* Request CQE generation if limits are reached. */
-       mlx5_tx_request_completion(txq, loc, true, olx);
        return MLX5_TXCMP_CODE_MULTI;
 }
 
@@ -3595,8 +3598,6 @@ mlx5_tx_burst_tso(struct mlx5_txq_data *restrict txq,
                --loc->elts_free;
                ++loc->pkts_sent;
                --pkts_n;
-               /* Request CQE generation if limits are reached. */
-               mlx5_tx_request_completion(txq, loc, false, olx);
                if (unlikely(!pkts_n || !loc->elts_free || !loc->wqe_free))
                        return MLX5_TXCMP_CODE_EXIT;
                loc->mbuf = *pkts++;
@@ -3669,6 +3670,7 @@ mlx5_tx_able_to_empw(struct mlx5_txq_data *restrict txq,
 
 /**
  * Check the next packet attributes to match with the eMPW batch ones.
+ * In addition, for legacy MPW the packet length is checked either.
  *
  * @param txq
  *   Pointer to TX queue structure.
@@ -3676,6 +3678,8 @@ mlx5_tx_able_to_empw(struct mlx5_txq_data *restrict txq,
  *   Pointer to Ethernet Segment of eMPW batch.
  * @param loc
  *   Pointer to burst routine local context.
+ * @param dlen
+ *   Length of previous packet in MPW descriptor.
  * @param olx
  *   Configured Tx offloads mask. It is fully defined at
  *   compile time and may be used for optimization.
@@ -3688,6 +3692,7 @@ static __rte_always_inline bool
 mlx5_tx_match_empw(struct mlx5_txq_data *restrict txq __rte_unused,
                   struct mlx5_wqe_eseg *restrict es,
                   struct mlx5_txq_local *restrict loc,
+                  uint32_t dlen,
                   unsigned int olx)
 {
        uint8_t swp_flags = 0;
@@ -3706,6 +3711,10 @@ mlx5_tx_match_empw(struct mlx5_txq_data *restrict txq __rte_unused,
                es->metadata != (loc->mbuf->ol_flags & PKT_TX_DYNF_METADATA ?
                                 *RTE_FLOW_DYNF_METADATA(loc->mbuf) : 0))
                return false;
+       /* Legacy MPW can send packets with the same lengt only. */
+       if (MLX5_TXOFF_CONFIG(MPW) &&
+           dlen != rte_pktmbuf_data_len(loc->mbuf))
+               return false;
        /* There must be no VLAN packets in eMPW loop. */
        if (MLX5_TXOFF_CONFIG(VLAN))
                assert(!(loc->mbuf->ol_flags & PKT_TX_VLAN_PKT));
@@ -3738,7 +3747,7 @@ mlx5_tx_sdone_empw(struct mlx5_txq_data *restrict txq,
                   struct mlx5_txq_local *restrict loc,
                   unsigned int ds,
                   unsigned int slen,
-                  unsigned int olx)
+                  unsigned int olx __rte_unused)
 {
        assert(!MLX5_TXOFF_CONFIG(INLINE));
 #ifdef MLX5_PMD_SOFT_COUNTERS
@@ -3753,8 +3762,6 @@ mlx5_tx_sdone_empw(struct mlx5_txq_data *restrict txq,
        loc->wqe_last->cseg.sq_ds = rte_cpu_to_be_32(txq->qp_num_8s | ds);
        txq->wqe_ci += (ds + 3) / 4;
        loc->wqe_free -= (ds + 3) / 4;
-       /* Request CQE generation if limits are reached. */
-       mlx5_tx_request_completion(txq, loc, false, olx);
 }
 
 /*
@@ -3797,8 +3804,6 @@ mlx5_tx_idone_empw(struct mlx5_txq_data *restrict txq,
        loc->wqe_last->cseg.sq_ds = rte_cpu_to_be_32(txq->qp_num_8s | len);
        txq->wqe_ci += (len + 3) / 4;
        loc->wqe_free -= (len + 3) / 4;
-       /* Request CQE generation if limits are reached. */
-       mlx5_tx_request_completion(txq, loc, false, olx);
 }
 
 /**
@@ -3876,7 +3881,9 @@ mlx5_tx_burst_empw_simple(struct mlx5_txq_data *restrict txq,
 
 next_empw:
                assert(NB_SEGS(loc->mbuf) == 1);
-               part = RTE_MIN(pkts_n, MLX5_EMPW_MAX_PACKETS);
+               part = RTE_MIN(pkts_n, MLX5_TXOFF_CONFIG(MPW) ?
+                                      MLX5_MPW_MAX_PACKETS :
+                                      MLX5_EMPW_MAX_PACKETS);
                if (unlikely(loc->elts_free < part)) {
                        /* We have no enough elts to save all mbufs. */
                        if (unlikely(loc->elts_free < MLX5_EMPW_MIN_PACKETS))
@@ -3906,6 +3913,10 @@ next_empw:
                eseg = &loc->wqe_last->eseg;
                dseg = &loc->wqe_last->dseg[0];
                loop = part;
+               /* Store the packet length for legacy MPW. */
+               if (MLX5_TXOFF_CONFIG(MPW))
+                       eseg->mss = rte_cpu_to_be_16
+                                       (rte_pktmbuf_data_len(loc->mbuf));
                for (;;) {
                        uint32_t dlen = rte_pktmbuf_data_len(loc->mbuf);
 #ifdef MLX5_PMD_SOFT_COUNTERS
@@ -3964,8 +3975,9 @@ next_empw:
                         * - check sum settings
                         * - metadata value
                         * - software parser settings
+                        * - packets length (legacy MPW only)
                         */
-                       if (!mlx5_tx_match_empw(txq, eseg, loc, olx)) {
+                       if (!mlx5_tx_match_empw(txq, eseg, loc, dlen, olx)) {
                                assert(loop);
                                part -= loop;
                                mlx5_tx_sdone_empw(txq, loc, part, slen, olx);
@@ -3992,8 +4004,6 @@ next_empw:
                txq->wqe_ci += (2 + part + 3) / 4;
                loc->wqe_free -= (2 + part + 3) / 4;
                pkts_n -= part;
-               /* Request CQE generation if limits are reached. */
-               mlx5_tx_request_completion(txq, loc, false, olx);
                if (unlikely(!pkts_n || !loc->elts_free || !loc->wqe_free))
                        return MLX5_TXCMP_CODE_EXIT;
                loc->mbuf = *pkts++;
@@ -4040,7 +4050,9 @@ mlx5_tx_burst_empw_inline(struct mlx5_txq_data *restrict txq,
                 * Limits the amount of packets in one WQE
                 * to improve CQE latency generation.
                 */
-               nlim = RTE_MIN(pkts_n, MLX5_EMPW_MAX_PACKETS);
+               nlim = RTE_MIN(pkts_n, MLX5_TXOFF_CONFIG(MPW) ?
+                                      MLX5_MPW_INLINE_MAX_PACKETS :
+                                      MLX5_EMPW_MAX_PACKETS);
                /* Check whether we have minimal amount WQEs */
                if (unlikely(loc->wqe_free <
                            ((2 + MLX5_EMPW_MIN_PACKETS + 3) / 4)))
@@ -4059,6 +4071,10 @@ mlx5_tx_burst_empw_inline(struct mlx5_txq_data *restrict txq,
                                  olx & ~MLX5_TXOFF_CONFIG_VLAN);
                eseg = &loc->wqe_last->eseg;
                dseg = &loc->wqe_last->dseg[0];
+               /* Store the packet length for legacy MPW. */
+               if (MLX5_TXOFF_CONFIG(MPW))
+                       eseg->mss = rte_cpu_to_be_16
+                                       (rte_pktmbuf_data_len(loc->mbuf));
                room = RTE_MIN(MLX5_WQE_SIZE_MAX / MLX5_WQE_SIZE,
                               loc->wqe_free) * MLX5_WQE_SIZE -
                                        MLX5_WQE_CSEG_SIZE -
@@ -4209,8 +4225,9 @@ next_mbuf:
                         * - check sum settings
                         * - metadata value
                         * - software parser settings
+                        * - packets length (legacy MPW only)
                         */
-                       if (!mlx5_tx_match_empw(txq, eseg, loc, olx))
+                       if (!mlx5_tx_match_empw(txq, eseg, loc, dlen, olx))
                                break;
                        /* Packet attributes match, continue the same eMPW. */
                        if ((uintptr_t)dseg >= (uintptr_t)txq->wqes_end)
@@ -4313,8 +4330,9 @@ mlx5_tx_burst_single_send(struct mlx5_txq_data *restrict txq,
                                 * free the packet immediately.
                                 */
                                rte_pktmbuf_free_seg(loc->mbuf);
-                       } else if (!MLX5_TXOFF_CONFIG(EMPW) &&
-                                  txq->inlen_mode) {
+                       } else if ((!MLX5_TXOFF_CONFIG(EMPW) ||
+                                    MLX5_TXOFF_CONFIG(MPW)) &&
+                                       txq->inlen_mode) {
                                /*
                                 * If minimal inlining is requested the eMPW
                                 * feature should be disabled due to data is
@@ -4469,8 +4487,6 @@ mlx5_tx_burst_single_send(struct mlx5_txq_data *restrict txq,
                }
                ++loc->pkts_sent;
                --pkts_n;
-               /* Request CQE generation if limits are reached. */
-               mlx5_tx_request_completion(txq, loc, false, olx);
                if (unlikely(!pkts_n || !loc->elts_free || !loc->wqe_free))
                        return MLX5_TXCMP_CODE_EXIT;
                loc->mbuf = *pkts++;
@@ -4749,6 +4765,8 @@ enter_send_single:
        /* Take a shortcut if nothing is sent. */
        if (unlikely(loc.pkts_sent == loc.pkts_loop))
                goto burst_exit;
+       /* Request CQE generation if limits are reached. */
+       mlx5_tx_request_completion(txq, &loc, olx);
        /*
         * Ring QP doorbell immediately after WQE building completion
         * to improve latencies. The pure software related data treatment
@@ -4769,8 +4787,18 @@ enter_send_single:
         *   impact under heavy loading conditions but the explicit write
         *   memory barrier is not required and it may improve core
         *   performance.
+        *
+        * - the legacy behaviour (prior 19.08 release) was to use some
+        *   heuristics to decide whether write memory barrier should
+        *   be performed. This behavior is supported with specifying
+        *   tx_db_nc=2, write barrier is skipped if application
+        *   provides the full recommended burst of packets, it
+        *   supposes the next packets are coming and the write barrier
+        *   will be issued on the next burst (after descriptor writing,
+        *   at least).
         */
-       mlx5_tx_dbrec_cond_wmb(txq, loc.wqe_last, !txq->db_nc);
+       mlx5_tx_dbrec_cond_wmb(txq, loc.wqe_last, !txq->db_nc &&
+                       (!txq->db_heu || pkts_n % MLX5_TX_DEFAULT_BURST));
        /* Not all of the mbufs may be stored into elts yet. */
        part = MLX5_TXOFF_CONFIG(INLINE) ? 0 : loc.pkts_sent - loc.pkts_copy;
        if (!MLX5_TXOFF_CONFIG(INLINE) && part) {
@@ -4938,6 +4966,34 @@ MLX5_TXOFF_DECL(iv,
                MLX5_TXOFF_CONFIG_INLINE | MLX5_TXOFF_CONFIG_VLAN |
                MLX5_TXOFF_CONFIG_METADATA)
 
+/*
+ * Generate routines with Legacy Multi-Packet Write support.
+ * This mode is supported by ConnectX-4LX only and imposes
+ * offload limitations, not supported:
+ *   - ACL/Flows (metadata are becoming meaningless)
+ *   - WQE Inline headers
+ *   - SRIOV (E-Switch offloads)
+ *   - VLAN insertion
+ *   - tunnel encapsulation/decapsulation
+ *   - TSO
+ */
+MLX5_TXOFF_DECL(none_mpw,
+               MLX5_TXOFF_CONFIG_NONE | MLX5_TXOFF_CONFIG_EMPW |
+               MLX5_TXOFF_CONFIG_MPW)
+
+MLX5_TXOFF_DECL(mci_mpw,
+               MLX5_TXOFF_CONFIG_MULTI | MLX5_TXOFF_CONFIG_CSUM |
+               MLX5_TXOFF_CONFIG_INLINE | MLX5_TXOFF_CONFIG_EMPW |
+               MLX5_TXOFF_CONFIG_MPW)
+
+MLX5_TXOFF_DECL(mc_mpw,
+               MLX5_TXOFF_CONFIG_MULTI | MLX5_TXOFF_CONFIG_CSUM |
+               MLX5_TXOFF_CONFIG_EMPW | MLX5_TXOFF_CONFIG_MPW)
+
+MLX5_TXOFF_DECL(i_mpw,
+               MLX5_TXOFF_CONFIG_INLINE | MLX5_TXOFF_CONFIG_EMPW |
+               MLX5_TXOFF_CONFIG_MPW)
+
 /*
  * Array of declared and compiled Tx burst function and corresponding
  * supported offloads set. The array is used to select the Tx burst
@@ -5040,7 +5096,6 @@ MLX5_TXOFF_INFO(mti,
                MLX5_TXOFF_CONFIG_INLINE |
                MLX5_TXOFF_CONFIG_METADATA)
 
-
 MLX5_TXOFF_INFO(mtv,
                MLX5_TXOFF_CONFIG_MULTI | MLX5_TXOFF_CONFIG_TSO |
                MLX5_TXOFF_CONFIG_VLAN |
@@ -5081,6 +5136,23 @@ MLX5_TXOFF_INFO(v,
 MLX5_TXOFF_INFO(iv,
                MLX5_TXOFF_CONFIG_INLINE | MLX5_TXOFF_CONFIG_VLAN |
                MLX5_TXOFF_CONFIG_METADATA)
+
+MLX5_TXOFF_INFO(none_mpw,
+               MLX5_TXOFF_CONFIG_NONE | MLX5_TXOFF_CONFIG_EMPW |
+               MLX5_TXOFF_CONFIG_MPW)
+
+MLX5_TXOFF_INFO(mci_mpw,
+               MLX5_TXOFF_CONFIG_MULTI | MLX5_TXOFF_CONFIG_CSUM |
+               MLX5_TXOFF_CONFIG_INLINE | MLX5_TXOFF_CONFIG_EMPW |
+               MLX5_TXOFF_CONFIG_MPW)
+
+MLX5_TXOFF_INFO(mc_mpw,
+               MLX5_TXOFF_CONFIG_MULTI | MLX5_TXOFF_CONFIG_CSUM |
+               MLX5_TXOFF_CONFIG_EMPW | MLX5_TXOFF_CONFIG_MPW)
+
+MLX5_TXOFF_INFO(i_mpw,
+               MLX5_TXOFF_CONFIG_INLINE | MLX5_TXOFF_CONFIG_EMPW |
+               MLX5_TXOFF_CONFIG_MPW)
 };
 
 /**
@@ -5163,11 +5235,8 @@ mlx5_select_tx_function(struct rte_eth_dev *dev)
        if (config->mps == MLX5_MPW_ENHANCED &&
            config->txq_inline_min <= 0) {
                /*
-                * The NIC supports Enhanced Multi-Packet Write.
-                * We do not support legacy MPW due to its
-                * hardware related problems, so we just ignore
-                * legacy MLX5_MPW settings. There should be no
-                * minimal required inline data.
+                * The NIC supports Enhanced Multi-Packet Write
+                * and does not require minimal inline data.
                 */
                olx |= MLX5_TXOFF_CONFIG_EMPW;
        }
@@ -5175,6 +5244,20 @@ mlx5_select_tx_function(struct rte_eth_dev *dev)
                /* We should support Flow metadata. */
                olx |= MLX5_TXOFF_CONFIG_METADATA;
        }
+       if (config->mps == MLX5_MPW) {
+               /*
+                * The NIC supports Legacy Multi-Packet Write.
+                * The MLX5_TXOFF_CONFIG_MPW controls the
+                * descriptor building method in combination
+                * with MLX5_TXOFF_CONFIG_EMPW.
+                */
+               if (!(olx & (MLX5_TXOFF_CONFIG_TSO |
+                            MLX5_TXOFF_CONFIG_SWP |
+                            MLX5_TXOFF_CONFIG_VLAN |
+                            MLX5_TXOFF_CONFIG_METADATA)))
+                       olx |= MLX5_TXOFF_CONFIG_EMPW |
+                              MLX5_TXOFF_CONFIG_MPW;
+       }
        /*
         * Scan the routines table to find the minimal
         * satisfying routine with requested offloads.
@@ -5243,7 +5326,11 @@ mlx5_select_tx_function(struct rte_eth_dev *dev)
                DRV_LOG(DEBUG, "\tVLANI (VLAN insertion)");
        if (txoff_func[m].olx & MLX5_TXOFF_CONFIG_METADATA)
                DRV_LOG(DEBUG, "\tMETAD (tx Flow metadata)");
-       if (txoff_func[m].olx & MLX5_TXOFF_CONFIG_EMPW)
-               DRV_LOG(DEBUG, "\tEMPW  (Enhanced MPW)");
+       if (txoff_func[m].olx & MLX5_TXOFF_CONFIG_EMPW) {
+               if (txoff_func[m].olx & MLX5_TXOFF_CONFIG_MPW)
+                       DRV_LOG(DEBUG, "\tMPW   (Legacy MPW)");
+               else
+                       DRV_LOG(DEBUG, "\tEMPW  (Enhanced MPW)");
+       }
        return txoff_func[m].func;
 }