From c1f43ca92398c0e6ab3706546a92962bbcd932b4 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 10 Sep 2026 10:51:08 +0200 Subject: [PATCH 1/8] net: bcmgenet: restore the hardware filters on open bcmgenet_hfb_init() runs INIT_LIST_HEAD() on priv->rxnfc_list, which drops every rule off the list, and bcmgenet_open() calls it on each ifup. Every rule the user configured is silently lost: # ethtool -N eth0 flow-type ether dst $MAC action 0 Added rule with ID 0 # ethtool -n eth0 | grep -c Filter: 1 # ip link set eth0 down && ip link set eth0 up # ethtool -n eth0 | grep -c Filter: 0 Initialise the lists once at probe and restore the rules on open, as bcmgenet_resume() already does. Fixes: 3e370952287c ("net: bcmgenet: add support for ethtool rxnfc flows") Signed-off-by: Nicolai Buchwitz --- .../net/ethernet/broadcom/genet/bcmgenet.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 8d54ca19a047c..815f4c1781241 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -744,8 +744,17 @@ static void bcmgenet_hfb_init(struct bcmgenet_priv *priv) INIT_LIST_HEAD(&priv->rxnfc_rules[i].list); priv->rxnfc_rules[i].state = BCMGENET_RXNFC_STATE_UNUSED; } +} + +static void bcmgenet_hfb_restore(struct bcmgenet_priv *priv) +{ + struct bcmgenet_rxnfc_rule *rule; bcmgenet_hfb_clear(priv); + + list_for_each_entry(rule, &priv->rxnfc_list, list) + if (rule->state != BCMGENET_RXNFC_STATE_UNUSED) + bcmgenet_hfb_create_rxnfc_filter(priv, rule); } static int bcmgenet_begin(struct net_device *dev) @@ -3318,8 +3327,8 @@ static int bcmgenet_open(struct net_device *dev) bcmgenet_set_hw_addr(priv, dev->dev_addr); - /* HFB init */ - bcmgenet_hfb_init(priv); + /* Restore the filters, the MAC was reset above */ + bcmgenet_hfb_restore(priv); /* Reinitialize TDMA and RDMA and SW housekeeping */ ret = bcmgenet_init_dma(priv, true); @@ -4021,6 +4030,7 @@ static int bcmgenet_probe(struct platform_device *pdev) /* Mii wait queue */ init_waitqueue_head(&priv->wq); + bcmgenet_hfb_init(priv); /* Always use RX_BUF_LENGTH (2KB) buffer for all chips */ priv->rx_buf_len = RX_BUF_LENGTH; INIT_WORK(&priv->bcmgenet_irq_work, bcmgenet_irq_task); @@ -4225,10 +4235,7 @@ static int bcmgenet_resume(struct device *d) bcmgenet_set_hw_addr(priv, dev->dev_addr); /* Restore hardware filters */ - bcmgenet_hfb_clear(priv); - list_for_each_entry(rule, &priv->rxnfc_list, list) - if (rule->state != BCMGENET_RXNFC_STATE_UNUSED) - bcmgenet_hfb_create_rxnfc_filter(priv, rule); + bcmgenet_hfb_restore(priv); /* Reinitialize TDMA and RDMA and SW housekeeping */ ret = bcmgenet_init_dma(priv, false); From cbbd0b09fc039fa073b91e8ab6ee99d100efad0d Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 10 Sep 2026 10:50:31 +0200 Subject: [PATCH 2/8] net: bcmgenet: stop Tx NAPI before disabling the queues bcmgenet_netif_stop() disables the Tx queues first and stops Tx NAPI several steps later. A completion in flight calls netif_tx_wake_queue() in between, so a queue runs again while bcmgenet_dma_teardown() and bcmgenet_fini_dma() free the rings, and a transmit entering that window touches freed control blocks. Close is safe because dev_deactivate_many() stops the qdisc before ndo_stop() runs. bcmgenet_suspend() leaves it running, so stop Tx NAPI before the queues. Signed-off-by: Nicolai Buchwitz --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 815f4c1781241..bfae11a70b75f 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -3383,6 +3383,8 @@ static void bcmgenet_netif_stop(struct net_device *dev, bool stop_phy) { struct bcmgenet_priv *priv = netdev_priv(dev); + /* Stop completion polling before it can wake a stopped queue */ + bcmgenet_disable_tx_napi(priv); netif_tx_disable(dev); /* Disable MAC receive */ @@ -3397,7 +3399,6 @@ static void bcmgenet_netif_stop(struct net_device *dev, bool stop_phy) /* Disable MAC transmit. TX DMA disabled must be done before this */ umac_enable_set(priv, CMD_TX_EN, false); - bcmgenet_disable_tx_napi(priv); bcmgenet_disable_rx_napi(priv); bcmgenet_intr_disable(priv); From 0b8b9498b086e546848b99820205e737a5d4b873 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 10 Sep 2026 10:51:20 +0200 Subject: [PATCH 3/8] net: bcmgenet: let the caller decide whether to start the PHY bcmgenet_netif_stop() already takes stop_phy. Give the start side the same choice so a caller that left the PHY running can bring the datapath back without tripping the phy_start() state check. No functional change. Signed-off-by: Nicolai Buchwitz --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index bfae11a70b75f..de44fd76d1f84 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -3279,7 +3279,7 @@ static void bcmgenet_get_hw_addr(struct bcmgenet_priv *priv, put_unaligned_be16(addr_tmp, &addr[4]); } -static void bcmgenet_netif_start(struct net_device *dev) +static void bcmgenet_netif_start(struct net_device *dev, bool start_phy) { struct bcmgenet_priv *priv = netdev_priv(dev); @@ -3296,7 +3296,8 @@ static void bcmgenet_netif_start(struct net_device *dev) /* Monitor link interrupts now */ bcmgenet_link_intr_enable(priv); - phy_start(dev->phydev); + if (start_phy) + phy_start(dev->phydev); } static int bcmgenet_open(struct net_device *dev) @@ -3359,7 +3360,7 @@ static int bcmgenet_open(struct net_device *dev) bcmgenet_phy_pause_set(dev, priv->rx_pause, priv->tx_pause); - bcmgenet_netif_start(dev); + bcmgenet_netif_start(dev, true); netif_tx_start_all_queues(dev); @@ -4248,7 +4249,7 @@ static int bcmgenet_resume(struct device *d) if (!device_may_wakeup(d)) phy_resume(dev->phydev); - bcmgenet_netif_start(dev); + bcmgenet_netif_start(dev, true); netif_device_attach(dev); From 1c25cbea3ff275a614fe7ba084f3ff51b6b25435 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 10 Sep 2026 10:51:34 +0200 Subject: [PATCH 4/8] net: bcmgenet: rename ENET_MAX_MTU_SIZE to ENET_MAX_FRAME_LEN ENET_MAX_MTU_SIZE holds a frame length, not an MTU. Both users program it into hardware that wants a frame length, so the name misleads as soon as the MTU stops being fixed at ETH_DATA_LEN. Name the receive offset too, which is open coded as 66. No functional change. Signed-off-by: Nicolai Buchwitz --- drivers/net/ethernet/broadcom/genet/bcmgenet.c | 8 ++++---- drivers/net/ethernet/broadcom/genet/bcmgenet.h | 14 ++++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index de44fd76d1f84..0417024cf7676 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -2414,8 +2414,8 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring, skb_put(skb, len); /* remove RSB and hardware 2bytes added for IP alignment */ - skb_pull(skb, 66); - len -= 66; + skb_pull(skb, ENET_RX_OFFSET); + len -= ENET_RX_OFFSET; if (priv->crc_fwd_en) { skb_trim(skb, len - ETH_FCS_LEN); @@ -2613,7 +2613,7 @@ static void init_umac(struct bcmgenet_priv *priv) UMAC_MIB_CTRL); bcmgenet_umac_writel(priv, 0, UMAC_MIB_CTRL); - bcmgenet_umac_writel(priv, ENET_MAX_MTU_SIZE, UMAC_MAX_FRAME_LEN); + bcmgenet_umac_writel(priv, ENET_MAX_FRAME_LEN, UMAC_MAX_FRAME_LEN); /* init tx registers, enable TSB */ reg = bcmgenet_tbuf_ctrl_get(priv); @@ -2719,7 +2719,7 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv, /* Set flow period for ring != 0 */ if (index) - flow_period_val = ENET_MAX_MTU_SIZE << 16; + flow_period_val = ENET_MAX_FRAME_LEN << 16; bcmgenet_tdma_ring_writel(priv, index, 0, TDMA_PROD_INDEX); bcmgenet_tdma_ring_writel(priv, index, 0, TDMA_CONS_INDEX); diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h index 9e4110c7fdf6f..e15553628a480 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h @@ -27,12 +27,18 @@ /* which ring is descriptor based */ #define DESC_INDEX 16 -/* Body(1500) + EH_SIZE(14) + VLANTAG(4) + BRCMTAG(6) + FCS(4) = 1528. - * 1536 is multiple of 256 bytes - */ #define ENET_BRCM_TAG_LEN 6 #define ENET_PAD 8 -#define ENET_MAX_MTU_SIZE (ETH_DATA_LEN + ETH_HLEN + VLAN_HLEN + \ + +/* The hardware writes a status block and two alignment bytes ahead of the + * frame. + */ +#define ENET_RSB_LEN 64 +#define ENET_RBUF_ALIGN 2 +#define ENET_RX_OFFSET (ENET_RSB_LEN + ENET_RBUF_ALIGN) + +/* Longest frame the MAC must accept for the default MTU */ +#define ENET_MAX_FRAME_LEN (ETH_DATA_LEN + ETH_HLEN + VLAN_HLEN + \ ENET_BRCM_TAG_LEN + ETH_FCS_LEN + ENET_PAD) #define DMA_MAX_BURST_LENGTH 0x10 From f6e66a7bb4f11b56d6978519513bbe2983225f0c Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Sun, 13 Sep 2026 20:08:09 +0200 Subject: [PATCH 5/8] net: bcmgenet: derive the receive buffer length from the MTU The receive buffer is a fixed 2048 bytes and the packet ready thresholds keep whatever the reset left them at, so neither follows the MTU. Compute the threshold from the MTU, program it into RBUF and TBUF, and size the buffer to what that threshold lets the hardware deliver, the status block on top of the threshold itself. The MTU is still fixed at ETH_DATA_LEN, so the threshold works out as the reset default and only the buffer grows, by the 64 bytes of status block it always had to hold. Signed-off-by: Nicolai Buchwitz --- .../net/ethernet/broadcom/genet/bcmgenet.c | 50 ++++++++++++++++--- .../net/ethernet/broadcom/genet/bcmgenet.h | 21 ++++++-- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 0417024cf7676..59dcbbacd3e02 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -49,7 +49,6 @@ #define GENET_Q0_TX_BD_CNT \ (TOTAL_DESC - priv->hw_params->tx_queues * priv->hw_params->tx_bds_per_q) -#define RX_BUF_LENGTH 2048 #define SKB_ALIGNMENT 32 /* Tx/Rx DMA register offset, skip 256 descriptors */ @@ -2368,7 +2367,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring, __func__, p_index, ring->c_index, ring->read_ptr, dma_length_status); - if (unlikely(len > RX_BUF_LENGTH)) { + if (unlikely(len > priv->rx_buf_len)) { netif_err(priv, rx_status, dev, "oversized packet\n"); BCMGENET_STATS64_INC(stats, length_errors); dev_kfree_skb_any(skb); @@ -2597,6 +2596,41 @@ static void bcmgenet_link_intr_enable(struct bcmgenet_priv *priv) bcmgenet_intrl2_0_writel(priv, int0_enable, INTRL2_CPU_MASK_CLEAR); } +/* Threshold in register units. Covers the alignment bytes and the frame, but + * not the status block, which the hardware adds on top. + */ +static unsigned int bcmgenet_pkt_rdy_thld(unsigned int mtu) +{ + unsigned int len = ENET_RBUF_ALIGN + mtu + ETH_HLEN + VLAN_HLEN; + + len = round_up(len, ENET_THLD_BURST) / ENET_THLD_UNIT; + + /* Keep the reset default for the common MTUs */ + return clamp_t(unsigned int, len, ENET_THLD_DEFAULT, ENET_THLD_MAX); +} + +/* A buffer has to hold everything the threshold lets the hardware deliver */ +static unsigned int bcmgenet_rx_buf_len(unsigned int mtu) +{ + return ENET_RSB_LEN + bcmgenet_pkt_rdy_thld(mtu) * ENET_THLD_UNIT; +} + +/* Program the MTU dependent registers. Call with the MAC disabled. */ +static void bcmgenet_set_mtu_regs(struct bcmgenet_priv *priv, unsigned int mtu) +{ + u32 thld = bcmgenet_pkt_rdy_thld(mtu); + + bcmgenet_umac_writel(priv, ENET_MAX_FRAME_LEN(mtu), UMAC_MAX_FRAME_LEN); + + /* GENET v1 maps other registers at these offsets */ + if (GENET_IS_V1(priv)) + return; + + bcmgenet_rbuf_writel(priv, thld, RBUF_PKT_RDY_THLD); + bcmgenet_writel(thld, priv->base + priv->hw_params->tbuf_offset + + TBUF_PKT_RDY_THLD); +} + static void init_umac(struct bcmgenet_priv *priv) { struct device *kdev = &priv->pdev->dev; @@ -2613,7 +2647,7 @@ static void init_umac(struct bcmgenet_priv *priv) UMAC_MIB_CTRL); bcmgenet_umac_writel(priv, 0, UMAC_MIB_CTRL); - bcmgenet_umac_writel(priv, ENET_MAX_FRAME_LEN, UMAC_MAX_FRAME_LEN); + bcmgenet_set_mtu_regs(priv, priv->dev->mtu); /* init tx registers, enable TSB */ reg = bcmgenet_tbuf_ctrl_get(priv); @@ -2719,7 +2753,7 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv, /* Set flow period for ring != 0 */ if (index) - flow_period_val = ENET_MAX_FRAME_LEN << 16; + flow_period_val = ENET_MAX_FRAME_LEN(priv->dev->mtu) << 16; bcmgenet_tdma_ring_writel(priv, index, 0, TDMA_PROD_INDEX); bcmgenet_tdma_ring_writel(priv, index, 0, TDMA_CONS_INDEX); @@ -2729,7 +2763,7 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv, TDMA_FLOW_PERIOD); bcmgenet_tdma_ring_writel(priv, index, ((size << DMA_RING_SIZE_SHIFT) | - RX_BUF_LENGTH), DMA_RING_BUF_SIZE); + priv->rx_buf_len), DMA_RING_BUF_SIZE); /* Set start and end address, read and write pointers */ bcmgenet_tdma_ring_writel(priv, index, start_ptr * words_per_bd, @@ -2777,7 +2811,7 @@ static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv, bcmgenet_rdma_ring_writel(priv, index, 0, RDMA_CONS_INDEX); bcmgenet_rdma_ring_writel(priv, index, ((size << DMA_RING_SIZE_SHIFT) | - RX_BUF_LENGTH), DMA_RING_BUF_SIZE); + priv->rx_buf_len), DMA_RING_BUF_SIZE); bcmgenet_rdma_ring_writel(priv, index, (DMA_FC_THRESH_LO << DMA_XOFF_THRESHOLD_SHIFT) | @@ -4033,8 +4067,8 @@ static int bcmgenet_probe(struct platform_device *pdev) /* Mii wait queue */ init_waitqueue_head(&priv->wq); bcmgenet_hfb_init(priv); - /* Always use RX_BUF_LENGTH (2KB) buffer for all chips */ - priv->rx_buf_len = RX_BUF_LENGTH; + + priv->rx_buf_len = bcmgenet_rx_buf_len(dev->mtu); INIT_WORK(&priv->bcmgenet_irq_work, bcmgenet_irq_task); priv->clk_wol = devm_clk_get_optional(&priv->pdev->dev, "enet-wol"); diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h index e15553628a480..65dbbf5ee8601 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h @@ -37,9 +37,21 @@ #define ENET_RBUF_ALIGN 2 #define ENET_RX_OFFSET (ENET_RSB_LEN + ENET_RBUF_ALIGN) -/* Longest frame the MAC must accept for the default MTU */ -#define ENET_MAX_FRAME_LEN (ETH_DATA_LEN + ETH_HLEN + VLAN_HLEN + \ - ENET_BRCM_TAG_LEN + ETH_FCS_LEN + ENET_PAD) +/* Longest frame the MAC must accept for a given MTU */ +#define ENET_FRAME_OVERHEAD (ETH_HLEN + VLAN_HLEN + ENET_BRCM_TAG_LEN + \ + ETH_FCS_LEN + ENET_PAD) +#define ENET_MAX_FRAME_LEN(mtu) ((mtu) + ENET_FRAME_OVERHEAD) + +/* RBUF and TBUF hand a frame to the DMA once the threshold is reached, so a + * longer frame arrives without an end of packet marker. Both registers are + * 8 bit in units of 16 bytes and want a multiple of the 256 byte burst size, + * so 0xf0 is the largest usable value. + */ +#define ENET_THLD_UNIT 16 +#define ENET_THLD_BURST 256 +#define ENET_THLD_DEFAULT 0x80 +#define ENET_THLD_MAX 0xf0 +#define ENET_THLD_MAX_LEN (ENET_THLD_MAX * ENET_THLD_UNIT) #define DMA_MAX_BURST_LENGTH 0x10 /* misc. configuration */ @@ -225,6 +237,8 @@ struct bcmgenet_rx_stats64 { #define RBUF_ALIGN_2B (1 << 1) #define RBUF_BAD_DIS (1 << 2) +#define RBUF_PKT_RDY_THLD 0x08 + #define RBUF_STATUS 0x0C #define RBUF_STATUS_WOL (1 << 0) #define RBUF_STATUS_MPD_INTR_ACTIVE (1 << 1) @@ -255,6 +269,7 @@ struct bcmgenet_rx_stats64 { #define TBUF_CTRL 0x00 #define TBUF_64B_EN (1 << 0) #define TBUF_BP_MC 0x0C +#define TBUF_PKT_RDY_THLD 0x10 #define TBUF_ENERGY_CTRL 0x14 #define TBUF_EEE_EN (1 << 0) #define TBUF_PM_EN (1 << 1) From be0baa71f4bb6d7818d0d0c1bc2b352c3ed3cf83 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Sun, 13 Sep 2026 20:08:21 +0200 Subject: [PATCH 6/8] net: bcmgenet: support an MTU of up to 3820 bytes The driver never sets dev->max_mtu, so the MTU is stuck at ETH_DATA_LEN. The thresholds follow the MTU, and their registers are 8 bit in units of 16 bytes and want a multiple of the 256 byte burst size, so 0xf0 is the largest usable value. That leaves an MTU of 3820 once the alignment bytes, the Ethernet header and a VLAN tag are taken off. Resize the buffers and rewrite the registers in place, so the PHY keeps running and the link stays up. A failed allocation falls back to the previous size, and if even that fails take the interface down rather than run on rings that are not there. Link: https://github.com/raspberrypi/linux/issues/5561 Signed-off-by: Nicolai Buchwitz --- .../net/ethernet/broadcom/genet/bcmgenet.c | 80 ++++++++++++++++++- .../net/ethernet/broadcom/genet/bcmgenet.h | 7 ++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 59dcbbacd3e02..e264581194686 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -3056,6 +3056,10 @@ static void bcmgenet_fini_dma(struct bcmgenet_priv *priv) struct netdev_queue *txq; int i; + /* An MTU change can fail with the rings already freed */ + if (!priv->rx_cbs) + return; + bcmgenet_fini_rx_napi(priv); bcmgenet_fini_tx_napi(priv); @@ -3066,7 +3070,9 @@ static void bcmgenet_fini_dma(struct bcmgenet_priv *priv) bcmgenet_free_rx_buffers(priv); kfree(priv->rx_cbs); + priv->rx_cbs = NULL; kfree(priv->tx_cbs); + priv->tx_cbs = NULL; } /* init_edma: Initialize DMA control register */ @@ -3126,6 +3132,7 @@ static int bcmgenet_init_dma(struct bcmgenet_priv *priv, bool flush_rx) GFP_KERNEL); if (!priv->tx_cbs) { kfree(priv->rx_cbs); + priv->rx_cbs = NULL; return -ENOMEM; } @@ -3144,7 +3151,9 @@ static int bcmgenet_init_dma(struct bcmgenet_priv *priv, bool flush_rx) netdev_err(priv->dev, "failed to initialize Rx queues\n"); bcmgenet_free_rx_buffers(priv); kfree(priv->rx_cbs); + priv->rx_cbs = NULL; kfree(priv->tx_cbs); + priv->tx_cbs = NULL; return ret; } @@ -3396,6 +3405,7 @@ static int bcmgenet_open(struct net_device *dev) bcmgenet_netif_start(dev, true); + priv->datapath_up = true; netif_tx_start_all_queues(dev); return 0; @@ -3454,7 +3464,11 @@ static int bcmgenet_close(struct net_device *dev) netif_dbg(priv, ifdown, dev, "bcmgenet_close\n"); - bcmgenet_netif_stop(dev, false); + /* A failed MTU change can have torn the datapath down already */ + if (priv->datapath_up) { + bcmgenet_netif_stop(dev, false); + priv->datapath_up = false; + } /* Really kill the PHY state machine and disconnect from it */ phy_disconnect(dev->phydev); @@ -3699,6 +3713,66 @@ static int bcmgenet_change_carrier(struct net_device *dev, bool new_carrier) return 0; } +static int bcmgenet_change_mtu(struct net_device *dev, int new_mtu) +{ + struct bcmgenet_priv *priv = netdev_priv(dev); + unsigned int old_mtu = dev->mtu; + int ret; + + if (!netif_running(dev)) { + WRITE_ONCE(dev->mtu, new_mtu); + priv->rx_buf_len = bcmgenet_rx_buf_len(new_mtu); + return 0; + } + + /* The watchdog trips on an idle queue once the rings are gone */ + netif_device_detach(dev); + + /* Only the buffers and the MTU registers change, leave the PHY up */ + bcmgenet_netif_stop(dev, false); + priv->datapath_up = false; + + WRITE_ONCE(dev->mtu, new_mtu); + priv->rx_buf_len = bcmgenet_rx_buf_len(new_mtu); + bcmgenet_set_mtu_regs(priv, new_mtu); + + ret = bcmgenet_init_dma(priv, true); + if (ret) { + /* Retry the size that was allocated a moment ago */ + WRITE_ONCE(dev->mtu, old_mtu); + priv->rx_buf_len = bcmgenet_rx_buf_len(old_mtu); + bcmgenet_set_mtu_regs(priv, old_mtu); + if (bcmgenet_init_dma(priv, true)) { + /* Nothing left to run on. Take the interface down so + * that close and suspend do not tear it down twice. + */ + netdev_err(dev, "failed to restore MTU %u, closing\n", + old_mtu); + netif_close(dev); + + /* Mark the device present again, __dev_open() + * refuses a detached one. The queues stay stopped + * because the interface is down by now. + */ + netif_device_attach(dev); + return ret; + } + } + + bcmgenet_hfb_restore(priv); + bcmgenet_netif_start(dev, false); + + /* bcmgenet_netif_start() only restores the link interrupt */ + if (bcmgenet_has_mdio_intr(priv)) + bcmgenet_intrl2_0_writel(priv, UMAC_IRQ_MDIO_EVENT, + INTRL2_CPU_MASK_CLEAR); + + priv->datapath_up = true; + netif_device_attach(dev); + + return ret; +} + static const struct net_device_ops bcmgenet_netdev_ops = { .ndo_open = bcmgenet_open, .ndo_stop = bcmgenet_close, @@ -3710,6 +3784,7 @@ static const struct net_device_ops bcmgenet_netdev_ops = { .ndo_set_features = bcmgenet_set_features, .ndo_get_stats64 = bcmgenet_get_stats64, .ndo_change_carrier = bcmgenet_change_carrier, + .ndo_change_mtu = bcmgenet_change_mtu, }; /* GENET hardware parameters/characteristics */ @@ -4068,7 +4143,10 @@ static int bcmgenet_probe(struct platform_device *pdev) init_waitqueue_head(&priv->wq); bcmgenet_hfb_init(priv); + /* v1 cannot program the thresholds, so it stays at the default MTU */ priv->rx_buf_len = bcmgenet_rx_buf_len(dev->mtu); + if (!GENET_IS_V1(priv)) + dev->max_mtu = ENET_MAX_MTU; INIT_WORK(&priv->bcmgenet_irq_work, bcmgenet_irq_task); priv->clk_wol = devm_clk_get_optional(&priv->pdev->dev, "enet-wol"); diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h index 65dbbf5ee8601..01d023ddc2cf3 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h @@ -52,6 +52,12 @@ #define ENET_THLD_DEFAULT 0x80 #define ENET_THLD_MAX 0xf0 #define ENET_THLD_MAX_LEN (ENET_THLD_MAX * ENET_THLD_UNIT) + +/* Largest MTU that fits one descriptor, with room for a VLAN tag so a VLAN + * interface can use the parent MTU. + */ +#define ENET_MAX_MTU (ENET_THLD_MAX_LEN - ENET_RBUF_ALIGN - \ + ETH_HLEN - VLAN_HLEN) #define DMA_MAX_BURST_LENGTH 0x10 /* misc. configuration */ @@ -642,6 +648,7 @@ struct bcmgenet_priv { unsigned autoneg_pause:1; unsigned tx_pause:1; unsigned rx_pause:1; + unsigned datapath_up:1; /* MDIO bus variables */ wait_queue_head_t wq; From 3f33029d40e56fdb887d238b5152dcbe293b4512 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Sat, 12 Sep 2026 19:57:26 +0200 Subject: [PATCH 7/8] net: bcmgenet: reassemble jumbo frames from status block fragments A frame longer than the packet ready threshold is not truncated. The hardware splits it across descriptors and writes a status block at the start of each one, so the first fragment arrives with SOP set and no EOP and is dropped as fragmented. That caps the MTU at 3820. In order to support a larger MTU, the fragments have to be reassembled after the status blocks have been stripped. The MAC only checksums frames up to the threshold and drops longer ones silently, so check those in software. That costs little at jumbo sizes, where the larger frame saves more per packet overhead. Use 16347 as the maximum MTU, based on the 14 bit UMAC_MAX_FRAME_LEN, which counts the FCS. Suggested-by: Justin Chen Signed-off-by: Nicolai Buchwitz --- .../net/ethernet/broadcom/genet/bcmgenet.c | 113 +++++++++++++++--- .../net/ethernet/broadcom/genet/bcmgenet.h | 6 + 2 files changed, 105 insertions(+), 14 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index e264581194686..8f097bb7167a1 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -2145,6 +2145,20 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev) goto out; } + /* The MAC holds a frame to insert its checksum, but only up to the + * packet ready threshold. Longer frames are dropped silently. + */ + if (unlikely(skb->len > priv->tx_csum_max_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; + } + nr_frags = skb_shinfo(skb)->nr_frags; + } + /* Retain how many bytes will be sent on the wire, without TSB inserted * by transmit checksum offload */ @@ -2287,6 +2301,57 @@ static struct sk_buff *bcmgenet_rx_refill(struct bcmgenet_priv *priv, return rx_skb; } +static void bcmgenet_discard_frags(struct bcmgenet_rx_ring *ring) +{ + if (!ring->frag_head) + return; + + dev_kfree_skb_any(ring->frag_head); + ring->frag_head = NULL; + ring->frag_tail = NULL; +} + +/* A frame longer than the threshold arrives in several descriptors, each with + * its own status block. + */ +static struct sk_buff *bcmgenet_reassemble(struct bcmgenet_rx_ring *ring, + struct sk_buff *skb, + unsigned int dma_flag, + unsigned int *len) +{ + struct sk_buff *head = ring->frag_head; + + /* the first fragment also carries the alignment bytes */ + if (dma_flag & DMA_SOP) { + skb_pull(skb, ENET_RX_OFFSET); + ring->frag_head = skb; + ring->frag_tail = skb; + return NULL; + } + + skb_pull(skb, ENET_RSB_LEN); + *len -= ENET_RSB_LEN; + + if (skb_shinfo(head)->frag_list) + ring->frag_tail->next = skb; + else + skb_shinfo(head)->frag_list = skb; + ring->frag_tail = skb; + + head->len += *len; + head->data_len += *len; + head->truesize += skb->truesize; + + if (!(dma_flag & DMA_EOP)) + return NULL; + + ring->frag_head = NULL; + ring->frag_tail = NULL; + *len = head->len; + + return head; +} + /* bcmgenet_desc_rx - descriptor based rx process. * this could be called from bottom half, or from NAPI polling method. */ @@ -2343,18 +2408,13 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring, if (unlikely(!skb)) { BCMGENET_STATS64_INC(stats, dropped); + bcmgenet_discard_frags(ring); goto next; } status = (struct status_64 *)skb->data; dma_length_status = status->length_status; - if (dev->features & NETIF_F_RXCSUM) { - rx_csum = (__force __be16)(status->rx_csum & 0xffff); - if (rx_csum) { - skb->csum = (__force __wsum)ntohs(rx_csum); - skb->ip_summed = CHECKSUM_COMPLETE; - } - } + rx_csum = (__force __be16)(status->rx_csum & 0xffff); /* DMA flags and length are still valid no matter how * we got the Receive Status Vector (64B RSB or register) @@ -2370,13 +2430,18 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring, if (unlikely(len > priv->rx_buf_len)) { netif_err(priv, rx_status, dev, "oversized packet\n"); BCMGENET_STATS64_INC(stats, length_errors); + bcmgenet_discard_frags(ring); dev_kfree_skb_any(skb); goto next; } - if (unlikely(!(dma_flag & DMA_EOP) || !(dma_flag & DMA_SOP))) { - netif_err(priv, rx_status, dev, - "dropping fragmented packet!\n"); + /* A new SOP resynchronizes after an incomplete frame */ + if (dma_flag & DMA_SOP) { + if (ring->frag_head) { + BCMGENET_STATS64_INC(stats, fragmented_errors); + bcmgenet_discard_frags(ring); + } + } else if (unlikely(!ring->frag_head)) { BCMGENET_STATS64_INC(stats, fragmented_errors); dev_kfree_skb_any(skb); goto next; @@ -2406,21 +2471,35 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring, DMA_RX_RXER)) == DMA_RX_RXER) u64_stats_inc(&stats->errors); u64_stats_update_end(&stats->syncp); + bcmgenet_discard_frags(ring); dev_kfree_skb_any(skb); goto next; } /* error packet */ skb_put(skb, len); - /* remove RSB and hardware 2bytes added for IP alignment */ - skb_pull(skb, ENET_RX_OFFSET); - len -= ENET_RX_OFFSET; + if (likely((dma_flag & (DMA_SOP | DMA_EOP)) == + (DMA_SOP | DMA_EOP))) { + /* remove RSB and hardware 2bytes added for IP alignment */ + skb_pull(skb, ENET_RX_OFFSET); + len -= ENET_RX_OFFSET; + } else { + skb = bcmgenet_reassemble(ring, skb, dma_flag, &len); + if (!skb) + goto next; + } if (priv->crc_fwd_en) { skb_trim(skb, len - ETH_FCS_LEN); len -= ETH_FCS_LEN; } + /* Only the last fragment has the checksum of the whole frame */ + if ((dev->features & NETIF_F_RXCSUM) && rx_csum) { + skb->csum = (__force __wsum)ntohs(rx_csum); + skb->ip_summed = CHECKSUM_COMPLETE; + } + bytes_processed += len; /*Finish setting up the received SKB and send it to the kernel*/ @@ -2622,6 +2701,9 @@ static void bcmgenet_set_mtu_regs(struct bcmgenet_priv *priv, unsigned int mtu) bcmgenet_umac_writel(priv, ENET_MAX_FRAME_LEN(mtu), UMAC_MAX_FRAME_LEN); + thld = bcmgenet_pkt_rdy_thld(mtu); + priv->tx_csum_max_len = thld * ENET_THLD_UNIT; + /* GENET v1 maps other registers at these offsets */ if (GENET_IS_V1(priv)) return; @@ -3068,6 +3150,9 @@ static void bcmgenet_fini_dma(struct bcmgenet_priv *priv) netdev_tx_reset_queue(txq); } + for (i = 0; i <= priv->hw_params->rx_queues; i++) + bcmgenet_discard_frags(&priv->rx_rings[i]); + bcmgenet_free_rx_buffers(priv); kfree(priv->rx_cbs); priv->rx_cbs = NULL; @@ -4146,7 +4231,7 @@ static int bcmgenet_probe(struct platform_device *pdev) /* v1 cannot program the thresholds, so it stays at the default MTU */ priv->rx_buf_len = bcmgenet_rx_buf_len(dev->mtu); if (!GENET_IS_V1(priv)) - dev->max_mtu = ENET_MAX_MTU; + dev->max_mtu = ENET_MAX_JUMBO_MTU; INIT_WORK(&priv->bcmgenet_irq_work, bcmgenet_irq_task); priv->clk_wol = devm_clk_get_optional(&priv->pdev->dev, "enet-wol"); diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h index 01d023ddc2cf3..574394684a901 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h @@ -58,6 +58,9 @@ */ #define ENET_MAX_MTU (ENET_THLD_MAX_LEN - ENET_RBUF_ALIGN - \ ETH_HLEN - VLAN_HLEN) + +/* UMAC_MAX_FRAME_LEN is 14 bits wide and counts the FCS */ +#define ENET_MAX_JUMBO_MTU (GENMASK(13, 0) - ENET_FRAME_OVERHEAD) #define DMA_MAX_BURST_LENGTH 0x10 /* misc. configuration */ @@ -599,6 +602,8 @@ struct bcmgenet_rx_ring { unsigned int cb_ptr; /* Rx ring initial CB ptr */ unsigned int end_ptr; /* Rx ring end CB ptr */ unsigned int old_discards; + struct sk_buff *frag_head; /* frame being reassembled */ + struct sk_buff *frag_tail; /* its last fragment */ struct bcmgenet_net_dim dim; u32 rx_max_coalesced_frames; u32 rx_coalesce_usecs; @@ -629,6 +634,7 @@ struct bcmgenet_priv { void __iomem *tx_bds; struct enet_cb *tx_cbs; unsigned int num_tx_bds; + unsigned int tx_csum_max_len; struct bcmgenet_tx_ring tx_rings[GENET_MAX_MQ_CNT + 1]; From 79b69ad7b0c4c02c45d63c5c74af8fb518511f46 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Mon, 14 Sep 2026 09:28:31 +0200 Subject: [PATCH 8/8] net: phy: broadcom: enable jumbo frames on BCM54xx Jumbo packets need two bits that default to off, extended packet length in the auxiliary control register and PCS transmit FIFO elasticity in the extended control register. The latter raises the transmit limit from 4.5 KB to 9 KB at the cost of 16 ns of 1000BASE-T transmit latency, and the two together take copper mode to 10 KB. Without them such frames are lost on a 100M link while the same frames pass at 1G. On a Raspberry Pi CM5, which uses a BCM54210PE, 9142 byte frames at 100M are lost 20 out of 20 with the MAC counting every one as transmitted. The same frames over the same path at 1G arrive intact. Set both, which bcm_phy_enable_jumbo() already does for bcm7xxx. The frames then arrive and the payloads check out byte for byte. Signed-off-by: Nicolai Buchwitz --- drivers/net/phy/broadcom.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c index a4d8c4d6f19fb..a3f0f4eb4df0e 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -570,6 +570,13 @@ static int bcm54xx_config_init(struct phy_device *phydev) bcm54xx_ptp_config_init(phydev); + /* Transmit is limited to 4.5 KB without it, see the BCM54210PE + * datasheet section 5.4.12.14. + */ + err = bcm_phy_enable_jumbo(phydev); + if (err < 0) + return err; + /* Acknowledge any left over interrupt and charge the device for * wake-up. */