* [PATCH net 1/2] bnxt_en: Add helper to fill SW USO payload BDs
2026-10-08 19:11 [PATCH net 0/2] bnxt_en: Fix SW USO padding Joe Damato
@ 2026-10-08 19:11 ` Joe Damato
2026-10-08 19:11 ` [PATCH net 2/2] bnxt_en: Pad short SW USO segments to BNXT_MIN_PKT_SIZE Joe Damato
1 sibling, 0 replies; 4+ messages in thread
From: Joe Damato @ 2026-10-08 19:11 UTC (permalink / raw)
To: netdev, Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: edumazet, horms, linux-kernel, Joe Damato, stable
Move the code that fills a payload BD and its software ring entry in
bnxt_sw_udp_gso_xmit() into a helper, bnxt_sw_gso_data_bd().
No functional change. A following patch uses the helper to add a pad BD
to short SW USO segments.
Cc: stable@vger.kernel.org
Signed-off-by: Joe Damato <joe@dama.to>
---
drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c | 33 ++++++++++++-------
1 file changed, 22 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c
index 6c1060fa2ea5..ef04c9d08066 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
@@ -181,15 +201,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,10 +219,6 @@ 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;
}
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH net 2/2] bnxt_en: Pad short SW USO segments to BNXT_MIN_PKT_SIZE
2026-10-08 19:11 [PATCH net 0/2] bnxt_en: Fix SW USO padding Joe Damato
2026-10-08 19:11 ` [PATCH net 1/2] bnxt_en: Add helper to fill SW USO payload BDs Joe Damato
@ 2026-10-08 19:11 ` Joe Damato
2026-10-09 5:26 ` Michael Chan
1 sibling, 1 reply; 4+ messages in thread
From: Joe Damato @ 2026-10-08 19:11 UTC (permalink / raw)
To: netdev, Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Joe Damato
Cc: edumazet, horms, linux-kernel, stable
Each SW USO segment is hdr_len + seg_payload bytes. With IPv4, hdr_len
is 42 bytes, so a segment carrying less than 10 bytes of UDP payload is
sent shorter than BNXT_MIN_PKT_SIZE. This happens with a short last
segment (e.g. gso_size 1400 and 1405 bytes of payload) or with a
gso_size below 10 bytes.
Fix the short last segment in bnxt_sw_udp_gso_xmit(): zero the pad
bytes after the header in the segment's inline header slot and add a
pad BD pointing at them after the payload BDs. Only the last segment
can need padding, and its pad BD fits within the existing bound in
bds_needed and BNXT_SW_USO_MAX_DESCS, since payload BDs are at most
num_segs + nr_frags.
If gso_size itself is too small, every segment would need a pad BD,
exceeding the descriptor bound. This case is unlikely, so
bnxt_features_check() clears GSO features for these skbs and the stack
segments them instead; bnxt_start_xmit() then pads each segment.
Fixes: cc5d90667db8 ("net: bnxt: Implement software USO")
Cc: stable@vger.kernel.org
Signed-off-by: Joe Damato <joe@dama.to>
---
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 12 ++++++++++
drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c | 23 +++++++++++++++++--
drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h | 15 ++++++++++++
3 files changed, 48 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index 15a8349ccf7b..d766dd469797 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 ef04c9d08066..f87e02ef3033 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c
@@ -82,11 +82,14 @@ int bnxt_sw_udp_gso_xmit(struct bnxt *bp, struct bnxt_tx_ring_info *txr,
if (unlikely(num_segs <= 1))
goto drop;
+ 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;
@@ -133,12 +136,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;
@@ -156,10 +161,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)];
@@ -222,6 +235,12 @@ int bnxt_sw_udp_gso_xmit(struct bnxt *bp, struct bnxt_tx_ring_info *txr,
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 -
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 4+ messages in thread