* [PATCH net-next v4 0/8] net: bcmgenet: support larger MTUs
@ 2026-10-12 9:00 Nicolai Buchwitz
2026-10-12 9:00 ` [PATCH net-next v4 1/8] net: bcmgenet: ring the doorbell when the last frame of a batch is dropped Nicolai Buchwitz
` (7 more replies)
0 siblings, 8 replies; 17+ messages in thread
From: Nicolai Buchwitz @ 2026-10-12 9:00 UTC (permalink / raw)
To: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Dave Stevenson, Justin Chen,
Pierre-Marin Leclercq, Nicolai Buchwitz, Florian Fainelli
Larger MTU aka. jumbo frames are requested often by Raspberry Pi users, but
the hardware has its quirks with larger frames, so it never got done. Dave
Stevenson's PoC [1] had the receive side working and Justin checked the RTL
what the hardware does with long frames, which got me past the rest.
GENET never sets dev->max_mtu, and the packet ready thresholds cut frames
off at their 2048 byte reset default. With this the receive dropped them as
fragmented and transmit never sent them out. See commit messages of the
patches for more details.
Patch 1 fixes a transmit error exit that left queued frames unpublished,
the padding in patch 6 adds three more of them. Patches 2 to 5 and 7
implement the MTU as far as one descriptor reaches (3564 bytes with 4K
pages). Patch 6 pads around a transmit quirk where a frame ending just past
the threshold stops the transmitter [3]. Patch 8 reassembles the fragments
the hardware produces above it, which gets the ceiling to 16347 [2].
Tested on a CM4 (GENET v5). MTU 1500 is unchanged, MTU 9000 reaches 991/987
Mbit/s. Pierre-Marin Leclercq tested on a Pi 4 (also GENET v5) with various
MTU sizes.
[1] https://github.com/raspberrypi/linux/pull/7614
[2] https://github.com/raspberrypi/linux/pull/7617#issuecomment-5669564355
[3] https://github.com/raspberrypi/linux/pull/7617#issuecomment-5735557324
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
Changes in v4:
- patch 5:
- floor UMAC_MAX_FRAME_LEN and the flow period at ETH_DATA_LEN, a lower
MTU narrowed what the MAC accepts (clashiko)
- move the TBUF threshold write to patch 6, where the padding needs it
- patch 6:
- keep the transmit threshold at its reset value up to ETH_DATA_LEN, v3
raised it on every system at the default MTU
- unshare the skb before padding or checksumming, ether_setup() leaves
IFF_TX_SKB_SHARING set
- note the threshold goes by skb->len and the window is reachable only
later in the series (clashiko)
- patch 7:
- skip the restart when both MTUs derive the same values
- clear datapath_up on the suspend teardown (clashiko)
- unwind only the Rx NAPI instances a failed ring init added, the retry
would otherwise re-add a listed NAPI (sashiko)
- note the one descriptor limit is lifted later in the series (clashiko)
- patch 8:
- count a reassembled frame once for DIM, not per descriptor
- require a continuation descriptor to carry payload
- give the measured TX descriptor length
- note the checksum fallback is on transmit
To: Doug Berger <opendmb@gmail.com>
To: Florian Fainelli <florian.fainelli@broadcom.com>
To: Nicolai Buchwitz <nb@tipi-net.de>
To: Broadcom internal kernel review list <bcm-kernel-feedback-list@broadcom.com>
To: Andrew Lunn <andrew+netdev@lunn.ch>
To: "David S. Miller" <davem@davemloft.net>
To: Eric Dumazet <edumazet@kernel.org>
To: Jakub Kicinski <kuba@kernel.org>
To: Paolo Abeni <pabeni@redhat.com>
Cc: Florian Fainelli <f.fainelli@gmail.com>
Cc: netdev@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
v3: https://patch.msgid.link/20261007-nb-genet-mtu-nn-v2-v3-0-74a796c019ce@tipi-net.de
v2: https://patch.msgid.link/20261006-nb-genet-mtu-nn-v2-v2-0-c4be21151912@tipi-net.de
v1: https://patch.msgid.link/20261002-nb-genet-mtu-nn-v2-v1-0-96dc6d54cbee@tipi-net.de
---
Nicolai Buchwitz (8):
net: bcmgenet: ring the doorbell when the last frame of a batch is dropped
net: bcmgenet: let the caller decide whether to start the PHY
net: bcmgenet: allow a continuation descriptor without the alignment pad
net: bcmgenet: rename ENET_MAX_MTU_SIZE to ENET_MAX_FRAME_LEN
net: bcmgenet: derive the receive buffer length from the MTU
net: bcmgenet: pad transmit frames out of the packet ready window
net: bcmgenet: allow the MTU to be changed
net: bcmgenet: reassemble jumbo frames from status block fragments
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 405 ++++++++++++++++++++++---
drivers/net/ethernet/broadcom/genet/bcmgenet.h | 25 +-
2 files changed, 381 insertions(+), 49 deletions(-)
---
base-commit: d8674294aefef02266c4d47ad10131f1bffbe534
change-id: 20261002-nb-genet-mtu-nn-v2-402b5e6f306f
Best regards,
--
Nicolai Buchwitz <nb@tipi-net.de>
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH net-next v4 1/8] net: bcmgenet: ring the doorbell when the last frame of a batch is dropped
2026-10-12 9:00 [PATCH net-next v4 0/8] net: bcmgenet: support larger MTUs Nicolai Buchwitz
@ 2026-10-12 9:00 ` Nicolai Buchwitz
2026-10-12 21:29 ` Florian Fainelli
2026-10-12 9:00 ` [PATCH net-next v4 2/8] net: bcmgenet: let the caller decide whether to start the PHY Nicolai Buchwitz
` (6 subsequent siblings)
7 siblings, 1 reply; 17+ messages in thread
From: Nicolai Buchwitz @ 2026-10-12 9:00 UTC (permalink / raw)
To: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Dave Stevenson, Justin Chen,
Pierre-Marin Leclercq, Nicolai Buchwitz, Florian Fainelli
bcmgenet_xmit() only writes the producer index at the end of a batch. The
error exits return before that write. If the dropped frame was the last of
an xmit_more batch, the frames queued before it stay in the ring until the
next transmit. The queue is still running, so the watchdog does not fire.
Found by a sashiko review, not by a bug report. The existing exits only
trigger on an allocation or DMA mapping failure, and the padding in a later
patch adds three more.
Write the producer index on those exits too.
Fixes: ddd0ca5d60b3 ("net: bcmgenet: add support for xmit_more")
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 25 ++++++++++++++++---------
1 file changed, 16 insertions(+), 9 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 4c9db2f9fc25..06ec8c211dd7 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -2119,6 +2119,15 @@ static void bcmgenet_hide_tsb(struct sk_buff *skb)
__skb_pull(skb, sizeof(struct status_64));
}
+static void bcmgenet_tx_kick(struct bcmgenet_priv *priv,
+ struct bcmgenet_tx_ring *ring,
+ struct netdev_queue *txq)
+{
+ if (!netdev_xmit_more() || netif_xmit_stopped(txq))
+ bcmgenet_tdma_ring_writel(priv, ring->index,
+ ring->prod_index, TDMA_PROD_INDEX);
+}
+
static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct bcmgenet_priv *priv = netdev_priv(dev);
@@ -2155,10 +2164,8 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
/* add the Transmit Status Block */
skb = bcmgenet_add_tsb(dev, skb, ring);
- if (!skb) {
- ret = NETDEV_TX_OK;
- goto out;
- }
+ if (!skb)
+ goto drop;
for (i = 0; i <= nr_frags; i++) {
tx_cb_ptr = bcmgenet_get_txcb(priv, ring);
@@ -2183,7 +2190,6 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
if (ret) {
priv->mib.tx_dma_failed++;
netif_err(priv, tx_err, dev, "Tx DMA map failed\n");
- ret = NETDEV_TX_OK;
goto out_unmap_frags;
}
dma_unmap_addr_set(tx_cb_ptr, dma_addr, mapping);
@@ -2225,10 +2231,7 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
if (ring->free_bds <= (MAX_SKB_FRAGS + 1))
netif_tx_stop_queue(txq);
- if (!netdev_xmit_more() || netif_xmit_stopped(txq))
- /* Packets are ready, update producer index */
- bcmgenet_tdma_ring_writel(priv, ring->index,
- ring->prod_index, TDMA_PROD_INDEX);
+ bcmgenet_tx_kick(priv, ring, txq);
out:
spin_unlock(&ring->lock);
@@ -2245,6 +2248,10 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
}
dev_kfree_skb(skb);
+drop:
+ /* The dropped frame may have been the last one of the batch */
+ bcmgenet_tx_kick(priv, ring, txq);
+ ret = NETDEV_TX_OK;
goto out;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v4 2/8] net: bcmgenet: let the caller decide whether to start the PHY
2026-10-12 9:00 [PATCH net-next v4 0/8] net: bcmgenet: support larger MTUs Nicolai Buchwitz
2026-10-12 9:00 ` [PATCH net-next v4 1/8] net: bcmgenet: ring the doorbell when the last frame of a batch is dropped Nicolai Buchwitz
@ 2026-10-12 9:00 ` Nicolai Buchwitz
2026-10-12 9:00 ` [PATCH net-next v4 3/8] net: bcmgenet: allow a continuation descriptor without the alignment pad Nicolai Buchwitz
` (5 subsequent siblings)
7 siblings, 0 replies; 17+ messages in thread
From: Nicolai Buchwitz @ 2026-10-12 9:00 UTC (permalink / raw)
To: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Dave Stevenson, Justin Chen,
Pierre-Marin Leclercq, Nicolai Buchwitz, Florian Fainelli
bcmgenet_netif_stop() already takes stop_phy, bcmgenet_netif_start() does
not. The MTU change in a later patch leaves the PHY running while the
datapath goes down and comes back, and phy_start() expects a stopped PHY.
Add the same parameter to the start side.
No functional change.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
---
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 06ec8c211dd7..86c8f8f8fe15 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -3355,7 +3355,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);
@@ -3372,7 +3372,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)
@@ -3435,7 +3436,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);
@@ -4319,7 +4320,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);
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v4 3/8] net: bcmgenet: allow a continuation descriptor without the alignment pad
2026-10-12 9:00 [PATCH net-next v4 0/8] net: bcmgenet: support larger MTUs Nicolai Buchwitz
2026-10-12 9:00 ` [PATCH net-next v4 1/8] net: bcmgenet: ring the doorbell when the last frame of a batch is dropped Nicolai Buchwitz
2026-10-12 9:00 ` [PATCH net-next v4 2/8] net: bcmgenet: let the caller decide whether to start the PHY Nicolai Buchwitz
@ 2026-10-12 9:00 ` Nicolai Buchwitz
2026-10-13 9:19 ` netdev-bot+sashiko
2026-10-12 9:00 ` [PATCH net-next v4 4/8] net: bcmgenet: rename ENET_MAX_MTU_SIZE to ENET_MAX_FRAME_LEN Nicolai Buchwitz
` (4 subsequent siblings)
7 siblings, 1 reply; 17+ messages in thread
From: Nicolai Buchwitz @ 2026-10-12 9:00 UTC (permalink / raw)
To: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Dave Stevenson, Justin Chen,
Pierre-Marin Leclercq, Nicolai Buchwitz, Florian Fainelli
A frame longer than the packet ready threshold arrives in several
descriptors, each with its own status block. Only the first one also
carries the two alignment bytes. The length check assumes the pad is always
there, so a continuation holding a single byte looks a byte too short and
the whole frame is dropped.
Account for the pad on the first descriptor only.
The MTU cannot produce a frame past the threshold yet, so nothing hits this
today. It is preparation for the larger MTU.
Tested-by: Pierre-Marin Leclercq <pierremarinleclercq88@gmail.com>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 86c8f8f8fe15..9f9c3fde8725 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -53,7 +53,8 @@
/* Page pool RX buffer layout:
* RSB(64) + pad(2) | frame data | skb_shared_info
- * The HW writes the 64B RSB + 2B alignment padding before the frame.
+ * The HW writes the 64B RSB before every descriptor of a frame. Only the
+ * first one also gets the 2B alignment padding.
*/
#define GENET_RSB_PAD (sizeof(struct status_64) + 2)
@@ -2336,6 +2337,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
unsigned int rx_offset, rx_size;
struct status_64 *status;
struct page *rx_page;
+ unsigned int min_len;
void *hard_start;
__be16 rx_csum;
@@ -2372,8 +2374,12 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
__func__, p_index, ring->c_index,
ring->read_ptr, dma_length_status);
+ /* Only the first descriptor carries the alignment pad */
+ min_len = dma_flag & DMA_SOP ? GENET_RSB_PAD
+ : sizeof(struct status_64);
+
/* Reject lengths that would underflow the SKB build path. */
- if (unlikely(len > RX_BUF_LENGTH || len < GENET_RSB_PAD)) {
+ if (unlikely(len > RX_BUF_LENGTH || len < min_len)) {
netif_err(priv, rx_status, dev,
"invalid packet length %d\n", len);
BCMGENET_STATS64_INC(stats, length_errors);
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v4 4/8] net: bcmgenet: rename ENET_MAX_MTU_SIZE to ENET_MAX_FRAME_LEN
2026-10-12 9:00 [PATCH net-next v4 0/8] net: bcmgenet: support larger MTUs Nicolai Buchwitz
` (2 preceding siblings ...)
2026-10-12 9:00 ` [PATCH net-next v4 3/8] net: bcmgenet: allow a continuation descriptor without the alignment pad Nicolai Buchwitz
@ 2026-10-12 9:00 ` Nicolai Buchwitz
2026-10-12 9:00 ` [PATCH net-next v4 5/8] net: bcmgenet: derive the receive buffer length from the MTU Nicolai Buchwitz
` (3 subsequent siblings)
7 siblings, 0 replies; 17+ messages in thread
From: Nicolai Buchwitz @ 2026-10-12 9:00 UTC (permalink / raw)
To: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Dave Stevenson, Justin Chen,
Pierre-Marin Leclercq, Nicolai Buchwitz, Florian Fainelli
ENET_MAX_MTU_SIZE holds a frame length, not an MTU. Both users program it
into hardware that expects a frame length. The name is wrong once the MTU
is no longer fixed at ETH_DATA_LEN.
No functional change.
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 4 ++--
drivers/net/ethernet/broadcom/genet/bcmgenet.h | 7 +++----
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 9f9c3fde8725..83ffc4a2a2a7 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -2646,7 +2646,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);
@@ -2752,7 +2752,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 86f2aed20dbe..501dd1256693 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -28,12 +28,11 @@
/* 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 + \
+
+/* 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
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v4 5/8] net: bcmgenet: derive the receive buffer length from the MTU
2026-10-12 9:00 [PATCH net-next v4 0/8] net: bcmgenet: support larger MTUs Nicolai Buchwitz
` (3 preceding siblings ...)
2026-10-12 9:00 ` [PATCH net-next v4 4/8] net: bcmgenet: rename ENET_MAX_MTU_SIZE to ENET_MAX_FRAME_LEN Nicolai Buchwitz
@ 2026-10-12 9:00 ` Nicolai Buchwitz
2026-10-12 21:45 ` Florian Fainelli
2026-10-12 9:00 ` [PATCH net-next v4 6/8] net: bcmgenet: pad transmit frames out of the packet ready window Nicolai Buchwitz
` (2 subsequent siblings)
7 siblings, 1 reply; 17+ messages in thread
From: Nicolai Buchwitz @ 2026-10-12 9:00 UTC (permalink / raw)
To: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Dave Stevenson, Justin Chen,
Pierre-Marin Leclercq, Nicolai Buchwitz, Florian Fainelli
The receive buffer length is a fixed 2048 bytes. The packet ready
thresholds keep whatever value the reset left. Neither follows the MTU.
Compute the receive threshold from the MTU and program it into RBUF. The
buffer length follows from it, with the status block on top. The MTU is
still fixed at ETH_DATA_LEN, so the threshold comes out at the reset
default and the buffer only grows by the status block the hardware already
wrote. Derive UMAC_MAX_FRAME_LEN and the flow period from the MTU too, with
a floor at ETH_DATA_LEN so a lower MTU does not stop the MAC accepting a
standard frame.
Suggested-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 85 +++++++++++++++++++++-----
drivers/net/ethernet/broadcom/genet/bcmgenet.h | 18 +++++-
2 files changed, 85 insertions(+), 18 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 83ffc4a2a2a7..9b9ce33f081a 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -48,18 +48,36 @@
#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
+/* RBUF and TBUF hand a frame to the DMA once the threshold is reached. 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
+
/* Page pool RX buffer layout:
* RSB(64) + pad(2) | frame data | skb_shared_info
* The HW writes the 64B RSB before every descriptor of a frame. Only the
* first one also gets the 2B alignment padding.
*/
-#define GENET_RSB_PAD (sizeof(struct status_64) + 2)
+#define GENET_RBUF_ALIGN 2
+#define GENET_RSB_PAD (sizeof(struct status_64) + GENET_RBUF_ALIGN)
-/* RX buffer plus the skb_shared_info napi_build_skb() places behind it */
-#define GENET_RX_BUF_SIZE SKB_HEAD_ALIGN(RX_BUF_LENGTH)
+/* A descriptor never spans more than one page, which also holds
+ * skb_shared_info behind the frame, so on 4K pages the page bounds the
+ * threshold before the register does. Larger pages fit several descriptors.
+ */
+#define ENET_SHINFO_LEN SKB_DATA_ALIGN(sizeof(struct skb_shared_info))
+#define ENET_THLD_PAGE_LEN round_down(PAGE_SIZE - ENET_SHINFO_LEN - \
+ sizeof(struct status_64), \
+ ENET_THLD_BURST)
+#define ENET_THLD_MAX_LEN min_t(unsigned int, \
+ ENET_THLD_MAX * ENET_THLD_UNIT, \
+ ENET_THLD_PAGE_LEN)
/* Tx/Rx DMA register offset, skip 256 descriptors */
#define WORDS_PER_BD(p) (p->hw_params->words_per_bd)
@@ -2260,7 +2278,7 @@ static int bcmgenet_rx_refill(struct bcmgenet_rx_ring *ring,
struct enet_cb *cb)
{
struct bcmgenet_priv *priv = ring->priv;
- unsigned int size = GENET_RX_BUF_SIZE;
+ unsigned int size = SKB_HEAD_ALIGN(priv->rx_buf_len);
unsigned int offset;
dma_addr_t mapping;
struct page *page;
@@ -2275,7 +2293,7 @@ static int bcmgenet_rx_refill(struct bcmgenet_rx_ring *ring,
/* page_pool handles DMA mapping via PP_FLAG_DMA_MAP */
mapping = page_pool_get_dma_addr(page) + offset;
- dma_sync_single_for_device(&priv->pdev->dev, mapping, RX_BUF_LENGTH,
+ dma_sync_single_for_device(&priv->pdev->dev, mapping, priv->rx_buf_len,
DMA_FROM_DEVICE);
cb->rx_page = page;
@@ -2354,10 +2372,10 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
}
/* Sync the full buffer; the HW may have written anywhere
- * up to RX_BUF_LENGTH.
+ * up to priv->rx_buf_len.
*/
page_pool_dma_sync_for_cpu(ring->page_pool, rx_page, rx_offset,
- RX_BUF_LENGTH);
+ priv->rx_buf_len);
hard_start = page_address(rx_page) + rx_offset;
status = (struct status_64 *)hard_start;
@@ -2379,7 +2397,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
: sizeof(struct status_64);
/* Reject lengths that would underflow the SKB build path. */
- if (unlikely(len > RX_BUF_LENGTH || len < min_len)) {
+ if (unlikely(len > priv->rx_buf_len || len < min_len)) {
netif_err(priv, rx_status, dev,
"invalid packet length %d\n", len);
BCMGENET_STATS64_INC(stats, length_errors);
@@ -2630,6 +2648,41 @@ static void bcmgenet_link_intr_enable(struct bcmgenet_priv *priv)
bcmgenet_intrl2_0_writel(priv, int0_enable, INTRL2_CPU_MASK_CLEAR);
}
+/* Receive 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 = GENET_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_LEN / ENET_THLD_UNIT);
+}
+
+/* A buffer has to hold everything the threshold lets the hardware deliver */
+static unsigned int bcmgenet_rx_buf_len(unsigned int mtu)
+{
+ return sizeof(struct status_64) +
+ 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_MAC_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);
+}
+
static void init_umac(struct bcmgenet_priv *priv)
{
struct device *kdev = &priv->pdev->dev;
@@ -2646,7 +2699,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);
@@ -2752,7 +2805,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_MAC_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);
@@ -2762,7 +2815,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,
@@ -2781,8 +2834,9 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv,
static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv,
struct bcmgenet_rx_ring *ring)
{
- /* Buffers share a page. bcmgenet_rx_refill() syncs each one for the
- * device, PP_FLAG_DMA_SYNC_DEV would sync the whole page.
+ /* Buffers may share a page, depending on PAGE_SIZE and the MTU.
+ * bcmgenet_rx_refill() syncs each one for the device,
+ * PP_FLAG_DMA_SYNC_DEV would sync the whole page.
*/
struct page_pool_params pp_params = {
.order = 0,
@@ -2845,7 +2899,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) |
@@ -4114,6 +4168,7 @@ 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);
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 501dd1256693..28395cb72fb6 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -31,9 +31,17 @@
#define ENET_BRCM_TAG_LEN 6
#define ENET_PAD 8
-/* 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. ENET_PAD is slack the
+ * driver has always carried, it rounded the default up to 1536 from 1528.
+ */
+#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)
+
+/* The MAC keeps accepting a standard frame below the default MTU */
+#define ENET_MAC_FRAME_LEN(mtu) ENET_MAX_FRAME_LEN(max_t(unsigned int, (mtu), \
+ ETH_DATA_LEN))
+
#define DMA_MAX_BURST_LENGTH 0x10
/* misc. configuration */
@@ -218,6 +226,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)
@@ -248,6 +258,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)
@@ -612,6 +623,7 @@ struct bcmgenet_priv {
void __iomem *rx_bds;
struct enet_cb *rx_cbs;
unsigned int num_rx_bds;
+ unsigned int rx_buf_len;
struct bcmgenet_rxnfc_rule rxnfc_rules[MAX_NUM_OF_FS_RULES];
struct list_head rxnfc_list;
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v4 6/8] net: bcmgenet: pad transmit frames out of the packet ready window
2026-10-12 9:00 [PATCH net-next v4 0/8] net: bcmgenet: support larger MTUs Nicolai Buchwitz
` (4 preceding siblings ...)
2026-10-12 9:00 ` [PATCH net-next v4 5/8] net: bcmgenet: derive the receive buffer length from the MTU Nicolai Buchwitz
@ 2026-10-12 9:00 ` Nicolai Buchwitz
2026-10-12 21:48 ` Florian Fainelli
2026-10-13 9:19 ` netdev-bot+sashiko
2026-10-12 9:00 ` [PATCH net-next v4 7/8] net: bcmgenet: allow the MTU to be changed Nicolai Buchwitz
2026-10-12 9:00 ` [PATCH net-next v4 8/8] net: bcmgenet: reassemble jumbo frames from status block fragments Nicolai Buchwitz
7 siblings, 2 replies; 17+ messages in thread
From: Nicolai Buchwitz @ 2026-10-12 9:00 UTC (permalink / raw)
To: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Dave Stevenson, Justin Chen,
Pierre-Marin Leclercq, Nicolai Buchwitz, Florian Fainelli
A frame that ends a few bytes past the transmit packet ready threshold
stops the transmitter as soon as a shorter frame follows. Tx DMA then
refuses to halt, so every later bcmgenet_init_dma() fails and the interface
cannot be opened again. IP fragmentation generates that pattern on every
datagram, full frames and a short tail.
The window starts one byte past the threshold and widens with it. On a v5
it ends 28, 32 and 46 bytes past thresholds of 2560, 3584 and 3840. Link
speed makes no difference. Pad frames landing in it to 64 bytes past the
threshold, rounded up from the widest window seen. The threshold goes by
skb->len, it does not count the 64 byte status block the driver prepends
later, measured at 0x80 and 0xf0.
Padding must not push a frame past what the peer accepts. Linux does not
bound how many VLAN tags a frame carries, so measure against the longest
frame the MAC has to accept rather than a tag count. For the MTUs where
such a frame would land in the window, lower the threshold instead. All
other MTUs keep the register maximum, and at or below ETH_DATA_LEN the
reset value stays, so an unchanged MTU keeps the threshold it always had.
Nothing reaches either path until a later patch raises dev->max_mtu.
The MAC inserts a checksum only into a frame it holds in full. A frame
longer than the threshold gets its checksum computed in software.
ether_setup() leaves IFF_TX_SKB_SHARING set, so unshare before either path
writes to the frame.
The padding is appended to the frame, so a protocol that locates data from
the end of it, such as a DSA tail tag or a PRP trailer, sees the zeros
instead. Nothing below an MTU of 3809 is affected, since no frame reaches
the window there. Above it the alternatives are dropping the frame or
leaving the transmitter stalled.
Tested-by: Pierre-Marin Leclercq <pierremarinleclercq88@gmail.com>
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 66 ++++++++++++++++++++++++++
drivers/net/ethernet/broadcom/genet/bcmgenet.h | 1 +
2 files changed, 67 insertions(+)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 9b9ce33f081a..e00661b2414b 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -59,6 +59,11 @@
#define ENET_THLD_DEFAULT 0x80
#define ENET_THLD_MAX 0xf0
+/* A frame ending just past the transmit threshold stops the transmitter once
+ * a shorter frame follows, so pad frames that land there this far past it.
+ */
+#define ENET_TX_SAFE_MARGIN 64
+
/* Page pool RX buffer layout:
* RSB(64) + pad(2) | frame data | skb_shared_info
* The HW writes the 64B RSB before every descriptor of a frame. Only the
@@ -2176,6 +2181,42 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
goto out;
}
+ /* Both paths below write to the frame, which ether_setup() lets the
+ * stack share. Match their conditions so nothing else is copied.
+ */
+ if (unlikely(skb->len > priv->tx_thld_len &&
+ (skb->ip_summed == CHECKSUM_PARTIAL ||
+ skb->len < priv->tx_thld_len + ENET_TX_SAFE_MARGIN))) {
+ skb = skb_share_check(skb, GFP_ATOMIC);
+ if (!skb) {
+ BCMGENET_STATS64_INC((&ring->stats64), dropped);
+ goto drop;
+ }
+ }
+
+ /* The MAC only inserts a checksum into a frame it holds in full, and
+ * silently drops a longer one, so fall back to software.
+ */
+ if (unlikely(skb->len > priv->tx_thld_len) &&
+ skb->ip_summed == CHECKSUM_PARTIAL) {
+ if (skb_checksum_help(skb)) {
+ BCMGENET_STATS64_INC((&ring->stats64), dropped);
+ dev_kfree_skb_any(skb);
+ goto drop;
+ }
+ }
+
+ /* Keep the frame out of the window just past the threshold */
+ if (unlikely(skb->len > priv->tx_thld_len &&
+ skb->len < priv->tx_thld_len + ENET_TX_SAFE_MARGIN)) {
+ if (skb_put_padto(skb, priv->tx_thld_len + ENET_TX_SAFE_MARGIN)) {
+ BCMGENET_STATS64_INC((&ring->stats64), dropped);
+ goto drop;
+ }
+ }
+
+ nr_frags = skb_shinfo(skb)->nr_frags;
+
/* Retain how many bytes will be sent on the wire, without TSB inserted
* by transmit checksum offload
*/
@@ -2662,6 +2703,27 @@ static unsigned int bcmgenet_pkt_rdy_thld(unsigned int mtu)
ENET_THLD_MAX_LEN / ENET_THLD_UNIT);
}
+/* Transmit threshold in register units. Frames landing in the window just
+ * past it are padded clear of it, so pick a threshold that leaves room for
+ * that padding inside the frame the MTU allows. Size the window against the
+ * longest frame the MAC has to accept, since the tag count is not bounded.
+ */
+static unsigned int bcmgenet_tx_pkt_rdy_thld(unsigned int mtu)
+{
+ unsigned int thld = ENET_THLD_MAX;
+
+ /* Keep the reset value while the MTU has no use for more */
+ if (mtu <= ETH_DATA_LEN)
+ return ENET_THLD_DEFAULT;
+
+ while (thld > ENET_THLD_DEFAULT &&
+ ENET_MAX_FRAME_LEN(mtu) - ETH_FCS_LEN > thld * ENET_THLD_UNIT &&
+ thld * ENET_THLD_UNIT + ENET_TX_SAFE_MARGIN > mtu + ETH_HLEN)
+ thld -= ENET_THLD_BURST / ENET_THLD_UNIT;
+
+ return thld;
+}
+
/* A buffer has to hold everything the threshold lets the hardware deliver */
static unsigned int bcmgenet_rx_buf_len(unsigned int mtu)
{
@@ -2672,8 +2734,10 @@ static unsigned int bcmgenet_rx_buf_len(unsigned int mtu)
/* Program the MTU dependent registers. Call with the MAC disabled. */
static void bcmgenet_set_mtu_regs(struct bcmgenet_priv *priv, unsigned int mtu)
{
+ u32 tx_thld = bcmgenet_tx_pkt_rdy_thld(mtu);
u32 thld = bcmgenet_pkt_rdy_thld(mtu);
+ priv->tx_thld_len = tx_thld * ENET_THLD_UNIT;
bcmgenet_umac_writel(priv, ENET_MAC_FRAME_LEN(mtu), UMAC_MAX_FRAME_LEN);
/* GENET v1 maps other registers at these offsets */
@@ -2681,6 +2745,8 @@ static void bcmgenet_set_mtu_regs(struct bcmgenet_priv *priv, unsigned int mtu)
return;
bcmgenet_rbuf_writel(priv, thld, RBUF_PKT_RDY_THLD);
+ bcmgenet_writel(tx_thld, priv->base + priv->hw_params->tbuf_offset +
+ TBUF_PKT_RDY_THLD);
}
static void init_umac(struct bcmgenet_priv *priv)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 28395cb72fb6..7703e1fb73b6 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -624,6 +624,7 @@ struct bcmgenet_priv {
struct enet_cb *rx_cbs;
unsigned int num_rx_bds;
unsigned int rx_buf_len;
+ unsigned int tx_thld_len;
struct bcmgenet_rxnfc_rule rxnfc_rules[MAX_NUM_OF_FS_RULES];
struct list_head rxnfc_list;
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v4 7/8] net: bcmgenet: allow the MTU to be changed
2026-10-12 9:00 [PATCH net-next v4 0/8] net: bcmgenet: support larger MTUs Nicolai Buchwitz
` (5 preceding siblings ...)
2026-10-12 9:00 ` [PATCH net-next v4 6/8] net: bcmgenet: pad transmit frames out of the packet ready window Nicolai Buchwitz
@ 2026-10-12 9:00 ` Nicolai Buchwitz
2026-10-12 21:38 ` Florian Fainelli
2026-10-13 9:19 ` netdev-bot+sashiko
2026-10-12 9:00 ` [PATCH net-next v4 8/8] net: bcmgenet: reassemble jumbo frames from status block fragments Nicolai Buchwitz
7 siblings, 2 replies; 17+ messages in thread
From: Nicolai Buchwitz @ 2026-10-12 9:00 UTC (permalink / raw)
To: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Dave Stevenson, Justin Chen,
Pierre-Marin Leclercq, Nicolai Buchwitz, Florian Fainelli
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.
Resize the buffers and rewrite the registers in place. The PHY keeps
running and the link stays up. Two MTUs at or below ETH_DATA_LEN derive the
same values, so a change between them skips the restart.
A failed allocation retries at the previous size. If that fails too, take
the interface down. Running on rings that were never allocated is worse.
Suggested-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
Link: https://github.com/raspberrypi/linux/issues/5561
Tested-by: Pierre-Marin Leclercq <pierremarinleclercq88@gmail.com>
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 97 +++++++++++++++++++++++++-
drivers/net/ethernet/broadcom/genet/bcmgenet.h | 1 +
2 files changed, 96 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index e00661b2414b..f158d6bc314b 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))
@@ -3179,8 +3185,12 @@ static int bcmgenet_init_rx_queues(struct net_device *dev)
/* Initialize Rx priority queues */
for (i = 0; i <= priv->hw_params->rx_queues; i++) {
ret = bcmgenet_init_rx_ring(priv, i, end - start, start, end);
- if (ret)
+ if (ret) {
+ /* Only the rings before this one hold a NAPI */
+ while (i--)
+ netif_napi_del(&priv->rx_rings[i].napi);
return ret;
+ }
start = end;
end += priv->hw_params->rx_bds_per_q;
@@ -3564,6 +3574,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;
@@ -3622,7 +3633,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);
@@ -3870,6 +3885,76 @@ 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;
+
+ /* Every MTU dependent value is floored at ETH_DATA_LEN, so a change
+ * below it reprograms nothing and must not cost queued frames.
+ */
+ if (!netif_running(dev) ||
+ 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;
+ }
+
+ /* 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);
+
+ /* A link event latched while the interrupts were off is gone. Internal
+ * PHYs on GENET v1-v4 are not polled, so resync the state machine.
+ */
+ phy_mac_interrupt(dev->phydev);
+
+ 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,
@@ -3881,6 +3966,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 */
@@ -4234,7 +4320,11 @@ static int bcmgenet_probe(struct platform_device *pdev)
/* Mii wait queue */
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");
@@ -4418,6 +4508,7 @@ static int bcmgenet_resume(struct device *d)
bcmgenet_rdma_disable(priv);
bcmgenet_intr_disable(priv);
bcmgenet_fini_dma(priv);
+ priv->datapath_up = false;
enable_irq(priv->irq1);
}
@@ -4449,6 +4540,7 @@ static int bcmgenet_resume(struct device *d)
bcmgenet_netif_start(dev, true);
+ priv->datapath_up = true;
netif_device_attach(dev);
return 0;
@@ -4510,6 +4602,7 @@ static int bcmgenet_suspend(struct device *d)
} else {
/* Teardown the interface */
bcmgenet_netif_stop(dev, true);
+ priv->datapath_up = false;
}
return 0;
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 7703e1fb73b6..13c3eb97d84b 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -636,6 +636,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;
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH net-next v4 8/8] net: bcmgenet: reassemble jumbo frames from status block fragments
2026-10-12 9:00 [PATCH net-next v4 0/8] net: bcmgenet: support larger MTUs Nicolai Buchwitz
` (6 preceding siblings ...)
2026-10-12 9:00 ` [PATCH net-next v4 7/8] net: bcmgenet: allow the MTU to be changed Nicolai Buchwitz
@ 2026-10-12 9:00 ` Nicolai Buchwitz
2026-10-12 21:49 ` Florian Fainelli
7 siblings, 1 reply; 17+ messages in thread
From: Nicolai Buchwitz @ 2026-10-12 9:00 UTC (permalink / raw)
To: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Dave Stevenson, Justin Chen,
Pierre-Marin Leclercq, Nicolai Buchwitz, Florian Fainelli
The hardware does not truncate a frame longer than the packet ready
threshold. It splits the frame across descriptors and writes a status block
at the start of each one. The first fragment then arrives with SOP and no
EOP and is dropped as fragmented. This caps the MTU.
Strip the status blocks and reassemble the fragments. Only the last block
holds the checksum of the whole frame. Broadcom confirmed from the RTL that
every GENET revision splits long frames this way, not just the v5 this was
tested on.
On transmit the MAC only checksums a frame it holds in full, so anything
longer than the threshold falls back to software. At jumbo sizes the larger
frame saves more per packet overhead than the checksum costs.
UMAC_MAX_FRAME_LEN is 14 bit and counts the FCS. That puts the maximum MTU
at 16347.
The TX descriptor length field is wider than DMA_BUFLENGTH_MASK suggests,
that mask only extracts the length from the RX status word. A frame of
16361 bytes, the largest that MTU allows, came back byte exact 200 times
over UMAC loopback.
Suggested-by: Justin Chen <justin.chen@broadcom.com>
Tested-by: Pierre-Marin Leclercq <pierremarinleclercq88@gmail.com>
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 139 ++++++++++++++++++++-----
drivers/net/ethernet/broadcom/genet/bcmgenet.h | 2 +
2 files changed, 116 insertions(+), 25 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index f158d6bc314b..6455519e81be 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -84,11 +84,8 @@
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)
+/* UMAC_MAX_FRAME_LEN is 14 bits wide and counts the FCS */
+#define ENET_MAX_JUMBO_MTU (GENMASK(13, 0) - ENET_FRAME_OVERHEAD)
/* Tx/Rx DMA register offset, skip 256 descriptors */
#define WORDS_PER_BD(p) (p->hw_params->words_per_bd)
@@ -2200,8 +2197,8 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
}
}
- /* The MAC only inserts a checksum into a frame it holds in full, and
- * silently drops a longer one, so fall back to software.
+ /* The MAC holds a frame to insert its checksum, but only as much as
+ * its FIFO takes. Longer frames are dropped silently.
*/
if (unlikely(skb->len > priv->tx_thld_len) &&
skb->ip_summed == CHECKSUM_PARTIAL) {
@@ -2351,6 +2348,54 @@ static int bcmgenet_rx_refill(struct bcmgenet_rx_ring *ring,
return 0;
}
+/* Drop the frame being collected. Its remaining descriptors carry no SOP,
+ * so they are dropped quietly until the next one does.
+ */
+static void bcmgenet_discard_frags(struct bcmgenet_rx_ring *ring)
+{
+ ring->frag_drop = true;
+
+ if (!ring->frag_head)
+ return;
+
+ dev_kfree_skb_any(ring->frag_head);
+ ring->frag_head = NULL;
+}
+
+/* A frame longer than the threshold arrives in several descriptors, each with
+ * its own status block. Only the first one carries a header, so hand the page
+ * of every later one to the frame already being collected. Returns the frame
+ * once EOP is in, NULL while more descriptors are expected or once the frame
+ * had to be dropped.
+ */
+static struct sk_buff *bcmgenet_add_frag(struct bcmgenet_rx_ring *ring,
+ struct page *page,
+ unsigned int offset,
+ unsigned int size,
+ unsigned int dma_flag,
+ unsigned int len)
+{
+ struct sk_buff *head = ring->frag_head;
+
+ if (unlikely(skb_shinfo(head)->nr_frags >= MAX_SKB_FRAGS)) {
+ BCMGENET_STATS64_INC((&ring->stats64), fragmented_errors);
+ bcmgenet_discard_frags(ring);
+ page_pool_put_full_page(ring->page_pool, page, true);
+ return NULL;
+ }
+
+ skb_add_rx_frag(head, skb_shinfo(head)->nr_frags, page,
+ offset + sizeof(struct status_64),
+ len - sizeof(struct status_64), size);
+
+ if (!(dma_flag & DMA_EOP))
+ return NULL;
+
+ ring->frag_head = NULL;
+
+ return head;
+}
+
/* bcmgenet_desc_rx - descriptor based rx process.
* this could be called from bottom half, or from NAPI polling method.
*/
@@ -2368,6 +2413,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
unsigned int rxpktprocessed = 0, rxpkttoprocess;
unsigned int bytes_processed = 0;
unsigned int p_index, mask;
+ unsigned int rxframes = 0;
unsigned int discards;
/* Clear status before servicing to reduce spurious interrupts */
@@ -2415,6 +2461,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
if (bcmgenet_rx_refill(ring, cb)) {
BCMGENET_STATS64_INC(stats, dropped);
+ bcmgenet_discard_frags(ring);
goto next;
}
@@ -2439,24 +2486,42 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
__func__, p_index, ring->c_index,
ring->read_ptr, dma_length_status);
- /* Only the first descriptor carries the alignment pad */
- min_len = dma_flag & DMA_SOP ? GENET_RSB_PAD
- : sizeof(struct status_64);
+ /* Only the first descriptor carries the alignment pad. A head
+ * with more to come must hold the Ethernet header, and a
+ * continuation must hold payload.
+ */
+ if (dma_flag & DMA_SOP) {
+ min_len = GENET_RSB_PAD;
+ if (!(dma_flag & DMA_EOP))
+ min_len += ETH_HLEN;
+ } else {
+ min_len = sizeof(struct status_64) + 1;
+ }
/* Reject lengths that would underflow the SKB build path. */
if (unlikely(len > priv->rx_buf_len || len < min_len)) {
netif_err(priv, rx_status, dev,
"invalid packet length %d\n", len);
BCMGENET_STATS64_INC(stats, length_errors);
+ bcmgenet_discard_frags(ring);
page_pool_put_full_page(ring->page_pool, rx_page,
true);
goto next;
}
- if (unlikely(!(dma_flag & DMA_EOP) || !(dma_flag & DMA_SOP))) {
- netif_err(priv, rx_status, dev,
- "dropping fragmented packet!\n");
- BCMGENET_STATS64_INC(stats, fragmented_errors);
+ /* 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);
+ }
+ ring->frag_drop = false;
+ } else if (unlikely(!ring->frag_head)) {
+ /* Rest of a dropped frame, or no SOP seen yet */
+ if (!ring->frag_drop) {
+ BCMGENET_STATS64_INC(stats, fragmented_errors);
+ ring->frag_drop = true;
+ }
page_pool_put_full_page(ring->page_pool, rx_page,
true);
goto next;
@@ -2486,17 +2551,27 @@ 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);
page_pool_put_full_page(ring->page_pool, rx_page,
true);
goto next;
} /* error packet */
+ if (!(dma_flag & DMA_SOP)) {
+ skb = bcmgenet_add_frag(ring, rx_page, rx_offset,
+ rx_size, dma_flag, len);
+ if (!skb)
+ goto next;
+ goto deliver;
+ }
+
/* Build SKB from the page - data starts at hard_start,
* frame begins after RSB(64) + pad(2) = 66 bytes.
*/
skb = napi_build_skb(hard_start, rx_size);
if (unlikely(!skb)) {
BCMGENET_STATS64_INC(stats, dropped);
+ bcmgenet_discard_frags(ring);
page_pool_put_full_page(ring->page_pool, rx_page,
true);
goto next;
@@ -2508,17 +2583,26 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
skb_reserve(skb, GENET_RSB_PAD);
__skb_put(skb, len - GENET_RSB_PAD);
- if (priv->crc_fwd_en) {
- skb_trim(skb, skb->len - ETH_FCS_LEN);
+ if (unlikely(!(dma_flag & DMA_EOP))) {
+ ring->frag_head = skb;
+ goto next;
+ }
+
+deliver:
+ /* The FCS trim may release the page with the last status block */
+ rx_csum = (__force __be16)(status->rx_csum & 0xffff);
+
+ if (priv->crc_fwd_en &&
+ unlikely(pskb_trim(skb, skb->len - ETH_FCS_LEN))) {
+ BCMGENET_STATS64_INC(stats, dropped);
+ dev_kfree_skb_any(skb);
+ goto next;
}
/* Set up checksum offload */
- 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 ((dev->features & NETIF_F_RXCSUM) && rx_csum) {
+ skb->csum = (__force __wsum)ntohs(rx_csum);
+ skb->ip_summed = CHECKSUM_COMPLETE;
}
len = skb->len;
@@ -2526,6 +2610,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
/*Finish setting up the received SKB and send it to the kernel*/
skb->protocol = eth_type_trans(skb, priv->dev);
+ rxframes++;
u64_stats_update_begin(&stats->syncp);
u64_stats_inc(&stats->packets);
@@ -2552,7 +2637,8 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
}
ring->dim.bytes = bytes_processed;
- ring->dim.packets = rxpktprocessed;
+ /* A long frame spans several descriptors but is one packet */
+ ring->dim.packets = rxframes;
return rxpktprocessed;
}
@@ -2626,6 +2712,8 @@ static void bcmgenet_free_rx_buffers(struct bcmgenet_priv *priv)
cb = ring->cbs + i;
bcmgenet_free_rx_cb(cb, ring->page_pool);
}
+ /* a partial frame still holds pages of this pool */
+ bcmgenet_discard_frags(ring);
}
}
@@ -2696,7 +2784,8 @@ static void bcmgenet_link_intr_enable(struct bcmgenet_priv *priv)
}
/* Receive threshold in register units. Covers the alignment bytes and the
- * frame, but not the status block, which the hardware adds on top.
+ * frame up to the register maximum, but not the status block, which the
+ * hardware adds on top. A longer frame arrives in several descriptors.
*/
static unsigned int bcmgenet_pkt_rdy_thld(unsigned int mtu)
{
@@ -4324,7 +4413,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 13c3eb97d84b..9c2c21081736 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -585,6 +585,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 */
+ bool frag_drop; /* discarding until the next SOP */
struct bcmgenet_net_dim dim;
u32 rx_max_coalesced_frames;
u32 rx_coalesce_usecs;
--
2.53.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v4 1/8] net: bcmgenet: ring the doorbell when the last frame of a batch is dropped
2026-10-12 9:00 ` [PATCH net-next v4 1/8] net: bcmgenet: ring the doorbell when the last frame of a batch is dropped Nicolai Buchwitz
@ 2026-10-12 21:29 ` Florian Fainelli
0 siblings, 0 replies; 17+ messages in thread
From: Florian Fainelli @ 2026-10-12 21:29 UTC (permalink / raw)
To: Nicolai Buchwitz, Doug Berger,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Dave Stevenson, Justin Chen,
Pierre-Marin Leclercq, Florian Fainelli
On 10/12/26 02:00, Nicolai Buchwitz wrote:
> bcmgenet_xmit() only writes the producer index at the end of a batch. The
> error exits return before that write. If the dropped frame was the last of
> an xmit_more batch, the frames queued before it stay in the ring until the
> next transmit. The queue is still running, so the watchdog does not fire.
>
> Found by a sashiko review, not by a bug report. The existing exits only
> trigger on an allocation or DMA mapping failure, and the padding in a later
> patch adds three more.
>
> Write the producer index on those exits too.
>
> Fixes: ddd0ca5d60b3 ("net: bcmgenet: add support for xmit_more")
> Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
--
Florian
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v4 7/8] net: bcmgenet: allow the MTU to be changed
2026-10-12 9:00 ` [PATCH net-next v4 7/8] net: bcmgenet: allow the MTU to be changed Nicolai Buchwitz
@ 2026-10-12 21:38 ` Florian Fainelli
2026-10-13 9:19 ` netdev-bot+sashiko
1 sibling, 0 replies; 17+ messages in thread
From: Florian Fainelli @ 2026-10-12 21:38 UTC (permalink / raw)
To: Nicolai Buchwitz, Doug Berger,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Dave Stevenson, Justin Chen,
Pierre-Marin Leclercq, Florian Fainelli
On 10/12/26 02:00, Nicolai Buchwitz wrote:
> 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.
>
> Resize the buffers and rewrite the registers in place. The PHY keeps
> running and the link stays up. Two MTUs at or below ETH_DATA_LEN derive the
> same values, so a change between them skips the restart.
>
> A failed allocation retries at the previous size. If that fails too, take
> the interface down. Running on rings that were never allocated is worse.
>
> Suggested-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
> Link: https://github.com/raspberrypi/linux/issues/5561
> Tested-by: Pierre-Marin Leclercq <pierremarinleclercq88@gmail.com>
> Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
--
Florian
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v4 5/8] net: bcmgenet: derive the receive buffer length from the MTU
2026-10-12 9:00 ` [PATCH net-next v4 5/8] net: bcmgenet: derive the receive buffer length from the MTU Nicolai Buchwitz
@ 2026-10-12 21:45 ` Florian Fainelli
0 siblings, 0 replies; 17+ messages in thread
From: Florian Fainelli @ 2026-10-12 21:45 UTC (permalink / raw)
To: Nicolai Buchwitz, Doug Berger,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Dave Stevenson, Justin Chen,
Pierre-Marin Leclercq, Florian Fainelli
On 10/12/26 02:00, Nicolai Buchwitz wrote:
> The receive buffer length is a fixed 2048 bytes. The packet ready
> thresholds keep whatever value the reset left. Neither follows the MTU.
>
> Compute the receive threshold from the MTU and program it into RBUF. The
> buffer length follows from it, with the status block on top. The MTU is
> still fixed at ETH_DATA_LEN, so the threshold comes out at the reset
> default and the buffer only grows by the status block the hardware already
> wrote. Derive UMAC_MAX_FRAME_LEN and the flow period from the MTU too, with
> a floor at ETH_DATA_LEN so a lower MTU does not stop the MAC accepting a
> standard frame.
>
> Suggested-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
> Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
--
Florian
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v4 6/8] net: bcmgenet: pad transmit frames out of the packet ready window
2026-10-12 9:00 ` [PATCH net-next v4 6/8] net: bcmgenet: pad transmit frames out of the packet ready window Nicolai Buchwitz
@ 2026-10-12 21:48 ` Florian Fainelli
2026-10-13 9:19 ` netdev-bot+sashiko
1 sibling, 0 replies; 17+ messages in thread
From: Florian Fainelli @ 2026-10-12 21:48 UTC (permalink / raw)
To: Nicolai Buchwitz, Doug Berger,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Dave Stevenson, Justin Chen,
Pierre-Marin Leclercq, Florian Fainelli
On 10/12/26 02:00, Nicolai Buchwitz wrote:
> A frame that ends a few bytes past the transmit packet ready threshold
> stops the transmitter as soon as a shorter frame follows. Tx DMA then
> refuses to halt, so every later bcmgenet_init_dma() fails and the interface
> cannot be opened again. IP fragmentation generates that pattern on every
> datagram, full frames and a short tail.
>
> The window starts one byte past the threshold and widens with it. On a v5
> it ends 28, 32 and 46 bytes past thresholds of 2560, 3584 and 3840. Link
> speed makes no difference. Pad frames landing in it to 64 bytes past the
> threshold, rounded up from the widest window seen. The threshold goes by
> skb->len, it does not count the 64 byte status block the driver prepends
> later, measured at 0x80 and 0xf0.
>
> Padding must not push a frame past what the peer accepts. Linux does not
> bound how many VLAN tags a frame carries, so measure against the longest
> frame the MAC has to accept rather than a tag count. For the MTUs where
> such a frame would land in the window, lower the threshold instead. All
> other MTUs keep the register maximum, and at or below ETH_DATA_LEN the
> reset value stays, so an unchanged MTU keeps the threshold it always had.
> Nothing reaches either path until a later patch raises dev->max_mtu.
>
> The MAC inserts a checksum only into a frame it holds in full. A frame
> longer than the threshold gets its checksum computed in software.
> ether_setup() leaves IFF_TX_SKB_SHARING set, so unshare before either path
> writes to the frame.
>
> The padding is appended to the frame, so a protocol that locates data from
> the end of it, such as a DSA tail tag or a PRP trailer, sees the zeros
> instead. Nothing below an MTU of 3809 is affected, since no frame reaches
> the window there. Above it the alternatives are dropping the frame or
> leaving the transmitter stalled.
>
> Tested-by: Pierre-Marin Leclercq <pierremarinleclercq88@gmail.com>
> Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
--
Florian
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v4 8/8] net: bcmgenet: reassemble jumbo frames from status block fragments
2026-10-12 9:00 ` [PATCH net-next v4 8/8] net: bcmgenet: reassemble jumbo frames from status block fragments Nicolai Buchwitz
@ 2026-10-12 21:49 ` Florian Fainelli
0 siblings, 0 replies; 17+ messages in thread
From: Florian Fainelli @ 2026-10-12 21:49 UTC (permalink / raw)
To: Nicolai Buchwitz, Doug Berger,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Dave Stevenson, Justin Chen,
Pierre-Marin Leclercq, Florian Fainelli
On 10/12/26 02:00, Nicolai Buchwitz wrote:
> The hardware does not truncate a frame longer than the packet ready
> threshold. It splits the frame across descriptors and writes a status block
> at the start of each one. The first fragment then arrives with SOP and no
> EOP and is dropped as fragmented. This caps the MTU.
>
> Strip the status blocks and reassemble the fragments. Only the last block
> holds the checksum of the whole frame. Broadcom confirmed from the RTL that
> every GENET revision splits long frames this way, not just the v5 this was
> tested on.
>
> On transmit the MAC only checksums a frame it holds in full, so anything
> longer than the threshold falls back to software. At jumbo sizes the larger
> frame saves more per packet overhead than the checksum costs.
>
> UMAC_MAX_FRAME_LEN is 14 bit and counts the FCS. That puts the maximum MTU
> at 16347.
>
> The TX descriptor length field is wider than DMA_BUFLENGTH_MASK suggests,
> that mask only extracts the length from the RX status word. A frame of
> 16361 bytes, the largest that MTU allows, came back byte exact 200 times
> over UMAC loopback.
>
> Suggested-by: Justin Chen <justin.chen@broadcom.com>
> Tested-by: Pierre-Marin Leclercq <pierremarinleclercq88@gmail.com>
> Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
--
Florian
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v4 3/8] net: bcmgenet: allow a continuation descriptor without the alignment pad
2026-10-12 9:00 ` [PATCH net-next v4 3/8] net: bcmgenet: allow a continuation descriptor without the alignment pad Nicolai Buchwitz
@ 2026-10-13 9:19 ` netdev-bot+sashiko
0 siblings, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-10-13 9:19 UTC (permalink / raw)
To: nb
Cc: opendmb, florian.fainelli, bcm-kernel-feedback-list,
andrew+netdev, davem, edumazet, kuba, pabeni, netdev,
linux-kernel, dave.stevenson, justin.chen, pierremarinleclercq88,
f.fainelli
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] Commit message accuracy / dead code at this point in the series
(baseline verdict: discard, resolved later in the series).
--
net: bcmgenet: allow a continuation descriptor without the alignment pad
This changes the RX length check in bcmgenet_desc_rx() so that only a
descriptor with DMA_SOP has to cover the 2 byte alignment pad. A
continuation descriptor now only has to cover the 64 byte status block.
The commit message says this prepares for a larger MTU.
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> index 86c8f8f8fe151..9f9c3fde87250 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
[ ... ]
> @@ -2372,8 +2374,12 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
> __func__, p_index, ring->c_index,
> ring->read_ptr, dma_length_status);
>
> + /* Only the first descriptor carries the alignment pad */
> + min_len = dma_flag & DMA_SOP ? GENET_RSB_PAD
> + : sizeof(struct status_64);
> +
> /* Reject lengths that would underflow the SKB build path. */
> - if (unlikely(len > RX_BUF_LENGTH || len < GENET_RSB_PAD)) {
> + if (unlikely(len > RX_BUF_LENGTH || len < min_len)) {
> netif_err(priv, rx_status, dev,
> "invalid packet length %d\n", len);
> BCMGENET_STATS64_INC(stats, length_errors);
[Severity: Low]
At this commit, does the relaxed bound change which frames are accepted?
Right after the length check, bcmgenet_desc_rx() still has this
unchanged check:
if (unlikely(!(dma_flag & DMA_EOP) || !(dma_flag & DMA_SOP))) {
netif_err(priv, rx_status, dev,
"dropping fragmented packet!\n");
BCMGENET_STATS64_INC(stats, fragmented_errors);
...
goto next;
}
At this point in the series the driver has no frag_head or
bcmgenet_add_frag() reassembly. So every descriptor without DMA_SOP is
still dropped, whatever the MTU.
The only visible change is in the accounting. A non-SOP descriptor with
len 64 or 65 now increments fragmented_errors and logs "dropping
fragmented packet!". Before this patch it incremented length_errors and
logged "invalid packet length".
The commit message says:
"The MTU cannot produce a frame past the threshold yet, so nothing
hits this today. It is preparation for the larger MTU."
Would it be more accurate to say this depends on the reassembly patch
rather than on the MTU change? A larger MTU alone would still hit the
fragmented check above.
The new bound also accepts len == 64 for a continuation, which is a
descriptor with no payload. The commit message only argues for the
single byte (len == 65) case.
Both points are handled later in the series. "net: bcmgenet: reassemble
jumbo frames from status block fragments" adds the reassembly path and
rewrites this logic. The non-SOP minimum becomes
sizeof(struct status_64) + 1, and a SOP descriptor without EOP gets
min_len += ETH_HLEN.
Would it make sense to fold this change into that patch, or to reword
the commit message so the dependency is clear?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261012-nb-genet-mtu-nn-v2-v4-0-2e2e3bfaf8cc%40tipi-net.de
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v4 6/8] net: bcmgenet: pad transmit frames out of the packet ready window
2026-10-12 9:00 ` [PATCH net-next v4 6/8] net: bcmgenet: pad transmit frames out of the packet ready window Nicolai Buchwitz
2026-10-12 21:48 ` Florian Fainelli
@ 2026-10-13 9:19 ` netdev-bot+sashiko
1 sibling, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-10-13 9:19 UTC (permalink / raw)
To: nb
Cc: opendmb, florian.fainelli, bcm-kernel-feedback-list,
andrew+netdev, davem, edumazet, kuba, pabeni, netdev,
linux-kernel, dave.stevenson, justin.chen, pierremarinleclercq88,
f.fainelli
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The new field tx_thld_len is used only on transmit: it is set in
bcmgenet_set_mtu_regs() and read in bcmgenet_xmit().
--
net: bcmgenet: pad transmit frames out of the packet ready window
The commit programs a transmit packet ready threshold that depends on the
MTU. Frames that land just past that threshold are padded so they no
longer stall the transmitter. A frame longer than the threshold gets its
checksum computed in software, and the skb is unshared before either path
writes to it.
[ ... ]
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
> index 28395cb72fb60..7703e1fb73b6c 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
> @@ -624,6 +624,7 @@ struct bcmgenet_priv {
> struct enet_cb *rx_cbs;
> unsigned int num_rx_bds;
> unsigned int rx_buf_len;
> + unsigned int tx_thld_len;
[Severity: Low]
This isn't a bug, but tx_thld_len only matters on transmit. It is set in
bcmgenet_set_mtu_regs() and read in bcmgenet_xmit().
In struct bcmgenet_priv it sits under the "/* receive variables */" comment,
next to rx_buf_len:
/* receive variables */
void __iomem *rx_bds;
struct enet_cb *rx_cbs;
unsigned int num_rx_bds;
unsigned int rx_buf_len;
unsigned int tx_thld_len;
Could it go under the "/* transmit variables */" block with num_tx_bds?
> struct bcmgenet_rxnfc_rule rxnfc_rules[MAX_NUM_OF_FS_RULES];
> struct list_head rxnfc_list;
>
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261012-nb-genet-mtu-nn-v2-v4-0-2e2e3bfaf8cc%40tipi-net.de
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH net-next v4 7/8] net: bcmgenet: allow the MTU to be changed
2026-10-12 9:00 ` [PATCH net-next v4 7/8] net: bcmgenet: allow the MTU to be changed Nicolai Buchwitz
2026-10-12 21:38 ` Florian Fainelli
@ 2026-10-13 9:19 ` netdev-bot+sashiko
1 sibling, 0 replies; 17+ messages in thread
From: netdev-bot+sashiko @ 2026-10-13 9:19 UTC (permalink / raw)
To: nb
Cc: opendmb, florian.fainelli, bcm-kernel-feedback-list,
andrew+netdev, davem, edumazet, kuba, pabeni, netdev,
linux-kernel, dave.stevenson, justin.chen, pierremarinleclercq88,
f.fainelli
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 0 (1) · Medium: 2 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] An RX filter update can be lost during the MTU change.
- [Medium] The live MTU change leaves the PHY state machine running
(bcmgenet_netif_stop(dev, false)), but nothing stops the PHY link…
- [Low] The patch fixes two existing bugs, but neither the commit message
nor a Fixes: tag mentions them.
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the patch adds
priv->datapath_up and leaves this path unguarded.
--
net: bcmgenet: allow the MTU to be changed
This commit sets dev->max_mtu for GENET v2 and later, derived from the
packet ready threshold and the one page descriptor limit. It adds an
ndo_change_mtu that rebuilds the DMA rings and MTU registers in place while
the PHY keeps running, and retries the old MTU if allocation fails. A new
priv->datapath_up flag tracks whether the datapath has already been torn
down.
Link: https://github.com/raspberrypi/linux/issues/5561
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> index e00661b2414b9..f158d6bc314b9 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
[ ... ]
> @@ -3179,8 +3185,12 @@ static int bcmgenet_init_rx_queues(struct net_device *dev)
> /* Initialize Rx priority queues */
> for (i = 0; i <= priv->hw_params->rx_queues; i++) {
> ret = bcmgenet_init_rx_ring(priv, i, end - start, start, end);
> - if (ret)
> + if (ret) {
> + /* Only the rings before this one hold a NAPI */
> + while (i--)
> + netif_napi_del(&priv->rx_rings[i].napi);
> return ret;
> + }
[Severity: Low]
This isn't a bug, but this unwind and the new priv->datapath_up handling in
bcmgenet_close(), bcmgenet_suspend() and bcmgenet_resume() look like they
also fix two existing problems. The commit message doesn't mention either
of them.
Before this patch, if bcmgenet_init_rx_ring() failed for ring i, the NAPIs
of rings 0..i-1 stayed listed after a failed bcmgenet_open() or
bcmgenet_resume(). The next netif_napi_add() would then hit the
already-listed warning.
Also, bcmgenet_close() used to call bcmgenet_netif_stop() unconditionally.
A close after a failed resume would call napi_disable() on NAPIs that were
already disabled during suspend, and then kfree() rx_cbs/tx_cbs a second
time. That path has nothing to do with the MTU, but the new comment in
bcmgenet_close() only says:
/* A failed MTU change can have torn the datapath down already */
Could these fixes be split into separate patches with Fixes: tags so they
can go to net and stable? The rx unwind is needed for the MTU retry, which
calls bcmgenet_init_dma() twice in a row, so it could come first in the
series.
[ ... ]
> @@ -3622,7 +3633,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;
> + }
[ ... ]
> +static int bcmgenet_change_mtu(struct net_device *dev, int new_mtu)
> +{
[ ... ]
> + /* 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);
[Severity: Medium]
Can the first link-up enable the MAC again in the middle of this
reconfiguration?
After ifup and before the first link-up, UMAC_CMD still has CMD_SW_RESET
set from init_umac()->reset_umac(). In that state umac_enable_set() returns
without writing anything:
drivers/net/ethernet/broadcom/genet/bcmgenet.c:umac_enable_set() {
...
if (reg & CMD_SW_RESET) {
spin_unlock_bh(&priv->reg_lock);
return;
}
...
}
So the MAC disable inside bcmgenet_netif_stop() has no effect.
The PHY state machine keeps running here. A link-up in this window runs
bcmgenet_mii_setup()->bcmgenet_mac_config() under phydev->lock, not RTNL:
drivers/net/ethernet/broadcom/genet/bcmmii.c:bcmgenet_mac_config() {
...
if (reg & CMD_SW_RESET) {
reg &= ~CMD_SW_RESET;
bcmgenet_umac_writel(priv, reg, UMAC_CMD);
udelay(2);
reg |= CMD_TX_EN | CMD_RX_EN;
}
...
}
The MAC would then be enabled while bcmgenet_set_mtu_regs() rewrites
UMAC_MAX_FRAME_LEN and the RBUF/TBUF thresholds. That function's own
comment says "Call with the MAC disabled". It would also be enabled while
bcmgenet_init_dma(priv, true) flushes the RBUF and rebuilds the rings.
Open and resume program these registers before phy_start(), so only the
new path is exposed. The window includes the 10-20 ms sleep in the DMA
teardown, so a network manager that sets the MTU right after ifup could
hit it. Does anything prevent this?
> +
> + ret = bcmgenet_init_dma(priv, true);
[ ... ]
> + 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);
> +
> + /* A link event latched while the interrupts were off is gone. Internal
> + * PHYs on GENET v1-v4 are not polled, so resync the state machine.
> + */
> + phy_mac_interrupt(dev->phydev);
> +
> + priv->datapath_up = true;
> + netif_device_attach(dev);
[Severity: Medium]
Can a concurrent RX address list update get lost here?
bcmgenet_netif_start() programs the RX filters through
bcmgenet_set_rx_mode() under netif_addr_lock_bh() and then drops the lock.
The device stays detached until the netif_device_attach() above. A unicast
or multicast list change in that window reaches __dev_set_rx_mode(), which
drops it:
net/core/dev_addr_lists.c:__dev_set_rx_mode() {
...
if (!netif_device_present(dev))
return;
...
}
netif_device_attach() doesn't resync the RX mode, and bcmgenet has no
ndo_change_rx_flags, so nothing queues the update for later.
RTNL is held here. However, IPV6_ADD_MEMBERSHIP
(ipv6_sock_mc_join()->__ipv6_dev_mc_inc()->dev_mc_add()) runs under the
socket lock and mc_lock, not RTNL, so it can race with this. The new group
would then sit in dev->mc without reaching the MDF filter until some
unrelated RX mode change.
Would it help to call bcmgenet_set_rx_mode() again after
netif_device_attach()?
[ ... ]
> @@ -4510,6 +4602,7 @@ static int bcmgenet_suspend(struct device *d)
> } else {
> /* Teardown the interface */
> bcmgenet_netif_stop(dev, true);
> + priv->datapath_up = false;
> }
[Severity: High]
This isn't a bug introduced by this patch, but the new priv->datapath_up
flag tracks exactly this state, and bcmgenet_suspend() still never checks
it. Can a second system suspend hang after a failed resume?
The sequence would be:
1. A non-WoL suspend runs bcmgenet_netif_stop(dev, true). Its
napi_disable() calls leave NAPI_STATE_SCHED|NPSVC set.
bcmgenet_fini_dma() then deletes the NAPIs and frees rx_cbs/tx_cbs
without NULLing them.
2. bcmgenet_resume() fails in bcmgenet_init_dma() (kzalloc failure, Tx/Rx
DMA halt timeout, or Rx buffer allocation failure):
ret = bcmgenet_init_dma(priv, false);
if (ret) {
netdev_err(dev, "failed to initialize DMA\n");
goto out_clk_disable;
}
The device is left detached, netif_running() is still true and
datapath_up is false.
3. On the next system suspend, bcmgenet_suspend() sees netif_running() and
reaches bcmgenet_disable_tx_napi(). In this branch that happens through
bcmgenet_netif_stop(), and the WoL branch calls it directly.
napi_disable_locked() would then loop forever in:
while (val & (NAPIF_STATE_SCHED | NAPIF_STATE_NPSVC)) {
usleep_range(20, 200);
val = READ_ONCE(n->state);
}
That happens because the earlier disable left those bits set. Even if
suspend got past that point, bcmgenet_fini_dma() would kfree() rx_cbs and
tx_cbs a second time.
bcmgenet_close() is now guarded by priv->datapath_up. Should the teardown
in both branches of bcmgenet_suspend() check it too?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261012-nb-genet-mtu-nn-v2-v4-0-2e2e3bfaf8cc%40tipi-net.de
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-10-13 9:19 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-12 9:00 [PATCH net-next v4 0/8] net: bcmgenet: support larger MTUs Nicolai Buchwitz
2026-10-12 9:00 ` [PATCH net-next v4 1/8] net: bcmgenet: ring the doorbell when the last frame of a batch is dropped Nicolai Buchwitz
2026-10-12 21:29 ` Florian Fainelli
2026-10-12 9:00 ` [PATCH net-next v4 2/8] net: bcmgenet: let the caller decide whether to start the PHY Nicolai Buchwitz
2026-10-12 9:00 ` [PATCH net-next v4 3/8] net: bcmgenet: allow a continuation descriptor without the alignment pad Nicolai Buchwitz
2026-10-13 9:19 ` netdev-bot+sashiko
2026-10-12 9:00 ` [PATCH net-next v4 4/8] net: bcmgenet: rename ENET_MAX_MTU_SIZE to ENET_MAX_FRAME_LEN Nicolai Buchwitz
2026-10-12 9:00 ` [PATCH net-next v4 5/8] net: bcmgenet: derive the receive buffer length from the MTU Nicolai Buchwitz
2026-10-12 21:45 ` Florian Fainelli
2026-10-12 9:00 ` [PATCH net-next v4 6/8] net: bcmgenet: pad transmit frames out of the packet ready window Nicolai Buchwitz
2026-10-12 21:48 ` Florian Fainelli
2026-10-13 9:19 ` netdev-bot+sashiko
2026-10-12 9:00 ` [PATCH net-next v4 7/8] net: bcmgenet: allow the MTU to be changed Nicolai Buchwitz
2026-10-12 21:38 ` Florian Fainelli
2026-10-13 9:19 ` netdev-bot+sashiko
2026-10-12 9:00 ` [PATCH net-next v4 8/8] net: bcmgenet: reassemble jumbo frames from status block fragments Nicolai Buchwitz
2026-10-12 21:49 ` Florian Fainelli
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox