[PATCH net-next v6 8/9] net: bcmgenet: allow the MTU to be changed
From: Nicolai Buchwitz
Date: Tue Nov 03 2026 - 05:46:53 EST
The driver never sets dev->max_mtu, so the MTU is stuck at ETH_DATA_LEN.
One descriptor reaches as far as the packet ready threshold, so derive the
maximum from it. The threshold registers are 8 bit in units of 16 bytes and
want a multiple of the 256 byte burst size. A descriptor is one page and
also holds skb_shared_info behind the frame. On 4K pages the page is the
tighter limit and leaves 3564 bytes. That includes room for a VLAN tag so a
VLAN interface can run at the parent MTU. The MAC accepts a little more
than one descriptor holds, so a double tagged frame at that maximum is
dropped as fragmented until a later patch reassembles those.
The PHY keeps running across the change so the link stays up, with its
lock held so that a link event cannot enable the MAC while the registers
change. Two MTUs at or below ETH_DATA_LEN derive the same values, so a
change between them needs no restart at all.
Only a DMA that refuses to halt still takes the interface down, nothing is
left to run on then.
Suggested-by: Dave Stevenson <dave.stevenson@xxxxxxxxxxxxxxx>
Link: https://github.com/raspberrypi/linux/issues/5561
Signed-off-by: Nicolai Buchwitz <nb@xxxxxxxxxxx>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 88 ++++++++++++++++++++++++++
1 file changed, 88 insertions(+)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index f6ed15bb1a91c..e42de63fac14d 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -84,6 +84,12 @@
ENET_THLD_MAX * ENET_THLD_UNIT, \
ENET_THLD_PAGE_LEN)
+/* 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 - GENET_RBUF_ALIGN - \
+ ETH_HLEN - VLAN_HLEN)
+
/* 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))
@@ -3965,6 +3971,81 @@ 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;
+ struct bcmgenet_dma_set set;
+ int ret;
+
+ /* Below ETH_DATA_LEN everything is floored, so there is nothing to
+ * reprogram. Same when a failed resume left the datapath down.
+ */
+ if (!netif_running(dev) || !priv->tx_cbs ||
+ max_t(unsigned int, old_mtu, ETH_DATA_LEN) ==
+ max_t(unsigned int, new_mtu, ETH_DATA_LEN)) {
+ WRITE_ONCE(dev->mtu, new_mtu);
+ priv->rx_buf_len = bcmgenet_rx_buf_len(new_mtu);
+ return 0;
+ }
+
+ ret = bcmgenet_alloc_dma(priv, bcmgenet_rx_buf_len(new_mtu), &set);
+ if (ret)
+ return ret;
+
+ /* The watchdog trips on an idle queue once the rings are gone */
+ netif_device_detach(dev);
+
+ /* The PHY stays up, hold its lock. A link event would otherwise take
+ * the MAC out of reset and enable it mid rewrite.
+ */
+ mutex_lock(&dev->phydev->lock);
+ bcmgenet_netif_stop(dev, 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, &set);
+ if (ret) {
+ /* Take it down here, or close and suspend tear it down twice */
+ netdev_err(dev, "failed to initialize DMA, closing\n");
+ WRITE_ONCE(dev->mtu, old_mtu);
+ priv->rx_buf_len = bcmgenet_rx_buf_len(old_mtu);
+ mutex_unlock(&dev->phydev->lock);
+ netif_close(dev);
+
+ /* __dev_open() refuses a detached device */
+ netif_device_attach(dev);
+ return ret;
+ }
+
+ bcmgenet_hfb_restore(priv);
+ bcmgenet_netif_start(dev, false);
+ mutex_unlock(&dev->phydev->lock);
+
+ /* 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);
+
+ /* Interrupts were off, so a latched link event is lost. Internal PHYs
+ * on v1-v4 are not polled either.
+ */
+ phy_mac_interrupt(dev->phydev);
+
+ netif_device_attach(dev);
+
+ /* Filter updates that came in while the device was detached got
+ * dropped. An IPv6 join does not hold RTNL.
+ */
+ netif_addr_lock_bh(dev);
+ bcmgenet_set_rx_mode(dev);
+ netif_addr_unlock_bh(dev);
+
+ return 0;
+}
+
static const struct net_device_ops bcmgenet_netdev_ops = {
.ndo_open = bcmgenet_open,
.ndo_stop = bcmgenet_close,
@@ -3976,6 +4057,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 */
@@ -4329,8 +4411,14 @@ static int bcmgenet_probe(struct platform_device *pdev)
/* Mii wait queue */
init_waitqueue_head(&priv->wq);
bcmgenet_hfb_init(priv);
+
priv->rx_buf_len = bcmgenet_rx_buf_len(dev->mtu);
priv->tx_thld_len = bcmgenet_tx_pkt_rdy_thld(dev->mtu) * ENET_THLD_UNIT;
+
+ /* v1 cannot program the thresholds, so it stays at the default 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");
--
2.53.0