[PATCH net-next v5 6/8] net: bcmgenet: pad transmit frames out of the packet ready window
flat view
WARM2d
REVIEWED: 1 (0M)
From: Nicolai Buchwitz <nb@tipi-net.de>
Date: 2026-10-13 14:20:48
Also in:
lkml
Subsystem:
broadcom genet ethernet driver, networking drivers, the rest · Maintainers:
Doug Berger, Florian Fainelli, Nicolai Buchwitz, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Revision v5 of 5 in this series; 1 review trailer.
Revisions (5)
A frame that ends a few bytes past the transmit packet ready threshold stops the transmitter as soon as a shorter frame follows. Tx DMA then refuses to halt, so every later bcmgenet_init_dma() fails and the interface cannot be opened again. IP fragmentation generates that pattern on every datagram, full frames and a short tail. The window starts one byte past the threshold and widens with it. On a v5 it ends 28, 32 and 46 bytes past thresholds of 2560, 3584 and 3840. Link speed makes no difference. Pad frames landing in it to 64 bytes past the threshold, rounded up from the widest window seen. The threshold goes by skb->len, it does not count the 64 byte status block the driver prepends later, measured at 0x80 and 0xf0. Padding must not push a frame past what the peer accepts. Linux does not bound how many VLAN tags a frame carries, so measure against the longest frame the MAC has to accept rather than a tag count. For the MTUs where such a frame would land in the window, lower the threshold instead. All other MTUs keep the register maximum, and at or below ETH_DATA_LEN the reset value stays, so an unchanged MTU keeps the threshold it always had. Nothing reaches either path until a later patch raises dev->max_mtu. The MAC inserts a checksum only into a frame it holds in full. A frame longer than the threshold gets its checksum computed in software. ether_setup() leaves IFF_TX_SKB_SHARING set, so unshare before either path writes to the frame. The padding is appended to the frame, so a protocol that locates data from the end of it, such as a DSA tail tag or a PRP trailer, sees the zeros instead. Nothing below an MTU of 3809 is affected, since no frame reaches the window there. Above it the alternatives are dropping the frame or leaving the transmitter stalled. Tested-by: Pierre-Marin Leclercq <redacted> Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de> --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 66 ++++++++++++++++++++++++++ drivers/net/ethernet/broadcom/genet/bcmgenet.h | 1 + 2 files changed, 67 insertions(+)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 9b9ce33f081a..e00661b2414b 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c@@ -59,6 +59,11 @@ #define ENET_THLD_DEFAULT 0x80 #define ENET_THLD_MAX 0xf0 +/* A frame ending just past the transmit threshold stops the transmitter once + * a shorter frame follows, so pad frames that land there this far past it. + */ +#define ENET_TX_SAFE_MARGIN 64 + /* Page pool RX buffer layout: * RSB(64) + pad(2) | frame data | skb_shared_info * The HW writes the 64B RSB before every descriptor of a frame. Only the
@@ -2176,6 +2181,42 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev) goto out; } + /* Both paths below write to the frame, which ether_setup() lets the + * stack share. Match their conditions so nothing else is copied. + */ + if (unlikely(skb->len > priv->tx_thld_len && + (skb->ip_summed == CHECKSUM_PARTIAL || + skb->len < priv->tx_thld_len + ENET_TX_SAFE_MARGIN))) { + skb = skb_share_check(skb, GFP_ATOMIC); + if (!skb) { + BCMGENET_STATS64_INC((&ring->stats64), dropped); + goto drop; + } + } + + /* The MAC only inserts a checksum into a frame it holds in full, and + * silently drops a longer one, so fall back to software. + */ + if (unlikely(skb->len > priv->tx_thld_len) && + skb->ip_summed == CHECKSUM_PARTIAL) { + if (skb_checksum_help(skb)) { + BCMGENET_STATS64_INC((&ring->stats64), dropped); + dev_kfree_skb_any(skb); + goto drop; + } + } + + /* Keep the frame out of the window just past the threshold */ + if (unlikely(skb->len > priv->tx_thld_len && + skb->len < priv->tx_thld_len + ENET_TX_SAFE_MARGIN)) { + if (skb_put_padto(skb, priv->tx_thld_len + ENET_TX_SAFE_MARGIN)) { + BCMGENET_STATS64_INC((&ring->stats64), dropped); + goto drop; + } + } + + nr_frags = skb_shinfo(skb)->nr_frags; + /* Retain how many bytes will be sent on the wire, without TSB inserted * by transmit checksum offload */
@@ -2662,6 +2703,27 @@ static unsigned int bcmgenet_pkt_rdy_thld(unsigned int mtu) ENET_THLD_MAX_LEN / ENET_THLD_UNIT); } +/* Transmit threshold in register units. Frames landing in the window just + * past it are padded clear of it, so pick a threshold that leaves room for + * that padding inside the frame the MTU allows. Size the window against the + * longest frame the MAC has to accept, since the tag count is not bounded. + */ +static unsigned int bcmgenet_tx_pkt_rdy_thld(unsigned int mtu) +{ + unsigned int thld = ENET_THLD_MAX; + + /* Keep the reset value while the MTU has no use for more */ + if (mtu <= ETH_DATA_LEN) + return ENET_THLD_DEFAULT; + + while (thld > ENET_THLD_DEFAULT && + ENET_MAX_FRAME_LEN(mtu) - ETH_FCS_LEN > thld * ENET_THLD_UNIT && + thld * ENET_THLD_UNIT + ENET_TX_SAFE_MARGIN > mtu + ETH_HLEN) + thld -= ENET_THLD_BURST / ENET_THLD_UNIT; + + return thld; +} + /* A buffer has to hold everything the threshold lets the hardware deliver */ static unsigned int bcmgenet_rx_buf_len(unsigned int mtu) {
@@ -2672,8 +2734,10 @@ static unsigned int bcmgenet_rx_buf_len(unsigned int mtu) /* Program the MTU dependent registers. Call with the MAC disabled. */ static void bcmgenet_set_mtu_regs(struct bcmgenet_priv *priv, unsigned int mtu) { + u32 tx_thld = bcmgenet_tx_pkt_rdy_thld(mtu); u32 thld = bcmgenet_pkt_rdy_thld(mtu); + priv->tx_thld_len = tx_thld * ENET_THLD_UNIT; bcmgenet_umac_writel(priv, ENET_MAC_FRAME_LEN(mtu), UMAC_MAX_FRAME_LEN); /* GENET v1 maps other registers at these offsets */
@@ -2681,6 +2745,8 @@ static void bcmgenet_set_mtu_regs(struct bcmgenet_priv *priv, unsigned int mtu) return; bcmgenet_rbuf_writel(priv, thld, RBUF_PKT_RDY_THLD); + bcmgenet_writel(tx_thld, priv->base + priv->hw_params->tbuf_offset + + TBUF_PKT_RDY_THLD); } static void init_umac(struct bcmgenet_priv *priv)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 28395cb72fb6..0f28497998dd 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h@@ -616,6 +616,7 @@ struct bcmgenet_priv { void __iomem *tx_bds; struct enet_cb *tx_cbs; unsigned int num_tx_bds; + unsigned int tx_thld_len; struct bcmgenet_tx_ring tx_rings[GENET_MAX_MQ_CNT + 1];
--
2.53.0