* [PATCH net-next v2 1/7] net: bcmgenet: let the caller decide whether to start the PHY
2026-10-05 22:24 [PATCH net-next v2 0/7] net: bcmgenet: support larger MTUs Nicolai Buchwitz
@ 2026-10-05 22:24 ` Nicolai Buchwitz
2026-10-05 22:24 ` [PATCH net-next v2 2/7] net: bcmgenet: allow a continuation descriptor without the alignment pad Nicolai Buchwitz
` (5 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Nicolai Buchwitz @ 2026-10-05 22:24 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
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 4c9db2f9fc25..a82579879f4b 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -3348,7 +3348,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);
@@ -3365,7 +3365,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)
@@ -3428,7 +3429,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);
@@ -4312,7 +4313,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 [flat|nested] 14+ messages in thread* [PATCH net-next v2 2/7] net: bcmgenet: allow a continuation descriptor without the alignment pad
2026-10-05 22:24 [PATCH net-next v2 0/7] net: bcmgenet: support larger MTUs Nicolai Buchwitz
2026-10-05 22:24 ` [PATCH net-next v2 1/7] net: bcmgenet: let the caller decide whether to start the PHY Nicolai Buchwitz
@ 2026-10-05 22:24 ` Nicolai Buchwitz
2026-10-05 23:05 ` Florian Fainelli
2026-10-05 22:24 ` [PATCH net-next v2 3/7] net: bcmgenet: rename ENET_MAX_MTU_SIZE to ENET_MAX_FRAME_LEN Nicolai Buchwitz
` (4 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Nicolai Buchwitz @ 2026-10-05 22:24 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
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.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
Tested-by: Pierre-Marin Leclercq <pierremarinleclercq88@gmail.com>
---
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 a82579879f4b..c781dfbe3f60 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)
@@ -2329,6 +2330,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;
@@ -2365,8 +2367,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 [flat|nested] 14+ messages in thread* Re: [PATCH net-next v2 2/7] net: bcmgenet: allow a continuation descriptor without the alignment pad
2026-10-05 22:24 ` [PATCH net-next v2 2/7] net: bcmgenet: allow a continuation descriptor without the alignment pad Nicolai Buchwitz
@ 2026-10-05 23:05 ` Florian Fainelli
0 siblings, 0 replies; 14+ messages in thread
From: Florian Fainelli @ 2026-10-05 23:05 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
On 10/5/26 15:24, Nicolai Buchwitz wrote:
> 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.
>
> Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
> Tested-by: Pierre-Marin Leclercq <pierremarinleclercq88@gmail.com>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
--
Florian
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH net-next v2 3/7] net: bcmgenet: rename ENET_MAX_MTU_SIZE to ENET_MAX_FRAME_LEN
2026-10-05 22:24 [PATCH net-next v2 0/7] net: bcmgenet: support larger MTUs Nicolai Buchwitz
2026-10-05 22:24 ` [PATCH net-next v2 1/7] net: bcmgenet: let the caller decide whether to start the PHY Nicolai Buchwitz
2026-10-05 22:24 ` [PATCH net-next v2 2/7] net: bcmgenet: allow a continuation descriptor without the alignment pad Nicolai Buchwitz
@ 2026-10-05 22:24 ` Nicolai Buchwitz
2026-10-05 23:06 ` Florian Fainelli
2026-10-05 22:24 ` [PATCH net-next v2 4/7] net: bcmgenet: derive the receive buffer length from the MTU Nicolai Buchwitz
` (3 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Nicolai Buchwitz @ 2026-10-05 22:24 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
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.
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 c781dfbe3f60..641d918577d4 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -2639,7 +2639,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);
@@ -2745,7 +2745,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 [flat|nested] 14+ messages in thread* Re: [PATCH net-next v2 3/7] net: bcmgenet: rename ENET_MAX_MTU_SIZE to ENET_MAX_FRAME_LEN
2026-10-05 22:24 ` [PATCH net-next v2 3/7] net: bcmgenet: rename ENET_MAX_MTU_SIZE to ENET_MAX_FRAME_LEN Nicolai Buchwitz
@ 2026-10-05 23:06 ` Florian Fainelli
0 siblings, 0 replies; 14+ messages in thread
From: Florian Fainelli @ 2026-10-05 23:06 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
On 10/5/26 15:24, Nicolai Buchwitz wrote:
> 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.
>
> Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
--
Florian
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH net-next v2 4/7] net: bcmgenet: derive the receive buffer length from the MTU
2026-10-05 22:24 [PATCH net-next v2 0/7] net: bcmgenet: support larger MTUs Nicolai Buchwitz
` (2 preceding siblings ...)
2026-10-05 22:24 ` [PATCH net-next v2 3/7] net: bcmgenet: rename ENET_MAX_MTU_SIZE to ENET_MAX_FRAME_LEN Nicolai Buchwitz
@ 2026-10-05 22:24 ` Nicolai Buchwitz
2026-10-05 23:11 ` Florian Fainelli
2026-10-06 22:35 ` netdev-bot+sashiko
2026-10-05 22:24 ` [PATCH net-next v2 5/7] net: bcmgenet: allow the MTU to be changed Nicolai Buchwitz
` (2 subsequent siblings)
6 siblings, 2 replies; 14+ messages in thread
From: Nicolai Buchwitz @ 2026-10-05 22:24 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
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.
Program the transmit threshold at its maximum as well. It sets how much of
a frame the MAC holds before it starts sending, and holding less buys
nothing. A later patch lowers it for the few MTUs that need the room.
Suggested-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 86 +++++++++++++++++++++-----
drivers/net/ethernet/broadcom/genet/bcmgenet.h | 4 ++
2 files changed, 76 insertions(+), 14 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 641d918577d4..75d1006a35c5 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)
@@ -2253,7 +2271,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;
@@ -2268,7 +2286,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;
@@ -2347,10 +2365,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;
@@ -2372,7 +2390,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);
@@ -2623,6 +2641,44 @@ 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_MAX_FRAME_LEN, UMAC_MAX_FRAME_LEN);
+
+ /* GENET v1 maps other registers at these offsets */
+ if (GENET_IS_V1(priv))
+ return;
+
+ bcmgenet_rbuf_writel(priv, thld, RBUF_PKT_RDY_THLD);
+ bcmgenet_writel(ENET_THLD_MAX,
+ 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;
@@ -2639,7 +2695,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);
@@ -2755,7 +2811,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,
@@ -2774,8 +2830,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,
@@ -2838,7 +2895,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) |
@@ -4107,6 +4164,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..6444bac168c3 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -218,6 +218,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 +250,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 +615,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 [flat|nested] 14+ messages in thread* Re: [PATCH net-next v2 4/7] net: bcmgenet: derive the receive buffer length from the MTU
2026-10-05 22:24 ` [PATCH net-next v2 4/7] net: bcmgenet: derive the receive buffer length from the MTU Nicolai Buchwitz
@ 2026-10-05 23:11 ` Florian Fainelli
2026-10-06 22:35 ` netdev-bot+sashiko
1 sibling, 0 replies; 14+ messages in thread
From: Florian Fainelli @ 2026-10-05 23:11 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
On 10/5/26 15:24, 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.
>
> Program the transmit threshold at its maximum as well. It sets how much of
> a frame the MAC holds before it starts sending, and holding less buys
> nothing. A later patch lowers it for the few MTUs that need the room.
>
> 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] 14+ messages in thread
* Re: [PATCH net-next v2 4/7] net: bcmgenet: derive the receive buffer length from the MTU
2026-10-05 22:24 ` [PATCH net-next v2 4/7] net: bcmgenet: derive the receive buffer length from the MTU Nicolai Buchwitz
2026-10-05 23:11 ` Florian Fainelli
@ 2026-10-06 22:35 ` netdev-bot+sashiko
1 sibling, 0 replies; 14+ messages in thread
From: netdev-bot+sashiko @ 2026-10-06 22:35 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
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 patch also changes what bcmgenet_init_tx_ring() writes into
each transmit ring's DMA_RING_BUF_SIZE register, and the commit…
--
net: bcmgenet: derive the receive buffer length from the MTU
This patch replaces the fixed 2048 byte RX_BUF_LENGTH with
priv->rx_buf_len. That value comes from an RBUF packet ready threshold
computed from the MTU, plus the 64 byte status block. It also programs
the RBUF and TBUF packet ready threshold registers. The TBUF threshold
is set to its maximum.
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> index 641d918577d4d..75d1006a35c55 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
[ ... ]
> @@ -2623,6 +2641,44 @@ static void bcmgenet_link_intr_enable(struct bcmgenet_priv *priv)
[ ... ]
> +/* 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;
> +}
[ ... ]
> @@ -2755,7 +2811,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);
[Severity: Low]
Is this change to the transmit ring intended? The commit message only
talks about the receive buffer and the RBUF/TBUF thresholds. It doesn't
mention that bcmgenet_init_tx_ring() now writes a different value into
each TDMA ring's DMA_RING_BUF_SIZE register.
Before the patch, the TX rings got the constant 2048. With this patch
they get priv->rx_buf_len, which bcmgenet_rx_buf_len() computes as:
sizeof(struct status_64) + bcmgenet_pkt_rdy_thld(mtu) * 16
That is 64 + 2048 = 2112 at the default MTU. The new value includes the
64 byte RX status block, which only the receive side writes.
This sentence in the commit message reads as if it describes the RX
page pool buffer only:
"the buffer only grows by the status block the hardware already
wrote."
The TX ring already took its value from an RX-named constant, so this
coupling is not new. What is new is that it now follows the MTU.
With the later "net: bcmgenet: allow the MTU to be changed" patch in
this series, every RX buffer resize would also reprogram the TX rings.
That reaches 3648 on 4K pages and 3904 on 64K pages.
Each TX descriptor carries its own length in len_stat, so this may have
no functional effect. Should the TX ring keep its own value here? If
the change is intended, could the commit message say so?
>
> /* Set start and end address, read and write pointers */
> bcmgenet_tdma_ring_writel(priv, index, start_ptr * words_per_bd,
[ ... ]
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
> index 501dd12566936..6444bac168c38 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
[ ... ]
> @@ -612,6 +615,7 @@ struct bcmgenet_priv {
> void __iomem *rx_bds;
> struct enet_cb *rx_cbs;
> unsigned int num_rx_bds;
> + unsigned int rx_buf_len;
This field is declared with the receive variables, but
bcmgenet_init_tx_ring() above also uses it to configure the transmit
rings.
> 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/20261006-nb-genet-mtu-nn-v2-v2-0-c4be21151912%40tipi-net.de
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH net-next v2 5/7] net: bcmgenet: allow the MTU to be changed
2026-10-05 22:24 [PATCH net-next v2 0/7] net: bcmgenet: support larger MTUs Nicolai Buchwitz
` (3 preceding siblings ...)
2026-10-05 22:24 ` [PATCH net-next v2 4/7] net: bcmgenet: derive the receive buffer length from the MTU Nicolai Buchwitz
@ 2026-10-05 22:24 ` Nicolai Buchwitz
2026-10-05 22:24 ` [PATCH net-next v2 6/7] net: bcmgenet: pad transmit frames out of the packet ready window Nicolai Buchwitz
2026-10-05 22:24 ` [PATCH net-next v2 7/7] net: bcmgenet: reassemble jumbo frames from status block fragments Nicolai Buchwitz
6 siblings, 0 replies; 14+ messages in thread
From: Nicolai Buchwitz @ 2026-10-05 22:24 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
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.
Resize the buffers and rewrite the registers in place. The PHY keeps
running and the link stays up.
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
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
Tested-by: Pierre-Marin Leclercq <pierremarinleclercq88@gmail.com>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 87 +++++++++++++++++++++++++-
drivers/net/ethernet/broadcom/genet/bcmgenet.h | 11 +++-
2 files changed, 92 insertions(+), 6 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 75d1006a35c5..3e2ebd9a2cc5 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -79,6 +79,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))
@@ -2667,7 +2673,7 @@ static void bcmgenet_set_mtu_regs(struct bcmgenet_priv *priv, unsigned int mtu)
{
u32 thld = bcmgenet_pkt_rdy_thld(mtu);
- bcmgenet_umac_writel(priv, ENET_MAX_FRAME_LEN, UMAC_MAX_FRAME_LEN);
+ 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))
@@ -2801,7 +2807,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);
@@ -3494,6 +3500,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;
@@ -3552,7 +3559,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);
@@ -3800,6 +3811,71 @@ 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);
+
+ /* 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,
@@ -3811,6 +3887,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 */
@@ -4164,7 +4241,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");
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 6444bac168c3..75cfbccfd4ce 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -31,9 +31,13 @@
#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)
+
#define DMA_MAX_BURST_LENGTH 0x10
/* misc. configuration */
@@ -627,6 +631,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 [flat|nested] 14+ messages in thread* [PATCH net-next v2 6/7] net: bcmgenet: pad transmit frames out of the packet ready window
2026-10-05 22:24 [PATCH net-next v2 0/7] net: bcmgenet: support larger MTUs Nicolai Buchwitz
` (4 preceding siblings ...)
2026-10-05 22:24 ` [PATCH net-next v2 5/7] net: bcmgenet: allow the MTU to be changed Nicolai Buchwitz
@ 2026-10-05 22:24 ` Nicolai Buchwitz
2026-10-06 22:35 ` netdev-bot+sashiko
2026-10-05 22:24 ` [PATCH net-next v2 7/7] net: bcmgenet: reassemble jumbo frames from status block fragments Nicolai Buchwitz
6 siblings, 1 reply; 14+ messages in thread
From: Nicolai Buchwitz @ 2026-10-05 22:24 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
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 CM4
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.
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.
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.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
Tested-by: Pierre-Marin Leclercq <pierremarinleclercq88@gmail.com>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 52 +++++++++++++++++++++++++-
drivers/net/ethernet/broadcom/genet/bcmgenet.h | 1 +
2 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 3e2ebd9a2cc5..e8f86374c7cd 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
@@ -2173,6 +2178,31 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
goto out;
}
+ /* 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);
+ ret = NETDEV_TX_OK;
+ goto out;
+ }
+ }
+
+ /* 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);
+ ret = NETDEV_TX_OK;
+ goto out;
+ }
+ }
+
+ nr_frags = skb_shinfo(skb)->nr_frags;
+
/* Retain how many bytes will be sent on the wire, without TSB inserted
* by transmit checksum offload
*/
@@ -2661,6 +2691,23 @@ 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;
+
+ 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)
{
@@ -2671,8 +2718,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_MAX_FRAME_LEN(mtu), UMAC_MAX_FRAME_LEN);
/* GENET v1 maps other registers at these offsets */
@@ -2680,8 +2729,7 @@ 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(ENET_THLD_MAX,
- priv->base + priv->hw_params->tbuf_offset +
+ bcmgenet_writel(tx_thld, priv->base + priv->hw_params->tbuf_offset +
TBUF_PKT_RDY_THLD);
}
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 75cfbccfd4ce..a4933a5d3823 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -620,6 +620,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 [flat|nested] 14+ messages in thread* Re: [PATCH net-next v2 6/7] net: bcmgenet: pad transmit frames out of the packet ready window
2026-10-05 22:24 ` [PATCH net-next v2 6/7] net: bcmgenet: pad transmit frames out of the packet ready window Nicolai Buchwitz
@ 2026-10-06 22:35 ` netdev-bot+sashiko
0 siblings, 0 replies; 14+ messages in thread
From: netdev-bot+sashiko @ 2026-10-06 22:35 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
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 2 · Low: 2
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] skb_put_padto() in bcmgenet_xmit() appends up to 63 zero bytes
after the existing end of the frame.
- [Medium] The two new error exits in bcmgenet_xmit()
(skb_checksum_help() failure and skb_put_padto() failure) jump…
- [Low] bcmgenet_xmit() gains a software checksum fallback that this
commit's message never mentions.
- [Low] Series ordering leaves one intermediate commit broken.
--
net: bcmgenet: pad transmit frames out of the packet ready window
This commit pads transmit frames that end just past the TBUF packet ready
threshold out to 64 bytes past it, to avoid a Tx DMA stall seen on CM4. It
also lowers the transmit threshold for the MTUs where the longest frame the
MAC accepts would land in that window. It adds a software checksum fallback
for CHECKSUM_PARTIAL frames longer than the threshold.
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> index 3e2ebd9a2cc50..e8f86374c7cd8 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
[ ... ]
> @@ -2173,6 +2178,31 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
> goto out;
> }
>
> + /* 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)) {
[Severity: Low]
The commit message covers the padding and the threshold change. Should it
also mention this software checksum fallback?
In bcmgenet_xmit(), any CHECKSUM_PARTIAL skb longer than priv->tx_thld_len
now goes through skb_checksum_help() instead of hardware offload. If that
call fails, the skb is dropped and counted in ring->stats64.dropped.
bcmgenet_tx_pkt_rdy_thld() also lowers the threshold from 0xf0 (3840 bytes)
to 0xe0 (3584 bytes) for MTU 3809 and up. So at those MTUs the CPU now
checksums every full-size frame, which the hardware handled before. In this
tree that covers MTU 3809..3820, and only on kernels with pages larger than
4K.
The changelog doesn't explain the hardware behaviour claimed in the comment
("silently drops a longer one") or the new drop path either.
The last patch in the series, "net: bcmgenet: reassemble jumbo frames from
status block fragments", does say "anything longer than the threshold falls
back to software", so the series does disclose it. Could this commit
describe it too, since this is where the code is added?
> + BCMGENET_STATS64_INC((&ring->stats64), dropped);
> + dev_kfree_skb_any(skb);
> + ret = NETDEV_TX_OK;
> + goto out;
> + }
> + }
> +
> + /* 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)) {
[Severity: Medium]
skb_put_padto() appends up to 63 zero bytes after the current end of the
frame. Can that break protocols that keep their metadata at the tail?
DSA tail taggers write the egress tag as the last bytes of the frame with
skb_put(). Examples are ksz9477_xmit(), ksz_common_xmit() and lan937x_xmit()
in net/dsa/tag_ksz.c, and trailer_xmit() in net/dsa/tag_trailer.c:
trailer = skb_put(skb, 4);
trailer[0] = 0x80;
trailer[1] = dsa_xmit_port_mask(skb, dev);
The DSA core pads short frames itself before tagging, in dsa_user_xmit(),
so that conduit padding never ends up after a tail tag:
if (dev->needed_tailroom)
eth_skb_pad(skb);
With GENET as the conduit, the switch would read zeros where it expects the
port mask/override or the 0x80 marker. The real tag bytes would then go out
on the wire.
PRP has the same problem. prp_fill_rct() puts the RCT last, and
skb_get_PRP_rct() looks for it at skb_tail_pointer(skb) - HSR_HLEN. With
zeros appended, the receiver treats the frame as a standard frame and loses
duplicate discard.
bcmgenet_tx_pkt_rdy_thld() lowers the threshold to 3584 for MTU 3809 and up,
so 3585..3647 byte frames get padded as well. At the end of the series (max
MTU 16347), a conduit with a jumbo MTU hits this for any 3841..3903 byte
frame. A remote peer can steer frame sizes into that range, for example
with ICMP echo sizes.
The commit message accepts this and says the alternatives are dropping the
frame or leaving the transmitter stalled. Where the tail is known to carry
data, for example netdev_uses_dsa() with a tail tagger, would dropping the
frame be safer than sending it with a corrupted tag?
> + BCMGENET_STATS64_INC((&ring->stats64), dropped);
> + ret = NETDEV_TX_OK;
> + goto out;
[Severity: Medium]
Both new error exits, after skb_checksum_help() and after skb_put_padto(),
jump to out. That label comes after the doorbell write at the end of
bcmgenet_xmit():
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);
out:
spin_unlock(&ring->lock);
Suppose an earlier skb in the same batch was queued with netdev_xmit_more()
true. ring->prod_index was advanced and netdev_tx_sent_queue() ran, but
TDMA_PROD_INDEX was not written.
The last skb of the batch might then fail here, for example on a GFP_ATOMIC
failure in __skb_pad() or pskb_expand_head(). In that case NETDEV_TX_OK is
returned without prod_index being published, and dev_hard_start_xmit()
treats that as the end of the batch.
Would the descriptors already queued then stay unsent until a later xmit
rings the doorbell? The queue isn't stopped, so the TX watchdog wouldn't
step in.
The existing exits for bcmgenet_add_tsb() failure and DMA mapping failure
behave the same way. This patch adds two more paths that do it.
> + }
> + }
> +
> + nr_frags = skb_shinfo(skb)->nr_frags;
> +
[ ... ]
> @@ -2680,8 +2729,7 @@ 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(ENET_THLD_MAX,
> - priv->base + priv->hw_params->tbuf_offset +
> + bcmgenet_writel(tx_thld, priv->base + priv->hw_params->tbuf_offset +
> TBUF_PKT_RDY_THLD);
> }
[Severity: Low]
This one is about the order of the series. Does it leave an intermediate
commit broken?
"net: bcmgenet: derive the receive buffer length from the MTU" programs
TBUF_PKT_RDY_THLD to ENET_THLD_MAX and defers the fix ("A later patch lowers
it for the few MTUs that need the room").
"net: bcmgenet: allow the MTU to be changed" then raises dev->max_mtu to
ENET_MAX_MTU, which is 3820 on kernels with pages larger than 4K.
vlan_dev_change_mtu() lets an 802.1Q device use the full parent MTU. So at
that commit, a stacked VLAN at MTU 3820 produces 3820 + 14 + 8 = 3842 byte
frames.
Frames of that size fall in the 3841..3886 window this patch describes for a
3840 threshold. And according to the new comment in bcmgenet_xmit(), the MAC
silently drops a CHECKSUM_PARTIAL frame longer than 3840 bytes.
Could this patch, and the threshold logic, be ordered before the max_mtu
increase? That way a bisect landing on "allow the MTU to be changed" would
not hit the stalled transmitter.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261006-nb-genet-mtu-nn-v2-v2-0-c4be21151912%40tipi-net.de
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH net-next v2 7/7] net: bcmgenet: reassemble jumbo frames from status block fragments
2026-10-05 22:24 [PATCH net-next v2 0/7] net: bcmgenet: support larger MTUs Nicolai Buchwitz
` (5 preceding siblings ...)
2026-10-05 22:24 ` [PATCH net-next v2 6/7] net: bcmgenet: pad transmit frames out of the packet ready window Nicolai Buchwitz
@ 2026-10-05 22:24 ` Nicolai Buchwitz
2026-10-06 22:35 ` netdev-bot+sashiko
6 siblings, 1 reply; 14+ messages in thread
From: Nicolai Buchwitz @ 2026-10-05 22:24 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
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.
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.
Suggested-by: Justin Chen <justin.chen@broadcom.com>
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
Tested-by: Pierre-Marin Leclercq <pierremarinleclercq88@gmail.com>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 106 +++++++++++++++++++++----
drivers/net/ethernet/broadcom/genet/bcmgenet.h | 2 +
2 files changed, 94 insertions(+), 14 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index e8f86374c7cd..faa13f12ce7e 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)
@@ -2178,8 +2175,8 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
goto out;
}
- /* 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) {
@@ -2333,6 +2330,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.
*/
@@ -2397,6 +2442,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;
}
@@ -2430,15 +2476,25 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
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;
@@ -2468,17 +2524,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;
@@ -2490,8 +2556,18 @@ 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:
+
+ 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 */
@@ -2608,6 +2684,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);
}
}
@@ -4293,7 +4371,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 a4933a5d3823..97c27b7920d5 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -581,6 +581,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 [flat|nested] 14+ messages in thread* Re: [PATCH net-next v2 7/7] net: bcmgenet: reassemble jumbo frames from status block fragments
2026-10-05 22:24 ` [PATCH net-next v2 7/7] net: bcmgenet: reassemble jumbo frames from status block fragments Nicolai Buchwitz
@ 2026-10-06 22:35 ` netdev-bot+sashiko
0 siblings, 0 replies; 14+ messages in thread
From: netdev-bot+sashiko @ 2026-10-06 22:35 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
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.
Critical: 0 · High: 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).
- [High] Use-after-free read of the last status block in
bcmgenet_desc_rx() when CRC forwarding is on.
- [Medium] TX descriptor length can overflow the 12-bit length field once
the MTU goes past about 4 KB.
- [Medium] Short SOP descriptor followed by continuation fragments can
reach BUG() in eth_type_trans().
- [Low] The comment on bcmgenet_pkt_rdy_thld() is now stale.
--
net: bcmgenet: reassemble jumbo frames from status block fragments
The hardware splits a frame longer than the packet ready threshold across
several descriptors, each with its own status block. This patch reassembles
those descriptors into one skb by attaching the later ones as page frags.
It also raises max_mtu on v2+ to ENET_MAX_JUMBO_MTU, which is derived from
the 14-bit UMAC_MAX_FRAME_LEN.
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> index e8f86374c7cd8..faa13f12ce7e6 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)
[Severity: Medium]
This limit comes from the UMAC frame length only. Once bcmgenet_probe()
sets max_mtu to it, the TX path can see MTUs up to 16347. Can the TX
descriptor length field hold buffers that large?
bcmgenet_xmit() still maps the linear head and each page frag as a single
descriptor. It writes the size into len_stat with no mask, cap or split:
bcmgenet_xmit() {
...
len_stat = (size << DMA_BUFLENGTH_SHIFT) |
(priv->hw_params->qtag_mask << DMA_TX_QTAG_SHIFT);
...
}
The only in-tree description of that field says it is 12 bits wide:
bcmgenet.h:
/* Tx/Rx Dma Descriptor common bits*/
#define DMA_BUFLENGTH_MASK 0x0fff
#define DMA_BUFLENGTH_SHIFT 16
At jumbo MTU, skb_headlen() can approach 16 KB, plus the 64-byte TSB that
bcmgenet_add_tsb() adds. TCP frags of around 9 KB are also common.
If the field really is 12 bits, would the length wrap modulo 4096, with the
upper bits spilling into bits 31:28? Would that truncate or corrupt the
frame on the wire? Before this patch the largest TX buffer was about 3.9 KB.
Separately, bcmgenet_init_tx_ring() still sets DMA_RING_BUF_SIZE from
priv->rx_buf_len, which is at most about 3.9 KB:
bcmgenet_init_tx_ring() {
...
bcmgenet_tdma_ring_writel(priv, index,
((size << DMA_RING_SIZE_SHIFT) |
priv->rx_buf_len), DMA_RING_BUF_SIZE);
...
}
Is that setting still valid now that one TX descriptor can be several times
larger? Could the commit message say whether the TX descriptor length and
the TX buffer size limits allow more than 4095 bytes?
[ ... ]
> @@ -2333,6 +2330,54 @@ static int bcmgenet_rx_refill(struct bcmgenet_rx_ring *ring,
> return 0;
> }
>
[ ... ]
> +/* 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.
> + */
[Severity: Low]
This isn't a bug, but the comment above bcmgenet_pkt_rdy_thld() now looks
stale:
/* Receive threshold in register units. Covers the alignment bytes and the
* frame, but not the status block, which the hardware adds on top.
*/
With max_mtu raised to ENET_MAX_JUMBO_MTU, any MTU above about 3.5-3.8 KB
is clamped to ENET_THLD_MAX_LEN / ENET_THLD_UNIT. The threshold then no
longer covers the frame, which contradicts the comment here. Should that
comment be updated?
[ ... ]
> @@ -2490,8 +2556,18 @@ 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;
> + }
[Severity: Medium]
Could a short SOP head lead to BUG() in eth_type_trans()?
For an SOP descriptor the only length check is len >= GENET_RSB_PAD (66). A
descriptor with SOP and no EOP that carries fewer than ETH_HLEN frame bytes
would still become ring->frag_head. Later descriptors are then added as
frags.
Nothing calls pskb_may_pull() before this line after deliver:
skb->protocol = eth_type_trans(skb, priv->dev);
If the linear part has fewer than 14 bytes and frags are present,
__skb_pull() would see skb->len < skb->data_len and call BUG().
bcmgenet_pkt_rdy_thld() keeps the threshold at ENET_THLD_DEFAULT (2048
bytes) or more. Correct hardware should therefore always fill an SOP
descriptor without EOP to around 2 KB. Reaching this would take a
malformed status block from the MAC, so this is hardening rather than a
path reachable from the network.
Would it be worth checking that an SOP descriptor without EOP holds at
least ETH_HLEN frame bytes?
> +
> +deliver:
> +
> + 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 */
[Severity: High]
Can this read the status block from a page that has already been freed?
For a reassembled frame, status still points to page_address(rx_page) +
rx_offset of the EOP descriptor. bcmgenet_add_frag() has already given that
page to the skb as its last frag.
When crc_fwd_en is set, pskb_trim() on this non-linear skb goes through
___pskb_trim(). That can release the page in two ways.
First, if the EOP descriptor carries 4 or fewer frame bytes, the last frag
is released with skb_frag_unref(). A non-SOP len of 64-68 passes the
min_len check.
Second, skb->sk is NULL, so skb_condense() runs. It may pull the remaining
frag data into the head and free every frag.
The page then goes back through napi_pp_put_page(). If it cannot be
recycled (pfmemalloc, remote NUMA node, or full cache and ring), it can be
returned to the page allocator via page_pool_return_netmem().
The very next statements read the status block:
/* Set up checksum offload */
if (dev->features & NETIF_F_RXCSUM) {
rx_csum = (__force __be16)(status->rx_csum & 0xffff);
The value read there is then used as CHECKSUM_COMPLETE. Before this patch
the skb was always linear and status pointed into the head buffer, which
skb_trim() never frees.
Should rx_csum be read before pskb_trim() is called?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261006-nb-genet-mtu-nn-v2-v2-0-c4be21151912%40tipi-net.de
^ permalink raw reply [flat|nested] 14+ messages in thread