From 617f7d3726d4e3c739f13ac397ab4eda60444400 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 501303b0a37080acfedbe3c033b93f337439a95d 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 22c300b69e40446209808d78dce7b1ce8b319e59 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 19d3a75e2c4a735ee533e3531d24c1cfbc0d272d 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 will mislead once the MTU is no longer fixed at ETH_DATA_LEN. Name the receive offset too, which was 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 e1498f29ae52bd68bdfa5dd2597717967856f1a3 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 36e861aa1b2e188339320ba3578476dbb50a0a91 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Fri, 11 Sep 2026 11:35:40 +0200 Subject: [PATCH 6/6] net: bcmgenet: support jumbo frames by dropping the status blocks The packet ready threshold is where the receive buffer writes its status block, not a limit on the frame, so anything past it arrives without an end of packet marker. That is what caps the MTU at 3820. Turn the status blocks off above ENET_MAX_MTU and the limit goes away. The receive length then comes from the descriptor. Both checksum offloads live in the blocks, so ndo_fix_features masks them out while jumbo is in use and restores them on the way back down. Measured on a CM4 at MTU 9000 with the threshold still at 0xf0: 986 Mbit/s receive, 990 transmit, payloads byte exact at 1514, 4096, 8192 and 9014 byte frames. Switching between 1500 and 9000 on a live interface works in both directions. Signed-off-by: Nicolai Buchwitz --- .../net/ethernet/broadcom/genet/bcmgenet.c | 148 +++++++++++++----- .../net/ethernet/broadcom/genet/bcmgenet.h | 7 +- 2 files changed, 114 insertions(+), 41 deletions(-) diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c index 4f350ac0d11da..7464dcf16b822 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c @@ -51,6 +51,15 @@ #define SKB_ALIGNMENT 32 +/* The packet ready threshold truncates a frame where the status block would + * be written, so above ENET_MAX_MTU the blocks have to be turned off. The + * checksum offloads live in those blocks, so they go too. + */ +static bool bcmgenet_jumbo(const struct net_device *dev) +{ + return dev->mtu > ENET_MAX_MTU; +} + /* Tx/Rx DMA register offset, skip 256 descriptors */ #define WORDS_PER_BD(p) (p->hw_params->words_per_bd) #define DMA_DESC_SIZE (WORDS_PER_BD(priv) * sizeof(u32)) @@ -2150,11 +2159,13 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev) */ GENET_CB(skb)->bytes_sent = skb->len; - /* add the Transmit Status Block */ - skb = bcmgenet_add_tsb(dev, skb, ring); - if (!skb) { - ret = NETDEV_TX_OK; - goto out; + /* add the Transmit Status Block, absent in jumbo mode */ + if (!bcmgenet_jumbo(dev)) { + skb = bcmgenet_add_tsb(dev, skb, ring); + if (!skb) { + ret = NETDEV_TX_OK; + goto out; + } } for (i = 0; i <= nr_frags; i++) { @@ -2209,7 +2220,8 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev) GENET_CB(skb)->last_cb = tx_cb_ptr; - bcmgenet_hide_tsb(skb); + if (!bcmgenet_jumbo(dev)) + bcmgenet_hide_tsb(skb); skb_tx_timestamp(skb); /* Decrement total BD count and advance our write pointer */ @@ -2339,6 +2351,10 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring, __be16 rx_csum; cb = &priv->rx_cbs[ring->read_ptr]; + if (bcmgenet_jumbo(dev)) + dma_length_status = + bcmgenet_readl(cb->bd_addr + + DMA_DESC_LENGTH_STATUS); skb = bcmgenet_rx_refill(priv, cb); if (unlikely(!skb)) { @@ -2346,13 +2362,17 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *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; + if (!bcmgenet_jumbo(dev)) { + 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; + } } } @@ -2412,9 +2432,14 @@ 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, ENET_RX_OFFSET); - len -= ENET_RX_OFFSET; + /* remove the alignment bytes, and the RSB if there is one */ + if (bcmgenet_jumbo(dev)) { + skb_pull(skb, ENET_RBUF_ALIGN); + len -= ENET_RBUF_ALIGN; + } else { + skb_pull(skb, ENET_RX_OFFSET); + len -= ENET_RX_OFFSET; + } if (priv->crc_fwd_en) { skb_trim(skb, len - ETH_FCS_LEN); @@ -2609,12 +2634,59 @@ static unsigned int bcmgenet_pkt_rdy_thld(unsigned int mtu) return clamp_t(unsigned int, len, ENET_THLD_DEFAULT, ENET_THLD_MAX); } -/* A buffer has to hold everything the threshold lets the hardware deliver */ +/* A buffer has to hold everything the hardware can deliver: a whole frame in + * jumbo mode, the threshold plus the status block otherwise. + */ static unsigned int bcmgenet_rx_buf_len(unsigned int mtu) { + if (mtu > ENET_MAX_MTU) + return round_up(ENET_RBUF_ALIGN + ENET_MAX_FRAME_LEN(mtu), + ENET_THLD_BURST); + return ENET_RSB_LEN + bcmgenet_pkt_rdy_thld(mtu) * ENET_THLD_UNIT; } +/* The status blocks follow the MTU, so this runs on an MTU change as well as + * at open. Call with the MAC disabled. + */ +static void bcmgenet_set_status_blocks(struct bcmgenet_priv *priv) +{ + bool jumbo = bcmgenet_jumbo(priv->dev); + u32 reg; + + reg = bcmgenet_tbuf_ctrl_get(priv); + if (jumbo) + reg &= ~TBUF_64B_EN; + else + reg |= TBUF_64B_EN; + bcmgenet_tbuf_ctrl_set(priv, reg); + + /* ip header optimization stays on, the RSB follows the MTU */ + reg = bcmgenet_rbuf_readl(priv, RBUF_CTRL); + reg |= RBUF_ALIGN_2B; + if (jumbo) + reg &= ~RBUF_64B_EN; + else + reg |= RBUF_64B_EN; + bcmgenet_rbuf_writel(priv, reg, RBUF_CTRL); + + /* rx checksumming needs the RSB */ + reg = bcmgenet_rbuf_readl(priv, RBUF_CHK_CTRL); + reg |= RBUF_L3_PARSE_DIS; + if (jumbo) + reg &= ~RBUF_RXCHK_EN; + else + reg |= RBUF_RXCHK_EN; + /* If UniMAC forwards CRC, we need to skip over it to get + * a valid CHK bit to be set in the per-packet status word + */ + if (priv->crc_fwd_en) + reg |= RBUF_SKIP_FCS; + else + reg &= ~RBUF_SKIP_FCS; + bcmgenet_rbuf_writel(priv, reg, RBUF_CHK_CTRL); +} + /* Program the MTU dependent registers. Call with the MAC disabled. */ static void bcmgenet_set_mtu_regs(struct bcmgenet_priv *priv, unsigned int mtu) { @@ -2650,27 +2722,7 @@ static void init_umac(struct bcmgenet_priv *priv) bcmgenet_set_mtu_regs(priv, priv->dev->mtu); - /* init tx registers, enable TSB */ - reg = bcmgenet_tbuf_ctrl_get(priv); - reg |= TBUF_64B_EN; - bcmgenet_tbuf_ctrl_set(priv, reg); - - /* init rx registers, enable ip header optimization and RSB */ - reg = bcmgenet_rbuf_readl(priv, RBUF_CTRL); - reg |= RBUF_ALIGN_2B | RBUF_64B_EN; - bcmgenet_rbuf_writel(priv, reg, RBUF_CTRL); - - /* enable rx checksumming */ - reg = bcmgenet_rbuf_readl(priv, RBUF_CHK_CTRL); - reg |= RBUF_RXCHK_EN | RBUF_L3_PARSE_DIS; - /* If UniMAC forwards CRC, we need to skip over it to get - * a valid CHK bit to be set in the per-packet status word - */ - if (priv->crc_fwd_en) - reg |= RBUF_SKIP_FCS; - else - reg &= ~RBUF_SKIP_FCS; - bcmgenet_rbuf_writel(priv, reg, RBUF_CHK_CTRL); + bcmgenet_set_status_blocks(priv); if (!GENET_IS_V1(priv) && !GENET_IS_V2(priv)) bcmgenet_rbuf_writel(priv, 1, RBUF_TBUF_SIZE_CTRL); @@ -3714,6 +3766,19 @@ static int bcmgenet_change_carrier(struct net_device *dev, bool new_carrier) return 0; } +/* Jumbo turns off the status blocks that carry both checksum offloads. Mask + * them here rather than clearing hw_features: a feature set in features but + * missing from hw_features counts as permanently on. + */ +static netdev_features_t bcmgenet_fix_features(struct net_device *dev, + netdev_features_t features) +{ + if (bcmgenet_jumbo(dev)) + features &= ~(NETIF_F_HW_CSUM | NETIF_F_RXCSUM); + + return features; +} + static int bcmgenet_change_mtu(struct net_device *dev, int new_mtu) { struct bcmgenet_priv *priv = netdev_priv(dev); @@ -3723,6 +3788,7 @@ static int bcmgenet_change_mtu(struct net_device *dev, int new_mtu) if (!netif_running(dev)) { WRITE_ONCE(dev->mtu, new_mtu); priv->rx_buf_len = bcmgenet_rx_buf_len(new_mtu); + netdev_update_features(dev); return 0; } @@ -3736,6 +3802,7 @@ static int bcmgenet_change_mtu(struct net_device *dev, int new_mtu) WRITE_ONCE(dev->mtu, new_mtu); priv->rx_buf_len = bcmgenet_rx_buf_len(new_mtu); bcmgenet_set_mtu_regs(priv, new_mtu); + bcmgenet_set_status_blocks(priv); ret = bcmgenet_init_dma(priv, true); if (ret) { @@ -3743,6 +3810,7 @@ static int bcmgenet_change_mtu(struct net_device *dev, int new_mtu) WRITE_ONCE(dev->mtu, old_mtu); priv->rx_buf_len = bcmgenet_rx_buf_len(old_mtu); bcmgenet_set_mtu_regs(priv, old_mtu); + bcmgenet_set_status_blocks(priv); 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. @@ -3770,6 +3838,7 @@ static int bcmgenet_change_mtu(struct net_device *dev, int new_mtu) priv->datapath_up = true; netif_device_attach(dev); + netdev_update_features(dev); return ret; } @@ -3786,6 +3855,7 @@ static const struct net_device_ops bcmgenet_netdev_ops = { .ndo_get_stats64 = bcmgenet_get_stats64, .ndo_change_carrier = bcmgenet_change_carrier, .ndo_change_mtu = bcmgenet_change_mtu, + .ndo_fix_features = bcmgenet_fix_features, }; /* GENET hardware parameters/characteristics */ @@ -4147,7 +4217,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..bf47be87f7af0 100644 --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h @@ -52,11 +52,14 @@ #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 the threshold allows while the status blocks are in use, 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) + +/* Above that the status blocks have to be turned off, see bcmgenet_jumbo() */ +#define ENET_MAX_JUMBO_MTU 9000 #define DMA_MAX_BURST_LENGTH 0x10 /* misc. configuration */