net/mlx5: support multi-packet send
authorNélio Laranjeiro <nelio.laranjeiro@6wind.com>
Fri, 24 Jun 2016 13:17:57 +0000 (15:17 +0200)
committerBruce Richardson <bruce.richardson@intel.com>
Mon, 27 Jun 2016 14:17:52 +0000 (16:17 +0200)
This feature enables the TX burst function to emit up to 5 packets using
only two work queue entries (WQEs) on devices that support it. Saves PCI
bandwidth and improves performance.

Signed-off-by: Nelio Laranjeiro <nelio.laranjeiro@6wind.com>
Signed-off-by: Adrien Mazarguil <adrien.mazarguil@6wind.com>
Signed-off-by: Olga Shern <olgas@mellanox.com>
doc/guides/nics/mlx5.rst
drivers/net/mlx5/mlx5.c
drivers/net/mlx5/mlx5_ethdev.c
drivers/net/mlx5/mlx5_rxtx.c
drivers/net/mlx5/mlx5_rxtx.h
drivers/net/mlx5/mlx5_txq.c

index 9ada221..063c4a5 100644 (file)
@@ -171,6 +171,16 @@ Run-time configuration
 
   This option should be used in combination with ``txq_inline`` above.
 
+- ``txq_mpw_en`` parameter [int]
+
+  A nonzero value enables multi-packet send. This feature allows the TX
+  burst function to pack up to five packets in two descriptors in order to
+  save PCI bandwidth and improve performance at the cost of a slightly
+  higher CPU usage.
+
+  It is currently only supported on the ConnectX-4 Lx family of adapters.
+  Enabled by default.
+
 Prerequisites
 -------------
 
index 73069d2..5aa4adc 100644 (file)
@@ -81,6 +81,9 @@
  */
 #define MLX5_TXQS_MIN_INLINE "txqs_min_inline"
 
+/* Device parameter to enable multi-packet send WQEs. */
+#define MLX5_TXQ_MPW_EN "txq_mpw_en"
+
 /**
  * Retrieve integer value from environment variable.
  *
@@ -282,6 +285,8 @@ mlx5_args_check(const char *key, const char *val, void *opaque)
                priv->txq_inline = tmp;
        } else if (strcmp(MLX5_TXQS_MIN_INLINE, key) == 0) {
                priv->txqs_inline = tmp;
+       } else if (strcmp(MLX5_TXQ_MPW_EN, key) == 0) {
+               priv->mps = !!tmp;
        } else {
                WARN("%s: unknown parameter", key);
                return -EINVAL;
@@ -307,6 +312,7 @@ mlx5_args(struct priv *priv, struct rte_devargs *devargs)
                MLX5_RXQ_CQE_COMP_EN,
                MLX5_TXQ_INLINE,
                MLX5_TXQS_MIN_INLINE,
+               MLX5_TXQ_MPW_EN,
                NULL,
        };
        struct rte_kvargs *kvlist;
@@ -503,6 +509,7 @@ mlx5_pci_devinit(struct rte_pci_driver *pci_drv, struct rte_pci_device *pci_dev)
                priv->port = port;
                priv->pd = pd;
                priv->mtu = ETHER_MTU;
+               priv->mps = mps; /* Enable MPW by default if supported. */
                priv->cqe_comp = 1; /* Enable compression by default. */
                err = mlx5_args(priv, pci_dev->devargs);
                if (err) {
@@ -551,7 +558,12 @@ mlx5_pci_devinit(struct rte_pci_driver *pci_drv, struct rte_pci_device *pci_dev)
 
                priv_get_num_vfs(priv, &num_vfs);
                priv->sriov = (num_vfs || sriov);
-               priv->mps = mps;
+               if (priv->mps && !mps) {
+                       ERROR("multi-packet send not supported on this device"
+                             " (" MLX5_TXQ_MPW_EN ")");
+                       err = ENOTSUP;
+                       goto port_error;
+               }
                /* Allocate and register default RSS hash keys. */
                priv->rss_conf = rte_calloc(__func__, hash_rxq_init_n,
                                            sizeof((*priv->rss_conf)[0]), 0);
index aeea4ff..698a50e 100644 (file)
@@ -584,7 +584,8 @@ mlx5_dev_infos_get(struct rte_eth_dev *dev, struct rte_eth_dev_info *info)
                  DEV_RX_OFFLOAD_UDP_CKSUM |
                  DEV_RX_OFFLOAD_TCP_CKSUM) :
                 0);
