Advertise RTE_ETH_TX_OFFLOAD_TCP_TSO for IPv4 and IPv6 TCP packets. For a packet with RTE_MBUF_F_TX_TCP_SEG and a non-zero tso_segsz, put the L2/L3/L4 headers alone in the first SGE and the payload in the following ones, pass tso_segsz as the client data unit, and request IP and TCP checksum offload with the pseudo-header checksum seeded. A first segment that also carries payload needs one extra SGE, so advertise nb_seg_max as one less than the maximum supported SGE count.
A packet that cannot be sent as TSO (invalid segment size, header size overflow, fragmented headers, or SGL build failure) is dropped. A packet with TCP_SEG but no payload or zero tso_segsz is sent unsegmented, with the TCP checksum offloaded as TCP_SEG implies. Signed-off-by: Tathagat Priyadarshi <[email protected]> --- v3: - Mask RTE_MBUF_F_TX_TCP_SEG in the non-TSO fallback branch so rte_ipv4_phdr_cksum / rte_ipv6_phdr_cksum calculate the full L4 length. - Reject invalid TSO packets (tso_segsz > MANA_TSO_MAX_SEGSZ or gso_hs > UINT8_MAX) to prevent hardware bitfield truncations. - Set dev_info nb_seg_max to max_sge - 1 to reflect the extra SGE for TSO. - Bound TSO SGL construction loop by expected_sge and verify SGE count. - Streamline commit log. v2: - Drop unsendable packets instead of break. - Honour TCP checksum offload when TCP_SEG packet is unsegmented. - Move GDMA_WR_OOB_IN_SGL and GDMA_WR_PAD_BY_SGE0 adjacent to struct gdma_work_request. - Add release notes entry and update mana.ini feature matrix. doc/guides/nics/features/mana.ini | 1 + doc/guides/rel_notes/release_26_11.rst | 4 + drivers/net/mana/gdma.c | 5 + drivers/net/mana/mana.c | 9 +- drivers/net/mana/mana.h | 9 +- drivers/net/mana/tx.c | 194 ++++++++++++++++++++----- 6 files changed, 184 insertions(+), 38 deletions(-) diff --git a/doc/guides/nics/features/mana.ini b/doc/guides/nics/features/mana.ini index c7c8215aab..6a4fb0a89e 100644 --- a/doc/guides/nics/features/mana.ini +++ b/doc/guides/nics/features/mana.ini @@ -10,6 +10,7 @@ Rx interrupt = Y RSS hash = Y L3 checksum offload = Y L4 checksum offload = Y +TSO = Y MTU update = Y Multiprocess aware = Y Linux = Y diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst index be758a0c02..7e5f463f88 100644 --- a/doc/guides/rel_notes/release_26_11.rst +++ b/doc/guides/rel_notes/release_26_11.rst @@ -164,6 +164,10 @@ New Features Added ``fdir_buffer_size`` devarg to select the Flow Director table size (``64k``, ``128k`` or ``256k``) at probe time. The default remains ``64k``. +* **Updated Microsoft MANA PMD.** + + * Added hardware TCP Segmentation Offload (TSO) support for IPv4 and IPv6. + * **Updated NVIDIA mlx5 ethernet driver.** * Increased the maximum number of secondary unicast MAC addresses from 128 to up to 4096 diff --git a/drivers/net/mana/gdma.c b/drivers/net/mana/gdma.c index a92aef59d2..65896b6a69 100644 --- a/drivers/net/mana/gdma.c +++ b/drivers/net/mana/gdma.c @@ -38,6 +38,11 @@ write_dma_client_oob(uint8_t *work_queue_buffer_pointer, header->inline_client_oob_size_in_dwords = client_oob_size / sizeof(uint32_t); header->client_data_unit = work_request->client_data_unit; + if (work_request->flags & GDMA_WR_OOB_IN_SGL) { + header->client_oob_in_sgl = 1; + if (work_request->flags & GDMA_WR_PAD_BY_SGE0) + header->last_v_bytes = work_request->sgl[0].size; + } DP_LOG(DEBUG, "queue buf %p sgl %u oob_h %u du %u oob_buf %p oob_b %u", work_queue_buffer_pointer, header->num_sgl_entries, diff --git a/drivers/net/mana/mana.c b/drivers/net/mana/mana.c index 5ddbbdc3d0..d6a8b2dd04 100644 --- a/drivers/net/mana/mana.c +++ b/drivers/net/mana/mana.c @@ -349,6 +349,7 @@ mana_dev_info_get(struct rte_eth_dev *dev, struct rte_eth_dev_info *dev_info) { struct mana_priv *priv = dev->data->dev_private; + uint16_t max_sge; dev_info->min_mtu = RTE_ETHER_MIN_MTU; dev_info->max_mtu = MANA_MAX_MTU; @@ -406,13 +407,13 @@ mana_dev_info_get(struct rte_eth_dev *dev, dev_info->rx_desc_lim.nb_mtu_seg_max = RTE_MIN(priv->max_recv_sge, UINT16_MAX); + max_sge = RTE_MIN(priv->max_send_sge, MAX_SGL_ENTRIES_FOR_TRANSMIT); + dev_info->tx_desc_lim.nb_min = MIN_BUFFERS_PER_QUEUE; dev_info->tx_desc_lim.nb_max = RTE_MIN(priv->max_tx_desc, UINT16_MAX); dev_info->tx_desc_lim.nb_align = MIN_BUFFERS_PER_QUEUE; - dev_info->tx_desc_lim.nb_seg_max = - RTE_MIN(priv->max_send_sge, MAX_SGL_ENTRIES_FOR_TRANSMIT); - dev_info->tx_desc_lim.nb_mtu_seg_max = - RTE_MIN(priv->max_send_sge, MAX_SGL_ENTRIES_FOR_TRANSMIT); + dev_info->tx_desc_lim.nb_seg_max = max_sge > 1 ? max_sge - 1 : 1; + dev_info->tx_desc_lim.nb_mtu_seg_max = max_sge; /* Speed */ dev_info->speed_capa = RTE_ETH_LINK_SPEED_100G; diff --git a/drivers/net/mana/mana.h b/drivers/net/mana/mana.h index 79879b7626..a259a12f66 100644 --- a/drivers/net/mana/mana.h +++ b/drivers/net/mana/mana.h @@ -33,7 +33,8 @@ struct mana_shared_data { RTE_ETH_TX_OFFLOAD_MULTI_SEGS | \ RTE_ETH_TX_OFFLOAD_IPV4_CKSUM | \ RTE_ETH_TX_OFFLOAD_TCP_CKSUM | \ - RTE_ETH_TX_OFFLOAD_UDP_CKSUM) + RTE_ETH_TX_OFFLOAD_UDP_CKSUM | \ + RTE_ETH_TX_OFFLOAD_TCP_TSO) #define INDIRECTION_TABLE_NUM_ELEMENTS 64 #define TOEPLITZ_HASH_KEY_SIZE_IN_BYTES 40 @@ -208,6 +209,12 @@ struct one_sgl { struct gdma_sgl_element gdma_sgl[MAX_SGL_ENTRIES_FOR_TRANSMIT]; }; +#define GDMA_WR_OOB_IN_SGL (1 << 0) +#define GDMA_WR_PAD_BY_SGE0 (1 << 1) + +/* client_data_unit is 14 bits in gdma_wqe_dma_oob */ +#define MANA_TSO_MAX_SEGSZ ((1u << 14) - 1) + struct gdma_work_request { struct gdma_header gdma_header; struct gdma_sgl_element *sgl; diff --git a/drivers/net/mana/tx.c b/drivers/net/mana/tx.c index 16e63e89c9..19a3aaec59 100644 --- a/drivers/net/mana/tx.c +++ b/drivers/net/mana/tx.c @@ -274,15 +274,43 @@ mana_tx_burst(void *dpdk_txq, struct rte_mbuf **tx_pkts, uint16_t nb_pkts) struct transmit_oob_v2 tx_oob; struct one_sgl sgl; uint16_t seg_idx; + uint16_t num_sge; + uint16_t expected_sge; + bool is_tso = false; + uint32_t gso_hs = 0; if (txq->desc_ring_len >= txq->num_desc) break; - /* Drop the packet if it exceeds max segments */ - if (unlikely(m_pkt->nb_segs > priv->max_send_sge || - m_pkt->nb_segs > MAX_SGL_ENTRIES_FOR_TRANSMIT)) { + if (m_pkt->ol_flags & RTE_MBUF_F_TX_TCP_SEG) { + gso_hs = m_pkt->l2_len + m_pkt->l3_len + m_pkt->l4_len; + /* Header must be in first segment; last_v_bytes is 8 bits */ + if (unlikely(m_pkt->data_len < gso_hs || + gso_hs > UINT8_MAX)) { + DP_LOG(ERR, "invalid TSO packet header length %u", + gso_hs); + goto drop; + } + if (unlikely(m_pkt->tso_segsz > MANA_TSO_MAX_SEGSZ)) { + DP_LOG(ERR, "TSO segment size %u exceeds max %u", + m_pkt->tso_segsz, MANA_TSO_MAX_SEGSZ); + goto drop; + } + if (likely(gso_hs != 0 && m_pkt->pkt_len > gso_hs && + m_pkt->tso_segsz != 0)) + is_tso = true; + } + + /* Drop the packet if it exceeds max segments. A TSO packet + * with payload in the first segment needs an extra SGE for + * the header. + */ + expected_sge = m_pkt->nb_segs + + (is_tso && m_pkt->data_len > gso_hs ? 1 : 0); + if (unlikely(expected_sge > priv->max_send_sge || + expected_sge > MAX_SGL_ENTRIES_FOR_TRANSMIT)) { DP_LOG(ERR, "send packet segments %d exceeding max", - m_pkt->nb_segs); + expected_sge); goto drop; } @@ -307,9 +335,47 @@ mana_tx_burst(void *dpdk_txq, struct rte_mbuf **tx_pkts, uint16_t nb_pkts) tx_oob.short_oob.tx_compute_IP_header_checksum = m_pkt->ol_flags & RTE_MBUF_F_TX_IP_CKSUM ? 1 : 0; - if ((m_pkt->ol_flags & RTE_MBUF_F_TX_L4_MASK) == - RTE_MBUF_F_TX_TCP_CKSUM) { + if (is_tso) { + struct rte_tcp_hdr *tcp_hdr = + rte_pktmbuf_mtod_offset(m_pkt, + struct rte_tcp_hdr *, + m_pkt->l2_len + m_pkt->l3_len); + + if (m_pkt->ol_flags & RTE_MBUF_F_TX_IPV4) { + struct rte_ipv4_hdr *ip_hdr = + rte_pktmbuf_mtod_offset(m_pkt, + struct rte_ipv4_hdr *, + m_pkt->l2_len); + + ip_hdr->total_length = 0; + ip_hdr->hdr_checksum = 0; + tcp_hdr->cksum = rte_ipv4_phdr_cksum(ip_hdr, + m_pkt->ol_flags); + } else if (m_pkt->ol_flags & RTE_MBUF_F_TX_IPV6) { + struct rte_ipv6_hdr *ip_hdr = + rte_pktmbuf_mtod_offset(m_pkt, + struct rte_ipv6_hdr *, + m_pkt->l2_len); + + ip_hdr->payload_len = 0; + tcp_hdr->cksum = rte_ipv6_phdr_cksum(ip_hdr, + m_pkt->ol_flags); + } else { + DP_LOG(ERR, "Invalid IP version for TSO"); + goto drop; + } + + tx_oob.short_oob.tx_compute_IP_header_checksum = + (m_pkt->ol_flags & RTE_MBUF_F_TX_IPV4) ? 1 : 0; + tx_oob.short_oob.tx_compute_TCP_checksum = 1; + tx_oob.short_oob.tx_compute_UDP_checksum = 0; + tx_oob.short_oob.tx_transport_header_offset = + m_pkt->l2_len + m_pkt->l3_len; + } else if ((m_pkt->ol_flags & RTE_MBUF_F_TX_L4_MASK) == + RTE_MBUF_F_TX_TCP_CKSUM || + (m_pkt->ol_flags & RTE_MBUF_F_TX_TCP_SEG)) { struct rte_tcp_hdr *tcp_hdr; + uint64_t ol = m_pkt->ol_flags & ~RTE_MBUF_F_TX_TCP_SEG; /* HW needs partial TCP checksum */ @@ -323,8 +389,7 @@ mana_tx_burst(void *dpdk_txq, struct rte_mbuf **tx_pkts, uint16_t nb_pkts) ip_hdr = rte_pktmbuf_mtod_offset(m_pkt, struct rte_ipv4_hdr *, m_pkt->l2_len); - tcp_hdr->cksum = rte_ipv4_phdr_cksum(ip_hdr, - m_pkt->ol_flags); + tcp_hdr->cksum = rte_ipv4_phdr_cksum(ip_hdr, ol); } else if (m_pkt->ol_flags & RTE_MBUF_F_TX_IPV6) { struct rte_ipv6_hdr *ip_hdr; @@ -332,8 +397,7 @@ mana_tx_burst(void *dpdk_txq, struct rte_mbuf **tx_pkts, uint16_t nb_pkts) ip_hdr = rte_pktmbuf_mtod_offset(m_pkt, struct rte_ipv6_hdr *, m_pkt->l2_len); - tcp_hdr->cksum = rte_ipv6_phdr_cksum(ip_hdr, - m_pkt->ol_flags); + tcp_hdr->cksum = rte_ipv6_phdr_cksum(ip_hdr, ol); } else { DP_LOG(ERR, "Invalid input for TCP CKSUM"); } @@ -407,34 +471,92 @@ mana_tx_burst(void *dpdk_txq, struct rte_mbuf **tx_pkts, uint16_t nb_pkts) m_pkt->pkt_len); /* Create SGL for packet data buffers */ - for (seg_idx = 0; seg_idx < m_pkt->nb_segs; seg_idx++) { + if (is_tso) { struct mana_mr_cache *mr = - mana_alloc_pmd_mr(&txq->mr_btree, priv, m_seg); + mana_alloc_pmd_mr(&txq->mr_btree, priv, m_pkt); - if (!mr) { - DP_LOG(ERR, "failed to get MR, pkt_idx %u", + if (unlikely(!mr)) { + DP_LOG(ERR, "failed to get MR for TSO hdr, pkt_idx %u", pkt_idx); - break; + goto drop; } - sgl.gdma_sgl[seg_idx].address = - rte_cpu_to_le_64(rte_pktmbuf_mtod(m_seg, - uint64_t)); - sgl.gdma_sgl[seg_idx].size = m_seg->data_len; - sgl.gdma_sgl[seg_idx].memory_key = mr->lkey; + /* SGE0: packet header only (L2 + L3 + L4) */ + sgl.gdma_sgl[0].address = + rte_cpu_to_le_64(rte_pktmbuf_mtod(m_pkt, uint64_t)); + sgl.gdma_sgl[0].size = gso_hs; + sgl.gdma_sgl[0].memory_key = mr->lkey; + num_sge = 1; + + /* If first mbuf has payload data beyond the header */ + if (m_pkt->data_len > gso_hs) { + sgl.gdma_sgl[1].address = + rte_cpu_to_le_64(rte_pktmbuf_mtod(m_pkt, + uint64_t) + + gso_hs); + sgl.gdma_sgl[1].size = m_pkt->data_len - gso_hs; + sgl.gdma_sgl[1].memory_key = mr->lkey; + num_sge = 2; + } - DP_LOG(DEBUG, - "seg idx %u addr 0x%" PRIx64 " size %x key %x", - seg_idx, sgl.gdma_sgl[seg_idx].address, - sgl.gdma_sgl[seg_idx].size, - sgl.gdma_sgl[seg_idx].memory_key); + /* Subsequent mbuf segments */ + m_seg = m_pkt->next; + while (m_seg && num_sge < expected_sge) { + mr = mana_alloc_pmd_mr(&txq->mr_btree, priv, + m_seg); + if (unlikely(!mr)) { + DP_LOG(ERR, + "failed to get MR for TSO seg, pkt_idx %u", + pkt_idx); + goto drop; + } + sgl.gdma_sgl[num_sge].address = + rte_cpu_to_le_64(rte_pktmbuf_mtod(m_seg, + uint64_t)); + sgl.gdma_sgl[num_sge].size = m_seg->data_len; + sgl.gdma_sgl[num_sge].memory_key = mr->lkey; + num_sge++; + m_seg = m_seg->next; + } - m_seg = m_seg->next; - } + if (unlikely(m_seg != NULL || num_sge != expected_sge)) { + DP_LOG(ERR, "failed to build TSO SGL, num_sge %u", + num_sge); + goto drop; + } + } else { + for (seg_idx = 0; seg_idx < m_pkt->nb_segs; seg_idx++) { + struct mana_mr_cache *mr = + mana_alloc_pmd_mr(&txq->mr_btree, priv, + m_seg); + + if (!mr) { + DP_LOG(ERR, "failed to get MR, pkt_idx %u", + pkt_idx); + break; + } + + sgl.gdma_sgl[seg_idx].address = + rte_cpu_to_le_64(rte_pktmbuf_mtod(m_seg, + uint64_t)); + sgl.gdma_sgl[seg_idx].size = m_seg->data_len; + sgl.gdma_sgl[seg_idx].memory_key = mr->lkey; + + DP_LOG(DEBUG, + "seg idx %u addr 0x%" PRIx64 " size %x key %x", + seg_idx, sgl.gdma_sgl[seg_idx].address, + sgl.gdma_sgl[seg_idx].size, + sgl.gdma_sgl[seg_idx].memory_key); + + m_seg = m_seg->next; + } - /* Skip this packet if we can't populate all segments */ - if (seg_idx != m_pkt->nb_segs) - goto drop; + /* Skip this packet if we can't populate all segments */ + if (seg_idx != m_pkt->nb_segs) + goto drop; + + num_sge = m_pkt->nb_segs; + } /* If we can at least queue post two WQEs and there are at * least two packets to send, use TX CQE suppression for the @@ -452,7 +574,7 @@ mana_tx_burst(void *dpdk_txq, struct rte_mbuf **tx_pkts, uint16_t nb_pkts) work_req.gdma_header.struct_size = sizeof(work_req); work_req.sgl = sgl.gdma_sgl; - work_req.num_sgl_elements = m_pkt->nb_segs; + work_req.num_sgl_elements = num_sge; if (tx_oob.short_oob.packet_format == SHORT_PACKET_FORMAT) work_req.inline_oob_size_in_bytes = sizeof(struct transmit_short_oob_v2); @@ -460,8 +582,14 @@ mana_tx_burst(void *dpdk_txq, struct rte_mbuf **tx_pkts, uint16_t nb_pkts) work_req.inline_oob_size_in_bytes = sizeof(struct transmit_oob_v2); work_req.inline_oob_data = &tx_oob; - work_req.flags = 0; - work_req.client_data_unit = NOT_USING_CLIENT_DATA_UNIT; + if (is_tso) { + work_req.flags = GDMA_WR_OOB_IN_SGL | + GDMA_WR_PAD_BY_SGE0; + work_req.client_data_unit = m_pkt->tso_segsz; + } else { + work_req.flags = 0; + work_req.client_data_unit = NOT_USING_CLIENT_DATA_UNIT; + } ret = gdma_post_work_request(&txq->gdma_sq, &work_req, &wqe_size_in_bu); -- 2.43.0

