From a2307fe3ddf50c09edaef6fe96a65e5b7deeb0e5 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 10 Sep 2026 10:51:08 +0200 Subject: [PATCH 1/6] 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 80d45900566cac87cc224469428ed61c54170ec3 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 10 Sep 2026 10:50:31 +0200 Subject: [PATCH 2/6] net: bcmgenet: stop Tx NAPI before disabling the queues bcmgenet_netif_stop() disables the Tx queues first and only stops Tx NAPI several steps later. A completion already 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. The close path is not affected because dev_deactivate_many() stops the qdisc before ndo_stop() runs. bcmgenet_suspend() and the MTU change added later in this series leave the qdisc running, so both can hit it. 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 fe3c9e4de324a6f6bc4ae928f1f5efa151e395e7 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 10 Sep 2026 10:51:20 +0200 Subject: [PATCH 3/6] 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 b48250c12e60fd5bbde7bd0df6c68393142b900d Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 10 Sep 2026 10:51:34 +0200 Subject: [PATCH 4/6] 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. Give the receive offset a name as well, it 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 c312a50e9ad56cbc434eaff407656556980f7fa5 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Thu, 10 Sep 2026 10:53:26 +0200 Subject: [PATCH 5/6] 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. Raising it alone does not work: the RBUF and TBUF packet ready thresholds cut a frame off at their 2048 byte reset default. Receive then finds no end of packet marker and drops the frame as fragmented, and transmit takes the frame but never puts it on the wire. Program both thresholds from the configured MTU and size the DMA buffers to hold exactly what the threshold permits. The 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. Changing the MTU only has to resize the buffers and rewrite those registers, so the PHY keeps running and the link stays up. Link: https://github.com/raspberrypi/linux/issues/5561 Signed-off-by: Nicolai Buchwitz --- .../net/ethernet/broadcom/genet/bcmgenet.c | 131 ++++++++++++++++-- .../net/ethernet/broadcom/genet/bcmgenet.h | 25 +++- 2 files changed, 145 insertions(+), 11 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 0417024cf7676..4f350ac0d11da 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,42 @@ 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_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; + + thld = bcmgenet_pkt_rdy_thld(mtu); + 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 +2648,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 +2754,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 +2764,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 +2812,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) | @@ -3022,6 +3057,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); @@ -3032,7 +3071,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 */ @@ -3092,6 +3133,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; } @@ -3110,7 +3152,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; } @@ -3362,6 +3406,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; @@ -3420,7 +3465,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); @@ -3665,6 +3714,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, @@ -3676,6 +3785,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 */ @@ -4033,8 +4143,11 @@ 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; + + /* 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 e15553628a480..bd0631a17fbcc 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h @@ -37,9 +37,26 @@ #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 + \ +/* Longest frame the MAC must accept for a given MTU */ +#define ENET_MAX_FRAME_LEN(mtu) ((mtu) + ETH_HLEN + VLAN_HLEN + \ ENET_BRCM_TAG_LEN + ETH_FCS_LEN + ENET_PAD) + +/* 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 and is dropped. 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) + +/* Largest MTU the threshold allows, 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 */ @@ -225,6 +242,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 +274,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) @@ -622,6 +642,7 @@ struct bcmgenet_priv { struct bcmgenet_rx_ring rx_rings[GENET_MAX_MQ_CNT + 1]; /* other misc variables */ + bool datapath_up; const struct bcmgenet_hw_params *hw_params; u32 flags; unsigned autoneg_pause:1; From 124162d1cdfbf50b51b1a1c6f5242e9342eba261 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Sat, 12 Sep 2026 19:57:26 +0200 Subject: [PATCH 6/6] 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. Reassemble the fragments instead of dropping them: strip the status block from every fragment, and the alignment bytes from the first as well, chain the rest onto the head through frag_list and hand the frame up when EOP arrives. Both status blocks stay on, so both checksum offloads keep working. Transmit still has one restriction. The MAC has to hold a whole frame to insert its checksum and it only holds up to the threshold. Above that the frame is dropped and no counter moves, so check those in software. Below ENET_MAX_MTU the threshold always covers the longest frame, so only jumbo traffic pays for it. The maximum MTU is 16347, derived from 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 | 87 +++++++++++++++++-- .../net/ethernet/broadcom/genet/bcmgenet.h | 16 +++- 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 4f350ac0d11da..82e00c724e594 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,16 @@ 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; +} + /* bcmgenet_desc_rx - descriptor based rx process. * this could be called from bottom half, or from NAPI polling method. */ @@ -2343,6 +2367,7 @@ 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; } @@ -2370,13 +2395,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,15 +2436,51 @@ 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; + /* Every fragment has a status block, only the first one also + * has the alignment bytes. + */ + if (dma_flag & DMA_SOP) { + skb_pull(skb, ENET_RX_OFFSET); + len -= ENET_RX_OFFSET; + } else { + skb_pull(skb, ENET_RSB_LEN); + len -= ENET_RSB_LEN; + } + + if (!(dma_flag & DMA_SOP)) { + /* chain onto the frame already being collected */ + if (!skb_shinfo(ring->frag_head)->frag_list) + skb_shinfo(ring->frag_head)->frag_list = skb; + else + ring->frag_tail->next = skb; + ring->frag_tail = skb; + + ring->frag_head->len += len; + ring->frag_head->data_len += len; + ring->frag_head->truesize += skb->truesize; + } + + if (!(dma_flag & DMA_EOP)) { + if (dma_flag & DMA_SOP) { + ring->frag_head = skb; + ring->frag_tail = skb; + } + goto next; + } + + if (!(dma_flag & DMA_SOP)) { + skb = ring->frag_head; + len = skb->len; + ring->frag_head = NULL; + ring->frag_tail = NULL; + } if (priv->crc_fwd_en) { skb_trim(skb, len - ETH_FCS_LEN); @@ -2622,11 +2688,13 @@ 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; - thld = bcmgenet_pkt_rdy_thld(mtu); bcmgenet_rbuf_writel(priv, thld, RBUF_PKT_RDY_THLD); bcmgenet_writel(thld, priv->base + priv->hw_params->tbuf_offset + TBUF_PKT_RDY_THLD); @@ -3069,6 +3137,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; @@ -4147,7 +4218,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 bd0631a17fbcc..754f72e07740d 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h @@ -38,8 +38,9 @@ #define ENET_RX_OFFSET (ENET_RSB_LEN + ENET_RBUF_ALIGN) /* Longest frame the MAC must accept for a given MTU */ -#define ENET_MAX_FRAME_LEN(mtu) ((mtu) + ETH_HLEN + VLAN_HLEN + \ - ENET_BRCM_TAG_LEN + ETH_FCS_LEN + ENET_PAD) +#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 and is dropped. Both @@ -52,11 +53,15 @@ #define ENET_THLD_MAX 0xf0 #define ENET_THLD_MAX_LEN (ENET_THLD_MAX * ENET_THLD_UNIT) -/* Largest MTU the threshold allows, with room for a VLAN tag so a VLAN - * interface can use the parent MTU. +/* Largest MTU that fits one descriptor, with room for a VLAN tag so a VLAN + * interface can use the parent MTU. Longer frames are split at the threshold + * and reassembled in bcmgenet_desc_rx(). */ #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 */ @@ -598,6 +603,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; @@ -636,6 +643,7 @@ struct bcmgenet_priv { struct enet_cb *rx_cbs; unsigned int num_rx_bds; unsigned int rx_buf_len; + unsigned int tx_csum_max_len; struct bcmgenet_rxnfc_rule rxnfc_rules[MAX_NUM_OF_FS_RULES]; struct list_head rxnfc_list;