summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaolo Abeni <pabeni@redhat.com>2026-10-15 16:10:33 +0200
committerPaolo Abeni <pabeni@redhat.com>2026-10-15 16:10:33 +0200
commit36e6e826f7cd49f94e76fbfdc6f2873385c456d6 (patch)
tree198579d77dc7bd07b1b7eee7010d7e09aeed43d4
parent73022f605c447f31610b94ed4a983d2b4bf2c721 (diff)
parentb0f7f9c87817da1c72d093d3e5f249d45d8edf88 (diff)
Merge branch 'bnxt_en-fix-sw-uso-padding'
Joe Damato says: ==================== bnxt_en: Fix SW USO padding As a followup to Eric's fix a51d233ccd48 ("bnxt_en: fix DMA mapping length for padded small packets") which fixes padding for normal TX, this series fixes padding for SW USO. There are two cases I try to address: 1. The last segment could be shorter than BNXT_MIN_PKT_SIZE. So, add an extra BD if needed to pad to that minimum. 2. The gso_size is so small that every segment would be shorter than BNXT_MIN_PKT_SIZE (under 10 bytes with IPv4). This seems exceedingly unlikely, but just in case, it is handled by clearing the GSO features for these skbs so the stack segments them. Patch 1 is a small refactor to move code into a helper that patch 2 uses to avoid duplicating code. I ran v1 on a bnxt machine and re-ran the uso.py test and it passed, but the existing uso.py test doesn't test this specific case, so running the test was more of a regression test. There were no functional changes to v2. Note that I did not see this issue in production and it should not be possible for QUIC to hit this case on either ipv4 or ipv6 because QUIC packets are at least 21 bytes. AFAIU, only ipv4 segments with less than 10 bytes of UDP payload can trigger this. As such, I was asked to send this to net-next. v1: https://lore.kernel.org/netdev/20261008191114.3736826-1-joe@dama.to/ ==================== Link: https://patch.msgid.link/20261009215426.2377946-1-joe@dama.to Signed-off-by: Paolo Abeni <pabeni@redhat.com>
-rw-r--r--drivers/net/ethernet/broadcom/bnxt/bnxt.c12
-rw-r--r--drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c61
-rw-r--r--drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h15
3 files changed, 75 insertions, 13 deletions
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index c1d3fcc7ae84..0682060b048c 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -14268,6 +14268,18 @@ static netdev_features_t bnxt_features_check(struct sk_buff *skb,
u8 *l4_proto;
features = vlan_features_check(skb, features);
+
+ /* SW USO pads at most the last segment; let the stack segment skbs
+ * where every segment would be shorter than BNXT_MIN_PKT_SIZE.
+ */
+ if (skb_is_gso(skb) &&
+ (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4) &&
+ !(bp->flags & BNXT_FLAG_UDP_GSO_CAP) &&
+ bnxt_sw_gso_pad_len(skb_transport_offset(skb) +
+ sizeof(struct udphdr),
+ skb_shinfo(skb)->gso_size))
+ features &= ~NETIF_F_GSO_MASK;
+
switch (vlan_get_protocol(skb)) {
case htons(ETH_P_IP):
if (!skb->encapsulation)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c
index 6c1060fa2ea5..80d5c4f963a9 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c
@@ -31,6 +31,26 @@ static u32 bnxt_sw_gso_lhint(unsigned int len)
return TX_BD_FLAGS_LHINT_2048_AND_LARGER;
}
+static struct tx_bd *bnxt_sw_gso_data_bd(struct bnxt *bp,
+ struct bnxt_tx_ring_info *txr,
+ u16 prod, dma_addr_t addr,
+ unsigned int len)
+{
+ struct tx_bd *txbd = &txr->tx_desc_ring[TX_RING(bp, prod)][TX_IDX(prod)];
+ struct bnxt_sw_tx_bd *tx_buf = &txr->tx_buf_ring[RING_TX(bp, prod)];
+
+ txbd->tx_bd_haddr = cpu_to_le64(addr);
+ txbd->tx_bd_len_flags_type = cpu_to_le32(len << TX_BD_LEN_SHIFT);
+ txbd->tx_bd_opaque = 0;
+
+ dma_unmap_addr_set(tx_buf, mapping, addr);
+ dma_unmap_len_set(tx_buf, len, 0);
+ tx_buf->skb = NULL;
+ tx_buf->is_sw_gso = 0;
+
+ return txbd;
+}
+
/* Transmit an skb requiring software UDP segmentation.
*
* Returns 1 if the skb was queued and new BDs were produced, 0 if the skb
@@ -62,11 +82,19 @@ int bnxt_sw_udp_gso_xmit(struct bnxt *bp, struct bnxt_tx_ring_info *txr,
if (unlikely(num_segs <= 1))
goto drop;
+ /* bnxt_features_check() makes the stack segment these, but paths
+ * that skip validate_xmit_skb() (e.g. generic XDP_TX of a GRO'd
+ * skb) can still get here. Padding every segment would need more
+ * BDs than bds_needed reserves below, so drop instead.
+ */
+ if (unlikely(bnxt_sw_gso_pad_len(hdr_len, mss)))
+ goto drop;
+
/* Upper bound on the number of descriptors needed.
*
* Each segment uses 1 long BD + 1 ext BD + payload BDs, which is
* at most num_segs + nr_frags (each frag boundary crossing adds at
- * most 1 extra BD).
+ * most 1 extra BD). The last segment may need 1 pad BD.
*/
bds_needed = 3 * num_segs + skb_shinfo(skb)->nr_frags + 1;
@@ -113,12 +141,14 @@ int bnxt_sw_udp_gso_xmit(struct bnxt *bp, struct bnxt_tx_ring_info *txr,
dma_addr_t dma_addr;
struct tx_bd *txbd;
struct udphdr *uh;
+ unsigned int pad;
void *this_hdr;
int bd_count;
bool last;
u32 flags;
last = (i == num_segs - 1);
+ pad = bnxt_sw_gso_pad_len(hdr_len, seg_payload);
offset = slot * TSO_HEADER_SIZE;
this_hdr = txr->tx_inline_buf + offset;
this_hdr_dma = txr->tx_inline_dma + offset;
@@ -136,10 +166,18 @@ int bnxt_sw_udp_gso_xmit(struct bnxt *bp, struct bnxt_tx_ring_info *txr,
iph->check = 0;
}
+ /* Pad bytes follow the header in the inline slot. Zero them
+ * so stale header bytes from an earlier packet are not sent.
+ */
+ if (pad)
+ memset(this_hdr + hdr_len, 0, pad);
+
dma_sync_single_for_device(&pdev->dev, this_hdr_dma,
- hdr_len, DMA_TO_DEVICE);
+ hdr_len + pad, DMA_TO_DEVICE);
bd_count = tso_dma_map_count(&map, seg_payload);
+ if (pad)
+ bd_count++;
tx_buf = &txr->tx_buf_ring[RING_TX(bp, prod)];
txbd = &txr->tx_desc_ring[TX_RING(bp, prod)][TX_IDX(prod)];
@@ -181,15 +219,10 @@ int bnxt_sw_udp_gso_xmit(struct bnxt *bp, struct bnxt_tx_ring_info *txr,
while (tso_dma_map_next(&map, &dma_addr, &chunk_len,
&mapping_len, seg_payload)) {
prod = NEXT_TX(prod);
- txbd = &txr->tx_desc_ring[TX_RING(bp, prod)][TX_IDX(prod)];
+ txbd = bnxt_sw_gso_data_bd(bp, txr, prod, dma_addr,
+ chunk_len);
tx_buf = &txr->tx_buf_ring[RING_TX(bp, prod)];
- txbd->tx_bd_haddr = cpu_to_le64(dma_addr);
- dma_unmap_addr_set(tx_buf, mapping, dma_addr);
- dma_unmap_len_set(tx_buf, len, 0);
- tx_buf->skb = NULL;
- tx_buf->is_sw_gso = 0;
-
if (mapping_len) {
if (last_unmap_buf) {
dma_unmap_addr_set(last_unmap_buf,
@@ -204,13 +237,15 @@ int bnxt_sw_udp_gso_xmit(struct bnxt *bp, struct bnxt_tx_ring_info *txr,
}
last_unmap_buf = tx_buf;
- flags = chunk_len << TX_BD_LEN_SHIFT;
- txbd->tx_bd_len_flags_type = cpu_to_le32(flags);
- txbd->tx_bd_opaque = 0;
-
seg_payload -= chunk_len;
}
+ if (pad) {
+ prod = NEXT_TX(prod);
+ txbd = bnxt_sw_gso_data_bd(bp, txr, prod,
+ this_hdr_dma + hdr_len, pad);
+ }
+
txbd->tx_bd_len_flags_type |=
cpu_to_le32(TX_BD_FLAGS_PACKET_END);
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h
index 77d9af97cc22..5916ee08c0e6 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h
@@ -19,10 +19,25 @@
* Each segment: 1 long BD + 1 ext BD + payload BDs.
* Total payload BDs across all segs <= num_segs + nr_frags (each frag
* boundary crossing adds at most 1 extra BD).
+ * The last segment may need 1 pad BD (see bnxt_sw_gso_pad_len()).
* So: 3 * max_segs + MAX_SKB_FRAGS + 1 = 3 * 64 + 17 + 1 = 210.
*/
#define BNXT_SW_USO_MAX_DESCS (3 * BNXT_SW_USO_MAX_SEGS + MAX_SKB_FRAGS + 1)
+/* Bytes of padding needed to bring a segment carrying @seg_payload bytes
+ * up to BNXT_MIN_PKT_SIZE, or 0 if no padding is needed.
+ */
+static inline unsigned int bnxt_sw_gso_pad_len(unsigned int hdr_len,
+ unsigned int seg_payload)
+{
+ unsigned int len = hdr_len + seg_payload;
+
+ if (len < BNXT_MIN_PKT_SIZE)
+ return BNXT_MIN_PKT_SIZE - len;
+ else
+ return 0;
+}
+
static inline u16 bnxt_inline_avail(struct bnxt_tx_ring_info *txr)
{
return BNXT_SW_USO_MAX_SEGS -