[PATCH net-next v2 6/7] net: bcmgenet: pad transmit frames out of the packet ready window

From: Nicolai Buchwitz

Date: Mon Oct 05 2026 - 18:28:38 EST


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 CM4
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.

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.

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.

Signed-off-by: Nicolai Buchwitz <nb@xxxxxxxxxxx>
Tested-by: Pierre-Marin Leclercq <pierremarinleclercq88@xxxxxxxxx>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 52 +++++++++++++++++++++++++-
drivers/net/ethernet/broadcom/genet/bcmgenet.h | 1 +
2 files changed, 51 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 3e2ebd9a2cc5..e8f86374c7cd 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
@@ -2173,6 +2178,31 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
goto out;
}

+ /* 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);
+ ret = NETDEV_TX_OK;
+ goto out;
+ }
+ }
+
+ /* 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);
+ ret = NETDEV_TX_OK;
+ goto out;
+ }
+ }
+
+ nr_frags = skb_shinfo(skb)->nr_frags;
+
/* Retain how many bytes will be sent on the wire, without TSB inserted
* by transmit checksum offload
*/
@@ -2661,6 +2691,23 @@ 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;
+
+ 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)
{
@@ -2671,8 +2718,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_MAX_FRAME_LEN(mtu), UMAC_MAX_FRAME_LEN);

/* GENET v1 maps other registers at these offsets */
@@ -2680,8 +2729,7 @@ 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(ENET_THLD_MAX,
- priv->base + priv->hw_params->tbuf_offset +
+ bcmgenet_writel(tx_thld, priv->base + priv->hw_params->tbuf_offset +
TBUF_PKT_RDY_THLD);
}

diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 75cfbccfd4ce..a4933a5d3823 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -620,6 +620,7 @@ struct bcmgenet_priv {
struct enet_cb *rx_cbs;
unsigned int num_rx_bds;
unsigned int rx_buf_len;
+ unsigned int tx_thld_len;
struct bcmgenet_rxnfc_rule rxnfc_rules[MAX_NUM_OF_FS_RULES];
struct list_head rxnfc_list;


--
2.53.0