-       info->tx_offload_capa = DEV_TX_OFFLOAD_VLAN_INSERT;
+       if (!priv->mps)
+               info->tx_offload_capa = DEV_TX_OFFLOAD_VLAN_INSERT;
        if (priv->hw_csum)
                info->tx_offload_capa |=
                        (DEV_TX_OFFLOAD_IPV4_CKSUM |
@@ -1318,7 +1319,17 @@ void
 priv_select_tx_function(struct priv *priv)
 {
        priv->dev->tx_pkt_burst = mlx5_tx_burst;
-       if (priv->txq_inline && (priv->txqs_n >= priv->txqs_inline)) {
+       /* Display warning for unsupported configurations. */
+       if (priv->sriov && priv->mps)
+               WARN("multi-packet send WQE cannot be used on a SR-IOV setup");
+       /* Select appropriate TX function. */
+       if ((priv->sriov == 0) && priv->mps && priv->txq_inline) {
+               priv->dev->tx_pkt_burst = mlx5_tx_burst_mpw_inline;
+               DEBUG("selected MPW inline TX function");
+       } else if ((priv->sriov == 0) && priv->mps) {
+               priv->dev->tx_pkt_burst = mlx5_tx_burst_mpw;
+               DEBUG("selected MPW TX function");
+       } else if (priv->txq_inline && (priv->txqs_n >= priv->txqs_inline)) {
                priv->dev->tx_pkt_burst = mlx5_tx_burst_inline;
                DEBUG("selected inline TX function (%u >= %u queues)",
                      priv->txqs_n, priv->txqs_inline);
index 2ca133e..4934d34 100644 (file)
@@ -801,6 +801,413 @@ mlx5_tx_burst_inline(void *dpdk_txq, struct rte_mbuf **pkts, uint16_t pkts_n)
        return i;
 }
 
+/**
+ * Open a MPW session.
+ *
+ * @param txq
+ *   Pointer to TX queue structure.
+ * @param mpw
+ *   Pointer to MPW session structure.
+ * @param length
+ *   Packet length.
+ */
+static inline void
+mlx5_mpw_new(struct txq *txq, struct mlx5_mpw *mpw, uint32_t length)
+{
+       uint16_t idx = txq->wqe_ci & (txq->wqe_n - 1);
+       volatile struct mlx5_wqe_data_seg (*dseg)[MLX5_MPW_DSEG_MAX] =
+               (volatile struct mlx5_wqe_data_seg (*)[])
+               (uintptr_t)&(*txq->wqes)[(idx + 1) & (txq->wqe_n - 1)];
+
+       mpw->state = MLX5_MPW_STATE_OPENED;
+       mpw->pkts_n = 0;
+       mpw->len = length;
+       mpw->total_len = 0;
+       mpw->wqe = &(*txq->wqes)[idx];
+       mpw->wqe->mpw.eseg.mss = htons(length);
+       mpw->wqe->mpw.eseg.inline_hdr_sz = 0;
+       mpw->wqe->mpw.eseg.rsvd0 = 0;
+       mpw->wqe->mpw.eseg.rsvd1 = 0;
+       mpw->wqe->mpw.eseg.rsvd2 = 0;
+       mpw->wqe->mpw.ctrl.data[0] = htonl((MLX5_OPC_MOD_MPW << 24) |
+                                          (txq->wqe_ci << 8) |
+                                          MLX5_OPCODE_LSO_MPW);
+       mpw->wqe->mpw.ctrl.data[2] = 0;
+       mpw->wqe->mpw.ctrl.data[3] = 0;
+       mpw->data.dseg[0] = &mpw->wqe->mpw.dseg[0];
+       mpw->data.dseg[1] = &mpw->wqe->mpw.dseg[1];
+       mpw->data.dseg[2] = &(*dseg)[0];
+       mpw->data.dseg[3] = &(*dseg)[1];
+       mpw->data.dseg[4] = &(*dseg)[2];
+}
+
+/**
+ * Close a MPW session.
+ *
+ * @param txq
+ *   Pointer to TX queue structure.
+ * @param mpw
+ *   Pointer to MPW session structure.
+ */
+static inline void
+mlx5_mpw_close(struct txq *txq, struct mlx5_mpw *mpw)
+{
+       unsigned int num = mpw->pkts_n;
+
+       /*
+        * Store size in multiple of 16 bytes. Control and Ethernet segments
+        * count as 2.
+        */
+       mpw->wqe->mpw.ctrl.data[1] = htonl(txq->qp_num_8s | (2 + num));
+       mpw->state = MLX5_MPW_STATE_CLOSED;
+       if (num < 3)
+               ++txq->wqe_ci;
+       else
+               txq->wqe_ci += 2;
+       tx_prefetch_wqe(txq, txq->wqe_ci);
+       tx_prefetch_wqe(txq, txq->wqe_ci + 1);
+}
+
+/**
+ * DPDK callback for TX with MPW support.
+ *
+ * @param dpdk_txq
+ *   Generic pointer to TX queue structure.
+ * @param[in] pkts
+ *   Packets to transmit.
+ * @param pkts_n
+ *   Number of packets in array.
+ *
+ * @return
+ *   Number of packets successfully transmitted (<= pkts_n).
+ */
+uint16_t
+mlx5_tx_burst_mpw(void *dpdk_txq, struct rte_mbuf **pkts, uint16_t pkts_n)
+{
+       struct txq *txq = (struct txq *)dpdk_txq;
+       uint16_t elts_head = txq->elts_head;
+       const unsigned int elts_n = txq->elts_n;
+       unsigned int i;
+       unsigned int max;
+       unsigned int comp;
+       struct mlx5_mpw mpw = {
+               .state = MLX5_MPW_STATE_CLOSED,
+       };
+
+       /* Prefetch first packet cacheline. */
+       tx_prefetch_cqe(txq, txq->cq_ci);
+       tx_prefetch_wqe(txq, txq->wqe_ci);
+       tx_prefetch_wqe(txq, txq->wqe_ci + 1);
+       /* Start processing. */
+       txq_complete(txq);
+       max = (elts_n - (elts_head - txq->elts_tail));
+       if (max > elts_n)
+               max -= elts_n;
+       assert(max >= 1);
+       assert(max <= elts_n);
+       /* Always leave one free entry in the ring. */
+       --max;
+       if (max == 0)
+               return 0;
+       if (max > pkts_n)
+               max = pkts_n;
+       for (i = 0; (i != max); ++i) {
+               struct rte_mbuf *buf = pkts[i];
+               volatile struct mlx5_wqe_data_seg *dseg;
+               unsigned int elts_head_next = (elts_head + 1) & (elts_n - 1);
+               uintptr_t addr;
+               uint32_t length;
+               uint32_t cs_flags = 0;
+
+               /* Should we enable HW CKSUM offload */
+               if (buf->ol_flags &
+                   (PKT_TX_IP_CKSUM | PKT_TX_TCP_CKSUM | PKT_TX_UDP_CKSUM))
+                       cs_flags = MLX5_ETH_WQE_L3_CSUM | MLX5_ETH_WQE_L4_CSUM;
+               /* Retrieve buffer information. */
+               addr = rte_pktmbuf_mtod(buf, uintptr_t);
+               length = DATA_LEN(buf);
+               /* Update element. */
+               (*txq->elts)[elts_head] = buf;
+               /* Start new session if packet differs. */
+               if ((mpw.state == MLX5_MPW_STATE_OPENED) &&
+                   ((mpw.len != length) ||
+                    (mpw.wqe->mpw.eseg.cs_flags != cs_flags)))
+                       mlx5_mpw_close(txq, &mpw);
+               if (mpw.state == MLX5_MPW_STATE_CLOSED) {
+                       mlx5_mpw_new(txq, &mpw, length);
+                       mpw.wqe->mpw.eseg.cs_flags = cs_flags;
+               }
+               dseg = mpw.data.dseg[mpw.pkts_n];
+               *dseg = (struct mlx5_wqe_data_seg){
+                       .byte_count = htonl(length),
+                       .lkey = txq_mp2mr(txq, txq_mb2mp(buf)),
+                       .addr = htonll(addr),
+               };
+               ++mpw.pkts_n;
+               if (mpw.pkts_n == MLX5_MPW_DSEG_MAX)
+                       mlx5_mpw_close(txq, &mpw);
+               elts_head = elts_head_next;
+#ifdef MLX5_PMD_SOFT_COUNTERS
+               /* Increment sent bytes counter. */
+               txq->stats.obytes += length;
+#endif
+       }
+       /* Take a shortcut if nothing must be sent. */
+       if (unlikely(i == 0))
+               return 0;
+       /* Check whether completion threshold has been reached. */
+       comp = txq->elts_comp + i;
+       if (comp >= MLX5_TX_COMP_THRESH) {
+               volatile union mlx5_wqe *wqe = mpw.wqe;
+
+               /* Request completion on last WQE. */
+               wqe->mpw.ctrl.data[2] = htonl(8);
+               /* Save elts_head in unused "immediate" field of WQE. */
+               wqe->mpw.ctrl.data[3] = elts_head;
+               txq->elts_comp = 0;
+       } else {
+               txq->elts_comp = comp;
+       }
+#ifdef MLX5_PMD_SOFT_COUNTERS
+       /* Increment sent packets counter. */
+       txq->stats.opackets += i;
+#endif
+       /* Ring QP doorbell. */
+       if (mpw.state == MLX5_MPW_STATE_OPENED)
+               mlx5_mpw_close(txq, &mpw);
+       mlx5_tx_dbrec(txq);
+       txq->elts_head = elts_head;
+       return i;
+}
+
+/**
+ * Open a MPW inline session.
+ *
+ * @param txq
+ *   Pointer to TX queue structure.
+ * @param mpw
+ *   Pointer to MPW session structure.
+ * @param length
+ *   Packet length.
+ */
+static inline void
+mlx5_mpw_inline_new(struct txq *txq, struct mlx5_mpw *mpw, uint32_t length)
+{
+       uint16_t idx = txq->wqe_ci & (txq->wqe_n - 1);
+
+       mpw->state = MLX5_MPW_INL_STATE_OPENED;
+       mpw->pkts_n = 0;
+       mpw->len = length;
+       mpw->total_len = 0;
+       mpw->wqe = &(*txq->wqes)[idx];
+       mpw->wqe->mpw_inl.ctrl.data[0] = htonl((MLX5_OPC_MOD_MPW << 24) |
+                                              (txq->wqe_ci << 8) |
+                                              MLX5_OPCODE_LSO_MPW);
+       mpw->wqe->mpw_inl.ctrl.data[2] = 0;
+       mpw->wqe->mpw_inl.ctrl.data[3] = 0;
+       mpw->wqe->mpw_inl.eseg.mss = htons(length);
+       mpw->wqe->mpw_inl.eseg.inline_hdr_sz = 0;
+       mpw->wqe->mpw_inl.eseg.cs_flags = 0;
+       mpw->wqe->mpw_inl.eseg.rsvd0 = 0;
+       mpw->wqe->mpw_inl.eseg.rsvd1 = 0;
+       mpw->wqe->mpw_inl.eseg.rsvd2 = 0;
+       mpw->data.raw = &mpw->wqe->mpw_inl.data[0];
+}
+
+/**
+ * Close a MPW inline session.
+ *
+ * @param txq
+ *   Pointer to TX queue structure.
+ * @param mpw
+ *   Pointer to MPW session structure.
+ */
+static inline void
+mlx5_mpw_inline_close(struct txq *txq, struct mlx5_mpw *mpw)
+{
+       unsigned int size;
+
+       size = sizeof(*mpw->wqe) - MLX5_MWQE64_INL_DATA + mpw->total_len;
+       /*
+        * Store size in multiple of 16 bytes. Control and Ethernet segments
+        * count as 2.
+        */
+       mpw->wqe->mpw_inl.ctrl.data[1] =
+               htonl(txq->qp_num_8s | ((size + 15) / 16));
+       mpw->state = MLX5_MPW_STATE_CLOSED;
+       mpw->wqe->mpw_inl.byte_cnt = htonl(mpw->total_len | MLX5_INLINE_SEG);
+       txq->wqe_ci += (size + (sizeof(*mpw->wqe) - 1)) / sizeof(*mpw->wqe);
+}
+
+/**
+ * DPDK callback for TX with MPW inline support.
+ *
+ * @param dpdk_txq
+ *   Generic pointer to TX queue structure.
+ * @param[in] pkts
+ *   Packets to transmit.
+ * @param pkts_n
+ *   Number of packets in array.
+ *
+ * @return
+ *   Number of packets successfully transmitted (<= pkts_n).
+ */
+uint16_t
+mlx5_tx_burst_mpw_inline(void *dpdk_txq, struct rte_mbuf **pkts,
+                        uint16_t pkts_n)
+{
+       struct txq *txq = (struct txq *)dpdk_txq;
+       uint16_t elts_head = txq->elts_head;
+       const unsigned int elts_n = txq->elts_n;
+       unsigned int i;
+       unsigned int max;
+       unsigned int comp;
+       unsigned int inline_room = txq->max_inline;
+       struct mlx5_mpw mpw = {
+               .state = MLX5_MPW_STATE_CLOSED,
+       };
+
+       /* Prefetch first packet cacheline. */
+       tx_prefetch_cqe(txq, txq->cq_ci);
+       tx_prefetch_wqe(txq, txq->wqe_ci);
+       tx_prefetch_wqe(txq, txq->wqe_ci + 1);
+       /* Start processing. */
+       txq_complete(txq);
+       max = (elts_n - (elts_head - txq->elts_tail));
+       if (max > elts_n)
+               max -= elts_n;
+       assert(max >= 1);
+       assert(max <= elts_n);
+       /* Always leave one free entry in the ring. */
+       --max;
+       if (max == 0)
+               return 0;
+       if (max > pkts_n)
+               max = pkts_n;
+       for (i = 0; (i != max); ++i) {
+               struct rte_mbuf *buf = pkts[i];
+               unsigned int elts_head_next = (elts_head + 1) & (elts_n - 1);
+               uintptr_t addr;
+               uint32_t length;
+               uint32_t cs_flags = 0;
+
+               /* Should we enable HW CKSUM offload */
+               if (buf->ol_flags &
+                   (PKT_TX_IP_CKSUM | PKT_TX_TCP_CKSUM | PKT_TX_UDP_CKSUM))
+                       cs_flags = MLX5_ETH_WQE_L3_CSUM | MLX5_ETH_WQE_L4_CSUM;
+               /* Retrieve buffer information. */
+               addr = rte_pktmbuf_mtod(buf, uintptr_t);
+               length = DATA_LEN(buf);
+               /* Update element. */
+               (*txq->elts)[elts_head] = buf;
+               /* Start new session if packet differs. */
+               if (mpw.state == MLX5_MPW_STATE_OPENED) {
+                       if ((mpw.len != length) ||
+                           (mpw.wqe->mpw.eseg.cs_flags != cs_flags))
+                               mlx5_mpw_close(txq, &mpw);
+               } else if (mpw.state == MLX5_MPW_INL_STATE_OPENED) {
+                       if ((mpw.len != length) ||
+                           (length > inline_room) ||
+                           (mpw.wqe->mpw_inl.eseg.cs_flags != cs_flags)) {
+                               mlx5_mpw_inline_close(txq, &mpw);
+                               inline_room = txq->max_inline;
+                       }
+               }
+               if (mpw.state == MLX5_MPW_STATE_CLOSED) {
+                       if (length > inline_room) {
+                               mlx5_mpw_new(txq, &mpw, length);
+                               mpw.wqe->mpw.eseg.cs_flags = cs_flags;
+                       } else {
+                               mlx5_mpw_inline_new(txq, &mpw, length);
+                               mpw.wqe->mpw_inl.eseg.cs_flags = cs_flags;
+                       }
+               }
+               if (mpw.state == MLX5_MPW_STATE_OPENED) {
+                       volatile struct mlx5_wqe_data_seg *dseg;
+
+                       assert(inline_room == txq->max_inline);
+                       dseg = mpw.data.dseg[mpw.pkts_n];
+                       *dseg = (struct mlx5_wqe_data_seg){
+                               .byte_count = htonl(length),
+                               .lkey = txq_mp2mr(txq, txq_mb2mp(buf)),
+                               .addr = htonll(addr),
+                       };
+                       ++mpw.pkts_n;
+                       if (mpw.pkts_n == MLX5_MPW_DSEG_MAX)
+                               mlx5_mpw_close(txq, &mpw);
+               } else {
+                       unsigned int max;
+
+                       assert(mpw.state == MLX5_MPW_INL_STATE_OPENED);
+                       assert(length <= inline_room);
+                       /* Maximum number of bytes before wrapping. */
+                       max = ((uintptr_t)&(*txq->wqes)[txq->wqe_n] -
+                              (uintptr_t)mpw.data.raw);
+                       if (length > max) {
+                               rte_memcpy((void *)(uintptr_t)mpw.data.raw,
+                                          (void *)addr,
+                                          max);
+                               mpw.data.raw =
+                                       (volatile void *)&(*txq->wqes)[0];
+                               rte_memcpy((void *)(uintptr_t)mpw.data.raw,
+                                          (void *)(addr + max),
+                                          length - max);
+                               mpw.data.raw += length - max;
+                       } else {
+                               rte_memcpy((void *)(uintptr_t)mpw.data.raw,
+                                          (void *)addr,
+                                          length);
+                               mpw.data.raw += length;
+                       }
+                       if ((uintptr_t)mpw.data.raw ==
+                           (uintptr_t)&(*txq->wqes)[txq->wqe_n])
+                               mpw.data.raw =
+                                       (volatile void *)&(*txq->wqes)[0];
+                       ++mpw.pkts_n;
+                       if (mpw.pkts_n == MLX5_MPW_DSEG_MAX) {
+                               mlx5_mpw_inline_close(txq, &mpw);
+                               inline_room = txq->max_inline;
+                       } else {
+                               inline_room -= length;
+                       }
+               }
+               mpw.total_len += length;
+               elts_head = elts_head_next;
+#ifdef MLX5_PMD_SOFT_COUNTERS
+               /* Increment sent bytes counter. */
+               txq->stats.obytes += length;
+#endif
+       }
+       /* Take a shortcut if nothing must be sent. */
+       if (unlikely(i == 0))
+               return 0;
+       /* Check whether completion threshold has been reached. */
+       comp = txq->elts_comp + i;
+       if (comp >= MLX5_TX_COMP_THRESH) {
+               volatile union mlx5_wqe *wqe = mpw.wqe;
+
+               /* Request completion on last WQE. */
+               wqe->mpw_inl.ctrl.data[2] = htonl(8);
+               /* Save elts_head in unused "immediate" field of WQE. */
+               wqe->mpw_inl.ctrl.data[3] = elts_head;
+               txq->elts_comp = 0;
+       } else {
+               txq->elts_comp = comp;
+       }
+#ifdef MLX5_PMD_SOFT_COUNTERS
+       /* Increment sent packets counter. */
+       txq->stats.opackets += i;
+#endif
+       /* Ring QP doorbell. */
+       if (mpw.state == MLX5_MPW_INL_STATE_OPENED)
+               mlx5_mpw_inline_close(txq, &mpw);
+       else if (mpw.state == MLX5_MPW_STATE_OPENED)
+               mlx5_mpw_close(txq, &mpw);
+       mlx5_tx_dbrec(txq);
+       txq->elts_head = elts_head;
+       return i;
+}
+
 /**
  * Translate RX completion flags to packet type.
  *
index 3c83148..41605f9 100644 (file)
@@ -312,6 +312,8 @@ uint16_t mlx5_tx_burst_secondary_setup(void *, struct rte_mbuf **, uint16_t);
 
 uint16_t mlx5_tx_burst(void *, struct rte_mbuf **, uint16_t);
 uint16_t mlx5_tx_burst_inline(void *, struct rte_mbuf **, uint16_t);
+uint16_t mlx5_tx_burst_mpw(void *, struct rte_mbuf **, uint16_t);
+uint16_t mlx5_tx_burst_mpw_inline(void *, struct rte_mbuf **, uint16_t);
 uint16_t mlx5_rx_burst(void *, struct rte_mbuf **, uint16_t);
 uint16_t removed_tx_burst(void *, struct rte_mbuf **, uint16_t);
 uint16_t removed_rx_burst(void *, struct rte_mbuf **, uint16_t);
index 6a4a96e..4f17fb0 100644 (file)
@@ -398,7 +398,7 @@ txq_ctrl_setup(struct rte_eth_dev *dev, struct txq_ctrl *txq_ctrl,
                .obj = tmpl.qp,
                /* Enable multi-packet send if supported. */
                .family_flags =
-                       (priv->mps ?
+                       ((priv->mps && !priv->sriov) ?
                         IBV_EXP_QP_BURST_CREATE_ENABLE_MULTI_PACKET_SEND_WR :
                         0),
        };