* [PATCH net-next v3 0/6] net: bcmgenet: add XDP support
@ 2026-03-19 11:53 Nicolai Buchwitz
2026-03-19 11:53 ` [PATCH net-next v3 1/6] net: bcmgenet: convert RX path to page_pool Nicolai Buchwitz
` (5 more replies)
0 siblings, 6 replies; 13+ messages in thread
From: Nicolai Buchwitz @ 2026-03-19 11:53 UTC (permalink / raw)
To: netdev
Cc: Nicolai Buchwitz, Alexei Starovoitov, Daniel Borkmann,
David S. Miller, Jakub Kicinski, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, bpf
Add XDP support to the bcmgenet driver, covering XDP_PASS, XDP_DROP,
XDP_TX, XDP_REDIRECT, and ndo_xdp_xmit.
The first patch converts the RX path from the existing kmalloc-based
allocation to page_pool, which is a prerequisite for XDP. The remaining
patches incrementally add XDP functionality and per-action statistics.
Tested on Raspberry Pi CM4 (BCM2711, bcmgenet, 1Gbps link):
- XDP_PASS: 943 Mbit/s TX, 935 Mbit/s RX (no regression vs baseline)
- XDP_PASS latency: 0.164ms avg, 0% packet loss
- XDP_DROP: all inbound traffic blocked as expected
- XDP_TX: TX counter increments (packet reflection working)
- Link flap with XDP attached: no errors
- Program swap under iperf3 load: no errors
- Upstream XDP selftests (xdp.py): pass_sb, drop_sb, tx_sb passing
Changes since v2:
- Fixed xdp_prepare_buff() called with meta_valid=false, causing
bcmgenet_xdp_build_skb() to compute metasize=UINT_MAX and corrupt
skb meta_len. Now passes true. (Simon Horman)
- Removed bcmgenet_dump_tx_queue() for ring 16 in bcmgenet_timeout().
Ring 16 has no netdev TX queue, so netdev_get_tx_queue(dev, 16)
accessed beyond the allocated _tx array. (Simon Horman)
- Fixed checkpatch alignment warnings in patches 4 and 5.
Changes since v1:
- Fixed tx_rings[DESC_INDEX] out-of-bounds access. Expanded array
to DESC_INDEX+1 and initialized ring 16 with dedicated BDs.
- Use ring 16 (hardware default descriptor ring) for XDP TX,
isolating from normal SKB TX queues.
- Piggyback ring 16 TX completion on RX NAPI poll (INTRL2_1 bit
collision with RX ring 0).
- Fixed ring 16 TX reclaim: skip INTRL2_1 clear, skip BQL
completion, use non-destructive reclaim in RX poll path.
- Prepend zeroed TSB before XDP TX frame data (TBUF_64B_EN requires
64-byte struct status_64 prefix on all TX buffers).
- Tested with upstream XDP selftests (xdp.py): pass_sb, drop_sb,
tx_sb all passing. The multi-buffer tests (pass_mb, drop_mb,
tx_mb) fail because bcmgenet does not support jumbo frames /
MTU changes; I plan to add ndo_change_mtu support in a follow-up
series.
Nicolai Buchwitz (6):
net: bcmgenet: convert RX path to page_pool
net: bcmgenet: register xdp_rxq_info for each RX ring
net: bcmgenet: add basic XDP support (PASS/DROP)
net: bcmgenet: add XDP_TX support
net: bcmgenet: add XDP_REDIRECT and ndo_xdp_xmit support
net: bcmgenet: add XDP statistics counters
drivers/net/ethernet/broadcom/Kconfig | 1 +
.../net/ethernet/broadcom/genet/bcmgenet.c | 608 +++++++++++++++---
.../net/ethernet/broadcom/genet/bcmgenet.h | 20 +-
3 files changed, 537 insertions(+), 92 deletions(-)
--
2.51.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH net-next v3 1/6] net: bcmgenet: convert RX path to page_pool
2026-03-19 11:53 [PATCH net-next v3 0/6] net: bcmgenet: add XDP support Nicolai Buchwitz
@ 2026-03-19 11:53 ` Nicolai Buchwitz
2026-03-20 17:02 ` Simon Horman
2026-03-19 11:53 ` [PATCH net-next v3 2/6] net: bcmgenet: register xdp_rxq_info for each RX ring Nicolai Buchwitz
` (4 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Nicolai Buchwitz @ 2026-03-19 11:53 UTC (permalink / raw)
To: netdev
Cc: Nicolai Buchwitz, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Vikas Gupta,
Rajashekar Hudumula, Bhargava Marreddy, Eric Biggers,
Arnd Bergmann, linux-kernel
Replace the per-packet __netdev_alloc_skb() + dma_map_single() in the
RX path with page_pool, which provides efficient page recycling and
DMA mapping management. This is a prerequisite for XDP support (which
requires stable page-backed buffers rather than SKB linear data).
Key changes:
- Create a page_pool per RX ring (PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV)
- bcmgenet_rx_refill() allocates pages via page_pool_alloc_pages()
- bcmgenet_desc_rx() builds SKBs from pages via napi_build_skb() with
skb_mark_for_recycle() for automatic page_pool return
- Buffer layout reserves XDP_PACKET_HEADROOM (256 bytes) before the HW
RSB (64 bytes) + alignment pad (2 bytes) for future XDP headroom
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/broadcom/Kconfig | 1 +
.../net/ethernet/broadcom/genet/bcmgenet.c | 213 +++++++++++-------
.../net/ethernet/broadcom/genet/bcmgenet.h | 4 +
3 files changed, 141 insertions(+), 77 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/Kconfig b/drivers/net/ethernet/broadcom/Kconfig
index cd7dddeb91dd..e3b9a5272406 100644
--- a/drivers/net/ethernet/broadcom/Kconfig
+++ b/drivers/net/ethernet/broadcom/Kconfig
@@ -78,6 +78,7 @@ config BCMGENET
select BCM7XXX_PHY
select MDIO_BCM_UNIMAC
select DIMLIB
+ select PAGE_POOL
select BROADCOM_PHY if ARCH_BCM2835
help
This driver supports the built-in Ethernet MACs found in the
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 482a31e7b72b..7410034d9bdc 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -52,6 +52,14 @@
#define RX_BUF_LENGTH 2048
#define SKB_ALIGNMENT 32
+/* Page pool RX buffer layout:
+ * XDP_PACKET_HEADROOM | RSB(64) + pad(2) | frame data | skb_shared_info
+ * The HW writes the 64B RSB + 2B alignment padding before the frame.
+ */
+#define GENET_XDP_HEADROOM XDP_PACKET_HEADROOM
+#define GENET_RSB_PAD (sizeof(struct status_64) + 2)
+#define GENET_RX_HEADROOM (GENET_XDP_HEADROOM + GENET_RSB_PAD)
+
/* 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))
@@ -1895,21 +1903,13 @@ static struct sk_buff *bcmgenet_free_tx_cb(struct device *dev,
}
/* Simple helper to free a receive control block's resources */
-static struct sk_buff *bcmgenet_free_rx_cb(struct device *dev,
- struct enet_cb *cb)
+static void bcmgenet_free_rx_cb(struct enet_cb *cb,
+ struct page_pool *pool)
{
- struct sk_buff *skb;
-
- skb = cb->skb;
- cb->skb = NULL;
-
- if (dma_unmap_addr(cb, dma_addr)) {
- dma_unmap_single(dev, dma_unmap_addr(cb, dma_addr),
- dma_unmap_len(cb, dma_len), DMA_FROM_DEVICE);
- dma_unmap_addr_set(cb, dma_addr, 0);
+ if (cb->rx_page) {
+ page_pool_put_full_page(pool, cb->rx_page, false);
+ cb->rx_page = NULL;
}
-
- return skb;
}
/* Unlocked version of the reclaim routine */
@@ -2248,46 +2248,30 @@ static netdev_tx_t bcmgenet_xmit(struct sk_buff *skb, struct net_device *dev)
goto out;
}
-static struct sk_buff *bcmgenet_rx_refill(struct bcmgenet_priv *priv,
- struct enet_cb *cb)
+static int bcmgenet_rx_refill(struct bcmgenet_rx_ring *ring,
+ struct enet_cb *cb)
{
- struct device *kdev = &priv->pdev->dev;
- struct sk_buff *skb;
- struct sk_buff *rx_skb;
+ struct bcmgenet_priv *priv = ring->priv;
dma_addr_t mapping;
+ struct page *page;
- /* Allocate a new Rx skb */
- skb = __netdev_alloc_skb(priv->dev, priv->rx_buf_len + SKB_ALIGNMENT,
- GFP_ATOMIC | __GFP_NOWARN);
- if (!skb) {
+ page = page_pool_alloc_pages(ring->page_pool,
+ GFP_ATOMIC | __GFP_NOWARN);
+ if (!page) {
priv->mib.alloc_rx_buff_failed++;
netif_err(priv, rx_err, priv->dev,
- "%s: Rx skb allocation failed\n", __func__);
- return NULL;
- }
-
- /* DMA-map the new Rx skb */
- mapping = dma_map_single(kdev, skb->data, priv->rx_buf_len,
- DMA_FROM_DEVICE);
- if (dma_mapping_error(kdev, mapping)) {
- priv->mib.rx_dma_failed++;
- dev_kfree_skb_any(skb);
- netif_err(priv, rx_err, priv->dev,
- "%s: Rx skb DMA mapping failed\n", __func__);
- return NULL;
+ "%s: Rx page allocation failed\n", __func__);
+ return -ENOMEM;
}
- /* Grab the current Rx skb from the ring and DMA-unmap it */
- rx_skb = bcmgenet_free_rx_cb(kdev, cb);
+ /* page_pool handles DMA mapping via PP_FLAG_DMA_MAP */
+ mapping = page_pool_get_dma_addr(page) + GENET_XDP_HEADROOM;
- /* Put the new Rx skb on the ring */
- cb->skb = skb;
- dma_unmap_addr_set(cb, dma_addr, mapping);
- dma_unmap_len_set(cb, dma_len, priv->rx_buf_len);
+ cb->rx_page = page;
+ cb->rx_page_offset = GENET_XDP_HEADROOM;
dmadesc_set_addr(priv, cb->bd_addr, mapping);
- /* Return the current Rx skb to caller */
- return rx_skb;
+ return 0;
}
/* bcmgenet_desc_rx - descriptor based rx process.
@@ -2339,25 +2323,28 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
while ((rxpktprocessed < rxpkttoprocess) &&
(rxpktprocessed < budget)) {
struct status_64 *status;
+ struct page *rx_page;
+ unsigned int rx_off;
__be16 rx_csum;
+ void *hard_start;
cb = &priv->rx_cbs[ring->read_ptr];
- skb = bcmgenet_rx_refill(priv, cb);
- if (unlikely(!skb)) {
+ /* Save the received page before refilling */
+ rx_page = cb->rx_page;
+ rx_off = cb->rx_page_offset;
+
+ if (bcmgenet_rx_refill(ring, cb)) {
BCMGENET_STATS64_INC(stats, dropped);
goto next;
}
- status = (struct status_64 *)skb->data;
+ page_pool_dma_sync_for_cpu(ring->page_pool, rx_page, 0,
+ RX_BUF_LENGTH);
+
+ hard_start = page_address(rx_page) + rx_off;
+ status = (struct status_64 *)hard_start;
dma_length_status = status->length_status;
- if (dev->features & NETIF_F_RXCSUM) {
- rx_csum = (__force __be16)(status->rx_csum & 0xffff);
- if (rx_csum) {
- skb->csum = (__force __wsum)ntohs(rx_csum);
- skb->ip_summed = CHECKSUM_COMPLETE;
- }
- }
/* DMA flags and length are still valid no matter how
* we got the Receive Status Vector (64B RSB or register)
@@ -2373,7 +2360,8 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
if (unlikely(len > RX_BUF_LENGTH)) {
netif_err(priv, rx_status, dev, "oversized packet\n");
BCMGENET_STATS64_INC(stats, length_errors);
- dev_kfree_skb_any(skb);
+ page_pool_put_full_page(ring->page_pool, rx_page,
+ true);
goto next;
}
@@ -2381,7 +2369,8 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
netif_err(priv, rx_status, dev,
"dropping fragmented packet!\n");
BCMGENET_STATS64_INC(stats, fragmented_errors);
- dev_kfree_skb_any(skb);
+ page_pool_put_full_page(ring->page_pool, rx_page,
+ true);
goto next;
}
@@ -2409,24 +2398,48 @@ 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);
- dev_kfree_skb_any(skb);
+ page_pool_put_full_page(ring->page_pool, rx_page,
+ true);
goto next;
} /* error packet */
- skb_put(skb, len);
+ /* 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, PAGE_SIZE - GENET_XDP_HEADROOM);
+ if (unlikely(!skb)) {
+ BCMGENET_STATS64_INC(stats, dropped);
+ page_pool_put_full_page(ring->page_pool, rx_page,
+ true);
+ goto next;
+ }
+
+ skb_mark_for_recycle(skb);
- /* remove RSB and hardware 2bytes added for IP alignment */
- skb_pull(skb, 66);
- len -= 66;
+ /* Reserve the RSB + pad, then set the data length */
+ skb_reserve(skb, GENET_RSB_PAD);
+ __skb_put(skb, len - GENET_RSB_PAD);
if (priv->crc_fwd_en) {
- skb_trim(skb, len - ETH_FCS_LEN);
+ skb_trim(skb, skb->len - ETH_FCS_LEN);
len -= ETH_FCS_LEN;
}
+ /* 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;
+ }
+ }
+
+ len = skb->len;
bytes_processed += len;
- /*Finish setting up the received SKB and send it to the kernel*/
+ /* Finish setting up the received SKB and send it to the
+ * kernel.
+ */
skb->protocol = eth_type_trans(skb, priv->dev);
u64_stats_update_begin(&stats->syncp);
@@ -2495,12 +2508,11 @@ static void bcmgenet_dim_work(struct work_struct *work)
dim->state = DIM_START_MEASURE;
}
-/* Assign skb to RX DMA descriptor. */
+/* Assign page_pool pages to RX DMA descriptors. */
static int bcmgenet_alloc_rx_buffers(struct bcmgenet_priv *priv,
struct bcmgenet_rx_ring *ring)
{
struct enet_cb *cb;
- struct sk_buff *skb;
int i;
netif_dbg(priv, hw, priv->dev, "%s\n", __func__);
@@ -2508,10 +2520,7 @@ static int bcmgenet_alloc_rx_buffers(struct bcmgenet_priv *priv,
/* loop here for each buffer needing assign */
for (i = 0; i < ring->size; i++) {
cb = ring->cbs + i;
- skb = bcmgenet_rx_refill(priv, cb);
- if (skb)
- dev_consume_skb_any(skb);
- if (!cb->skb)
+ if (bcmgenet_rx_refill(ring, cb))
return -ENOMEM;
}
@@ -2520,16 +2529,18 @@ static int bcmgenet_alloc_rx_buffers(struct bcmgenet_priv *priv,
static void bcmgenet_free_rx_buffers(struct bcmgenet_priv *priv)
{
- struct sk_buff *skb;
+ struct bcmgenet_rx_ring *ring;
struct enet_cb *cb;
- int i;
+ int q, i;
- for (i = 0; i < priv->num_rx_bds; i++) {
- cb = &priv->rx_cbs[i];
-
- skb = bcmgenet_free_rx_cb(&priv->pdev->dev, cb);
- if (skb)
- dev_consume_skb_any(skb);
+ for (q = 0; q <= priv->hw_params->rx_queues; q++) {
+ ring = &priv->rx_rings[q];
+ if (!ring->page_pool)
+ continue;
+ for (i = 0; i < ring->size; i++) {
+ cb = ring->cbs + i;
+ bcmgenet_free_rx_cb(cb, ring->page_pool);
+ }
}
}
@@ -2747,6 +2758,31 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv,
netif_napi_add_tx(priv->dev, &ring->napi, bcmgenet_tx_poll);
}
+static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv,
+ struct bcmgenet_rx_ring *ring)
+{
+ struct page_pool_params pp_params = {
+ .order = 0,
+ .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
+ .pool_size = ring->size,
+ .nid = NUMA_NO_NODE,
+ .dev = &priv->pdev->dev,
+ .dma_dir = DMA_FROM_DEVICE,
+ .offset = GENET_XDP_HEADROOM,
+ .max_len = RX_BUF_LENGTH,
+ };
+
+ ring->page_pool = page_pool_create(&pp_params);
+ if (IS_ERR(ring->page_pool)) {
+ int err = PTR_ERR(ring->page_pool);
+
+ ring->page_pool = NULL;
+ return err;
+ }
+
+ return 0;
+}
+
/* Initialize a RDMA ring */
static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv,
unsigned int index, unsigned int size,
@@ -2765,10 +2801,17 @@ static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv,
ring->cb_ptr = start_ptr;
ring->end_ptr = end_ptr - 1;
- ret = bcmgenet_alloc_rx_buffers(priv, ring);
+ ret = bcmgenet_rx_ring_create_pool(priv, ring);
if (ret)
return ret;
+ ret = bcmgenet_alloc_rx_buffers(priv, ring);
+ if (ret) {
+ page_pool_destroy(ring->page_pool);
+ ring->page_pool = NULL;
+ return ret;
+ }
+
bcmgenet_init_dim(ring, bcmgenet_dim_work);
bcmgenet_init_rx_coalesce(ring);
@@ -2961,6 +3004,20 @@ static void bcmgenet_fini_rx_napi(struct bcmgenet_priv *priv)
}
}
+static void bcmgenet_destroy_rx_page_pools(struct bcmgenet_priv *priv)
+{
+ struct bcmgenet_rx_ring *ring;
+ unsigned int i;
+
+ for (i = 0; i <= priv->hw_params->rx_queues; ++i) {
+ ring = &priv->rx_rings[i];
+ if (ring->page_pool) {
+ page_pool_destroy(ring->page_pool);
+ ring->page_pool = NULL;
+ }
+ }
+}
+
/* Initialize Rx queues
*
* Queues 0-15 are priority queues. Hardware Filtering Block (HFB) can be
@@ -3032,6 +3089,7 @@ static void bcmgenet_fini_dma(struct bcmgenet_priv *priv)
}
bcmgenet_free_rx_buffers(priv);
+ bcmgenet_destroy_rx_page_pools(priv);
kfree(priv->rx_cbs);
kfree(priv->tx_cbs);
}
@@ -3108,6 +3166,7 @@ static int bcmgenet_init_dma(struct bcmgenet_priv *priv, bool flush_rx)
if (ret) {
netdev_err(priv->dev, "failed to initialize Rx queues\n");
bcmgenet_free_rx_buffers(priv);
+ bcmgenet_destroy_rx_page_pools(priv);
kfree(priv->rx_cbs);
kfree(priv->tx_cbs);
return ret;
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 9e4110c7fdf6..11a0ec563a89 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -15,6 +15,7 @@
#include <linux/phy.h>
#include <linux/dim.h>
#include <linux/ethtool.h>
+#include <net/page_pool/helpers.h>
#include "../unimac.h"
@@ -469,6 +470,8 @@ struct bcmgenet_rx_stats64 {
struct enet_cb {
struct sk_buff *skb;
+ struct page *rx_page;
+ unsigned int rx_page_offset;
void __iomem *bd_addr;
DEFINE_DMA_UNMAP_ADDR(dma_addr);
DEFINE_DMA_UNMAP_LEN(dma_len);
@@ -575,6 +578,7 @@ struct bcmgenet_rx_ring {
struct bcmgenet_net_dim dim;
u32 rx_max_coalesced_frames;
u32 rx_coalesce_usecs;
+ struct page_pool *page_pool;
struct bcmgenet_priv *priv;
};
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH net-next v3 2/6] net: bcmgenet: register xdp_rxq_info for each RX ring
2026-03-19 11:53 [PATCH net-next v3 0/6] net: bcmgenet: add XDP support Nicolai Buchwitz
2026-03-19 11:53 ` [PATCH net-next v3 1/6] net: bcmgenet: convert RX path to page_pool Nicolai Buchwitz
@ 2026-03-19 11:53 ` Nicolai Buchwitz
2026-03-19 21:18 ` Justin Chen
2026-03-19 11:53 ` [PATCH net-next v3 3/6] net: bcmgenet: add basic XDP support (PASS/DROP) Nicolai Buchwitz
` (3 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Nicolai Buchwitz @ 2026-03-19 11:53 UTC (permalink / raw)
To: netdev
Cc: Nicolai Buchwitz, Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, linux-kernel, bpf
Register an xdp_rxq_info per RX ring and associate it with the ring's
page_pool via MEM_TYPE_PAGE_POOL. This is required infrastructure for
XDP program execution: the XDP framework needs to know the memory model
backing each RX queue for correct page lifecycle management.
No functional change - XDP programs are not yet attached or executed.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
.../net/ethernet/broadcom/genet/bcmgenet.c | 22 +++++++++++++++++--
.../net/ethernet/broadcom/genet/bcmgenet.h | 2 ++
2 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 7410034d9bdc..6e610e73e12f 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -2771,16 +2771,32 @@ static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv,
.offset = GENET_XDP_HEADROOM,
.max_len = RX_BUF_LENGTH,
};
+ int err;
ring->page_pool = page_pool_create(&pp_params);
if (IS_ERR(ring->page_pool)) {
- int err = PTR_ERR(ring->page_pool);
-
+ err = PTR_ERR(ring->page_pool);
ring->page_pool = NULL;
return err;
}
+ err = xdp_rxq_info_reg(&ring->xdp_rxq, priv->dev, ring->index, 0);
+ if (err)
+ goto err_free_pp;
+
+ err = xdp_rxq_info_reg_mem_model(&ring->xdp_rxq, MEM_TYPE_PAGE_POOL,
+ ring->page_pool);
+ if (err)
+ goto err_unreg_rxq;
+
return 0;
+
+err_unreg_rxq:
+ xdp_rxq_info_unreg(&ring->xdp_rxq);
+err_free_pp:
+ page_pool_destroy(ring->page_pool);
+ ring->page_pool = NULL;
+ return err;
}
/* Initialize a RDMA ring */
@@ -2807,6 +2823,7 @@ static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv,
ret = bcmgenet_alloc_rx_buffers(priv, ring);
if (ret) {
+ xdp_rxq_info_unreg(&ring->xdp_rxq);
page_pool_destroy(ring->page_pool);
ring->page_pool = NULL;
return ret;
@@ -3012,6 +3029,7 @@ static void bcmgenet_destroy_rx_page_pools(struct bcmgenet_priv *priv)
for (i = 0; i <= priv->hw_params->rx_queues; ++i) {
ring = &priv->rx_rings[i];
if (ring->page_pool) {
+ xdp_rxq_info_unreg(&ring->xdp_rxq);
page_pool_destroy(ring->page_pool);
ring->page_pool = NULL;
}
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 11a0ec563a89..82a6d29f481d 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -16,6 +16,7 @@
#include <linux/dim.h>
#include <linux/ethtool.h>
#include <net/page_pool/helpers.h>
+#include <net/xdp.h>
#include "../unimac.h"
@@ -579,6 +580,7 @@ struct bcmgenet_rx_ring {
u32 rx_max_coalesced_frames;
u32 rx_coalesce_usecs;
struct page_pool *page_pool;
+ struct xdp_rxq_info xdp_rxq;
struct bcmgenet_priv *priv;
};
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH net-next v3 3/6] net: bcmgenet: add basic XDP support (PASS/DROP)
2026-03-19 11:53 [PATCH net-next v3 0/6] net: bcmgenet: add XDP support Nicolai Buchwitz
2026-03-19 11:53 ` [PATCH net-next v3 1/6] net: bcmgenet: convert RX path to page_pool Nicolai Buchwitz
2026-03-19 11:53 ` [PATCH net-next v3 2/6] net: bcmgenet: register xdp_rxq_info for each RX ring Nicolai Buchwitz
@ 2026-03-19 11:53 ` Nicolai Buchwitz
2026-03-19 21:23 ` Justin Chen
2026-03-19 11:53 ` [PATCH net-next v3 4/6] net: bcmgenet: add XDP_TX support Nicolai Buchwitz
` (2 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Nicolai Buchwitz @ 2026-03-19 11:53 UTC (permalink / raw)
To: netdev
Cc: Nicolai Buchwitz, Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, linux-kernel, bpf
Add XDP program attachment via ndo_bpf and execute XDP programs in the
RX path. XDP_PASS builds an SKB from the xdp_buff (handling
xdp_adjust_head/tail), XDP_DROP returns the page to page_pool without
SKB allocation.
XDP_TX and XDP_REDIRECT are not yet supported and return XDP_ABORTED.
Advertise NETDEV_XDP_ACT_BASIC in xdp_features.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
.../net/ethernet/broadcom/genet/bcmgenet.c | 147 +++++++++++++++---
.../net/ethernet/broadcom/genet/bcmgenet.h | 4 +
2 files changed, 133 insertions(+), 18 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 6e610e73e12f..e8f90d6b16a2 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -35,6 +35,8 @@
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/phy.h>
+#include <linux/bpf_trace.h>
+#include <linux/filter.h>
#include <linux/unaligned.h>
@@ -2274,6 +2276,53 @@ static int bcmgenet_rx_refill(struct bcmgenet_rx_ring *ring,
return 0;
}
+static struct sk_buff *bcmgenet_xdp_build_skb(struct bcmgenet_rx_ring *ring,
+ struct xdp_buff *xdp,
+ struct page *rx_page)
+{
+ unsigned int metasize;
+ struct sk_buff *skb;
+
+ skb = napi_build_skb(xdp->data_hard_start, PAGE_SIZE);
+ if (unlikely(!skb))
+ return NULL;
+
+ skb_mark_for_recycle(skb);
+
+ metasize = xdp->data - xdp->data_meta;
+ skb_reserve(skb, xdp->data - xdp->data_hard_start);
+ __skb_put(skb, xdp->data_end - xdp->data);
+
+ if (metasize)
+ skb_metadata_set(skb, metasize);
+
+ return skb;
+}
+
+static unsigned int
+bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring, struct bpf_prog *prog,
+ struct xdp_buff *xdp, struct page *rx_page)
+{
+ unsigned int act;
+
+ act = bpf_prog_run_xdp(prog, xdp);
+
+ switch (act) {
+ case XDP_PASS:
+ return XDP_PASS;
+ case XDP_DROP:
+ page_pool_put_full_page(ring->page_pool, rx_page, true);
+ return XDP_DROP;
+ default:
+ bpf_warn_invalid_xdp_action(ring->priv->dev, prog, act);
+ fallthrough;
+ case XDP_ABORTED:
+ trace_xdp_exception(ring->priv->dev, prog, act);
+ page_pool_put_full_page(ring->page_pool, rx_page, true);
+ return XDP_ABORTED;
+ }
+}
+
/* bcmgenet_desc_rx - descriptor based rx process.
* this could be called from bottom half, or from NAPI polling method.
*/
@@ -2283,6 +2332,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
struct bcmgenet_rx_stats64 *stats = &ring->stats64;
struct bcmgenet_priv *priv = ring->priv;
struct net_device *dev = priv->dev;
+ struct bpf_prog *xdp_prog;
struct enet_cb *cb;
struct sk_buff *skb;
u32 dma_length_status;
@@ -2293,6 +2343,8 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
unsigned int p_index, mask;
unsigned int discards;
+ xdp_prog = READ_ONCE(priv->xdp_prog);
+
/* Clear status before servicing to reduce spurious interrupts */
mask = 1 << (UMAC_IRQ1_RX_INTR_SHIFT + ring->index);
bcmgenet_intrl2_1_writel(priv, mask, INTRL2_CPU_CLEAR);
@@ -2403,26 +2455,52 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
goto next;
} /* error packet */
- /* 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, PAGE_SIZE - GENET_XDP_HEADROOM);
- if (unlikely(!skb)) {
- BCMGENET_STATS64_INC(stats, dropped);
- page_pool_put_full_page(ring->page_pool, rx_page,
- true);
- goto next;
- }
-
- skb_mark_for_recycle(skb);
+ /* XDP: frame data starts after RSB + pad */
+ if (xdp_prog) {
+ struct xdp_buff xdp;
+ unsigned int xdp_act;
+ int pkt_len;
+
+ pkt_len = len - GENET_RSB_PAD;
+ if (priv->crc_fwd_en)
+ pkt_len -= ETH_FCS_LEN;
+
+ xdp_init_buff(&xdp, PAGE_SIZE, &ring->xdp_rxq);
+ xdp_prepare_buff(&xdp, page_address(rx_page),
+ GENET_RX_HEADROOM, pkt_len, true);
+
+ xdp_act = bcmgenet_run_xdp(ring, xdp_prog, &xdp,
+ rx_page);
+ if (xdp_act != XDP_PASS)
+ goto next;
+
+ /* XDP_PASS: build SKB from (possibly modified) xdp */
+ skb = bcmgenet_xdp_build_skb(ring, &xdp, rx_page);
+ if (unlikely(!skb)) {
+ BCMGENET_STATS64_INC(stats, dropped);
+ page_pool_put_full_page(ring->page_pool,
+ rx_page, true);
+ goto next;
+ }
+ } else {
+ /* Build SKB from the page - data starts at
+ * hard_start, frame begins after RSB(64) + pad(2).
+ */
+ skb = napi_build_skb(hard_start,
+ PAGE_SIZE - GENET_XDP_HEADROOM);
+ if (unlikely(!skb)) {
+ BCMGENET_STATS64_INC(stats, dropped);
+ page_pool_put_full_page(ring->page_pool,
+ rx_page, true);
+ goto next;
+ }
- /* Reserve the RSB + pad, then set the data length */
- skb_reserve(skb, GENET_RSB_PAD);
- __skb_put(skb, len - GENET_RSB_PAD);
+ skb_mark_for_recycle(skb);
+ 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);
- len -= ETH_FCS_LEN;
+ if (priv->crc_fwd_en)
+ skb_trim(skb, skb->len - ETH_FCS_LEN);
}
/* Set up checksum offload */
@@ -3743,6 +3821,37 @@ static int bcmgenet_change_carrier(struct net_device *dev, bool new_carrier)
return 0;
}
+static int bcmgenet_xdp_setup(struct net_device *dev,
+ struct netdev_bpf *xdp)
+{
+ struct bcmgenet_priv *priv = netdev_priv(dev);
+ struct bpf_prog *old_prog;
+ struct bpf_prog *prog = xdp->prog;
+
+ if (prog && dev->mtu > PAGE_SIZE - GENET_RX_HEADROOM -
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info))) {
+ NL_SET_ERR_MSG_MOD(xdp->extack,
+ "MTU too large for single-page XDP buffer");
+ return -EOPNOTSUPP;
+ }
+
+ old_prog = xchg(&priv->xdp_prog, prog);
+ if (old_prog)
+ bpf_prog_put(old_prog);
+
+ return 0;
+}
+
+static int bcmgenet_xdp(struct net_device *dev, struct netdev_bpf *xdp)
+{
+ switch (xdp->command) {
+ case XDP_SETUP_PROG:
+ return bcmgenet_xdp_setup(dev, xdp);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
static const struct net_device_ops bcmgenet_netdev_ops = {
.ndo_open = bcmgenet_open,
.ndo_stop = bcmgenet_close,
@@ -3754,6 +3863,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_bpf = bcmgenet_xdp,
};
/* GENET hardware parameters/characteristics */
@@ -4056,6 +4166,7 @@ static int bcmgenet_probe(struct platform_device *pdev)
NETIF_F_RXCSUM;
dev->hw_features |= dev->features;
dev->vlan_features |= dev->features;
+ dev->xdp_features = NETDEV_XDP_ACT_BASIC;
netdev_sw_irq_coalesce_default_on(dev);
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 82a6d29f481d..1459473ac1b0 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -16,6 +16,7 @@
#include <linux/dim.h>
#include <linux/ethtool.h>
#include <net/page_pool/helpers.h>
+#include <linux/bpf.h>
#include <net/xdp.h>
#include "../unimac.h"
@@ -671,6 +672,9 @@ struct bcmgenet_priv {
u8 sopass[SOPASS_MAX];
struct bcmgenet_mib_counters mib;
+
+ /* XDP */
+ struct bpf_prog *xdp_prog;
};
static inline bool bcmgenet_has_40bits(struct bcmgenet_priv *priv)
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH net-next v3 4/6] net: bcmgenet: add XDP_TX support
2026-03-19 11:53 [PATCH net-next v3 0/6] net: bcmgenet: add XDP support Nicolai Buchwitz
` (2 preceding siblings ...)
2026-03-19 11:53 ` [PATCH net-next v3 3/6] net: bcmgenet: add basic XDP support (PASS/DROP) Nicolai Buchwitz
@ 2026-03-19 11:53 ` Nicolai Buchwitz
2026-03-19 21:31 ` Justin Chen
2026-03-20 17:02 ` Simon Horman
2026-03-19 11:53 ` [PATCH net-next v3 5/6] net: bcmgenet: add XDP_REDIRECT and ndo_xdp_xmit support Nicolai Buchwitz
2026-03-19 11:53 ` [PATCH net-next v3 6/6] net: bcmgenet: add XDP statistics counters Nicolai Buchwitz
5 siblings, 2 replies; 13+ messages in thread
From: Nicolai Buchwitz @ 2026-03-19 11:53 UTC (permalink / raw)
To: netdev
Cc: Nicolai Buchwitz, Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, linux-kernel, bpf
Implement XDP_TX using ring 16 (DESC_INDEX), the hardware default
descriptor ring, dedicated to XDP TX for isolation from SKB TX queues.
Ring 16 gets 32 BDs carved from ring 0's allocation. TX completion is
piggybacked on RX NAPI poll since ring 16's INTRL2_1 bit collides with
RX ring 0, similar to how bnxt, ice, and other XDP drivers handle TX
completion within the RX poll path.
The GENET MAC has TBUF_64B_EN set globally, requiring every TX buffer
to start with a 64-byte struct status_64 (TSB). For local XDP_TX, the
TSB is prepended by backing xdp->data into the RSB area (unused after
BPF execution) and zeroing it. For foreign frames (ndo_xdp_xmit), the
TSB is written into the xdp_frame headroom.
The page_pool DMA direction is changed from DMA_FROM_DEVICE to
DMA_BIDIRECTIONAL to allow TX reuse of the existing DMA mapping.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
.../net/ethernet/broadcom/genet/bcmgenet.c | 195 ++++++++++++++++--
.../net/ethernet/broadcom/genet/bcmgenet.h | 4 +-
2 files changed, 181 insertions(+), 18 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index e8f90d6b16a2..54df1694f1cd 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -48,8 +48,10 @@
#define GENET_Q0_RX_BD_CNT \
(TOTAL_DESC - priv->hw_params->rx_queues * priv->hw_params->rx_bds_per_q)
+#define GENET_Q16_TX_BD_CNT 64
#define GENET_Q0_TX_BD_CNT \
- (TOTAL_DESC - priv->hw_params->tx_queues * priv->hw_params->tx_bds_per_q)
+ (TOTAL_DESC - priv->hw_params->tx_queues * priv->hw_params->tx_bds_per_q \
+ - GENET_Q16_TX_BD_CNT)
#define RX_BUF_LENGTH 2048
#define SKB_ALIGNMENT 32
@@ -1893,6 +1895,14 @@ static struct sk_buff *bcmgenet_free_tx_cb(struct device *dev,
if (cb == GENET_CB(skb)->last_cb)
return skb;
+ } else if (cb->xdpf) {
+ if (cb->xdp_dma_map)
+ dma_unmap_single(dev, dma_unmap_addr(cb, dma_addr),
+ dma_unmap_len(cb, dma_len),
+ DMA_TO_DEVICE);
+ dma_unmap_addr_set(cb, dma_addr, 0);
+ xdp_return_frame(cb->xdpf);
+ cb->xdpf = NULL;
} else if (dma_unmap_addr(cb, dma_addr)) {
dma_unmap_page(dev,
dma_unmap_addr(cb, dma_addr),
@@ -1927,8 +1937,13 @@ static unsigned int __bcmgenet_tx_reclaim(struct net_device *dev,
unsigned int c_index;
struct sk_buff *skb;
- /* Clear status before servicing to reduce spurious interrupts */
- bcmgenet_intrl2_1_writel(priv, (1 << ring->index), INTRL2_CPU_CLEAR);
+ /* Clear status before servicing to reduce spurious interrupts.
+ * Ring DESC_INDEX (XDP TX) has no interrupt; skip the clear to
+ * avoid clobbering RX ring 0's bit at the same position.
+ */
+ if (ring->index != DESC_INDEX)
+ bcmgenet_intrl2_1_writel(priv, BIT(ring->index),
+ INTRL2_CPU_CLEAR);
/* Compute how many buffers are transmitted since last xmit call */
c_index = bcmgenet_tdma_ring_readl(priv, ring->index, TDMA_CONS_INDEX)
@@ -1964,8 +1979,11 @@ static unsigned int __bcmgenet_tx_reclaim(struct net_device *dev,
u64_stats_add(&stats->bytes, bytes_compl);
u64_stats_update_end(&stats->syncp);
- netdev_tx_completed_queue(netdev_get_tx_queue(dev, ring->index),
- pkts_compl, bytes_compl);
+ /* Ring DESC_INDEX (XDP TX) has no netdev TX queue; skip BQL */
+ if (ring->index != DESC_INDEX)
+ netdev_tx_completed_queue(netdev_get_tx_queue(dev,
+ ring->index),
+ pkts_compl, bytes_compl);
return txbds_processed;
}
@@ -2042,6 +2060,9 @@ static void bcmgenet_tx_reclaim_all(struct net_device *dev)
do {
bcmgenet_tx_reclaim(dev, &priv->tx_rings[i++], true);
} while (i <= priv->hw_params->tx_queues && netif_is_multiqueue(dev));
+
+ /* Also reclaim XDP TX ring */
+ bcmgenet_tx_reclaim(dev, &priv->tx_rings[DESC_INDEX], true);
}
/* Reallocate the SKB to put enough headroom in front of it and insert
@@ -2299,10 +2320,96 @@ static struct sk_buff *bcmgenet_xdp_build_skb(struct bcmgenet_rx_ring *ring,
return skb;
}
+static bool
+bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
+ struct xdp_frame *xdpf, bool dma_map)
+{
+ struct bcmgenet_tx_ring *ring = &priv->tx_rings[DESC_INDEX];
+ struct device *kdev = &priv->pdev->dev;
+ struct enet_cb *tx_cb_ptr;
+ dma_addr_t mapping;
+ unsigned int dma_len;
+ u32 len_stat;
+
+ spin_lock(&ring->lock);
+
+ if (ring->free_bds < 1) {
+ spin_unlock(&ring->lock);
+ return false;
+ }
+
+ tx_cb_ptr = bcmgenet_get_txcb(priv, ring);
+
+ if (dma_map) {
+ void *tsb_start;
+
+ /* The GENET MAC has TBUF_64B_EN set globally, so hardware
+ * expects a 64-byte TSB prefix on every TX buffer. For
+ * redirected frames (ndo_xdp_xmit) we prepend a zeroed TSB
+ * using the frame's headroom.
+ */
+ if (unlikely(xdpf->headroom < sizeof(struct status_64))) {
+ bcmgenet_put_txcb(priv, ring);
+ spin_unlock(&ring->lock);
+ return false;
+ }
+
+ tsb_start = xdpf->data - sizeof(struct status_64);
+ memset(tsb_start, 0, sizeof(struct status_64));
+
+ dma_len = xdpf->len + sizeof(struct status_64);
+ mapping = dma_map_single(kdev, tsb_start, dma_len,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(kdev, mapping)) {
+ tx_cb_ptr->skb = NULL;
+ tx_cb_ptr->xdpf = NULL;
+ bcmgenet_put_txcb(priv, ring);
+ spin_unlock(&ring->lock);
+ return false;
+ }
+ } else {
+ struct page *page = virt_to_page(xdpf->data);
+
+ /* For local XDP_TX the caller already prepended the TSB
+ * into xdpf->data/len, so dma_len == xdpf->len.
+ */
+ dma_len = xdpf->len;
+ mapping = page_pool_get_dma_addr(page) +
+ sizeof(*xdpf) + xdpf->headroom;
+ dma_sync_single_for_device(kdev, mapping, dma_len,
+ DMA_BIDIRECTIONAL);
+ }
+
+ dma_unmap_addr_set(tx_cb_ptr, dma_addr, mapping);
+ dma_unmap_len_set(tx_cb_ptr, dma_len, dma_len);
+ tx_cb_ptr->skb = NULL;
+ tx_cb_ptr->xdpf = xdpf;
+ tx_cb_ptr->xdp_dma_map = dma_map;
+
+ len_stat = (dma_len << DMA_BUFLENGTH_SHIFT) |
+ (priv->hw_params->qtag_mask << DMA_TX_QTAG_SHIFT) |
+ DMA_TX_APPEND_CRC | DMA_SOP | DMA_EOP;
+
+ dmadesc_set(priv, tx_cb_ptr->bd_addr, mapping, len_stat);
+
+ ring->free_bds--;
+ ring->prod_index++;
+ ring->prod_index &= DMA_P_INDEX_MASK;
+
+ bcmgenet_tdma_ring_writel(priv, ring->index, ring->prod_index,
+ TDMA_PROD_INDEX);
+
+ spin_unlock(&ring->lock);
+
+ return true;
+}
+
static unsigned int
bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring, struct bpf_prog *prog,
struct xdp_buff *xdp, struct page *rx_page)
{
+ struct bcmgenet_priv *priv = ring->priv;
+ struct xdp_frame *xdpf;
unsigned int act;
act = bpf_prog_run_xdp(prog, xdp);
@@ -2310,14 +2417,33 @@ bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring, struct bpf_prog *prog,
switch (act) {
case XDP_PASS:
return XDP_PASS;
+ case XDP_TX:
+ /* Prepend a zeroed TSB (Transmit Status Block). The GENET
+ * MAC has TBUF_64B_EN set globally, so hardware expects every
+ * TX buffer to begin with a 64-byte struct status_64. Back
+ * up xdp->data into the RSB area (which is no longer needed
+ * after the BPF program ran) and zero it.
+ */
+ xdp->data -= sizeof(struct status_64);
+ xdp->data_meta -= sizeof(struct status_64);
+ memset(xdp->data, 0, sizeof(struct status_64));
+
+ xdpf = xdp_convert_buff_to_frame(xdp);
+ if (unlikely(!xdpf) ||
+ unlikely(!bcmgenet_xdp_xmit_frame(priv, xdpf, false))) {
+ page_pool_put_full_page(ring->page_pool, rx_page,
+ true);
+ return XDP_DROP;
+ }
+ return XDP_TX;
case XDP_DROP:
page_pool_put_full_page(ring->page_pool, rx_page, true);
return XDP_DROP;
default:
- bpf_warn_invalid_xdp_action(ring->priv->dev, prog, act);
+ bpf_warn_invalid_xdp_action(priv->dev, prog, act);
fallthrough;
case XDP_ABORTED:
- trace_xdp_exception(ring->priv->dev, prog, act);
+ trace_xdp_exception(priv->dev, prog, act);
page_pool_put_full_page(ring->page_pool, rx_page, true);
return XDP_ABORTED;
}
@@ -2555,9 +2681,15 @@ static int bcmgenet_rx_poll(struct napi_struct *napi, int budget)
{
struct bcmgenet_rx_ring *ring = container_of(napi,
struct bcmgenet_rx_ring, napi);
+ struct bcmgenet_priv *priv = ring->priv;
struct dim_sample dim_sample = {};
unsigned int work_done;
+ /* Reclaim completed XDP TX frames (ring 16 has no interrupt) */
+ if (priv->xdp_prog)
+ bcmgenet_tx_reclaim(priv->dev,
+ &priv->tx_rings[DESC_INDEX], false);
+
work_done = bcmgenet_desc_rx(ring, budget);
if (work_done < budget && napi_complete_done(napi, work_done))
@@ -2832,8 +2964,11 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv,
bcmgenet_tdma_ring_writel(priv, index, end_ptr * words_per_bd - 1,
DMA_END_ADDR);
- /* Initialize Tx NAPI */
- netif_napi_add_tx(priv->dev, &ring->napi, bcmgenet_tx_poll);
+ /* Initialize Tx NAPI for priority queues only; ring DESC_INDEX
+ * (XDP TX) has its completions handled inline in RX NAPI.
+ */
+ if (index != DESC_INDEX)
+ netif_napi_add_tx(priv->dev, &ring->napi, bcmgenet_tx_poll);
}
static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv,
@@ -2845,7 +2980,7 @@ static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv,
.pool_size = ring->size,
.nid = NUMA_NO_NODE,
.dev = &priv->pdev->dev,
- .dma_dir = DMA_FROM_DEVICE,
+ .dma_dir = DMA_BIDIRECTIONAL,
.offset = GENET_XDP_HEADROOM,
.max_len = RX_BUF_LENGTH,
};
@@ -2977,6 +3112,7 @@ static int bcmgenet_tdma_disable(struct bcmgenet_priv *priv)
reg = bcmgenet_tdma_readl(priv, DMA_CTRL);
mask = (1 << (priv->hw_params->tx_queues + 1)) - 1;
+ mask |= BIT(DESC_INDEX);
mask = (mask << DMA_RING_BUF_EN_SHIFT) | DMA_EN;
reg &= ~mask;
bcmgenet_tdma_writel(priv, reg, DMA_CTRL);
@@ -3022,14 +3158,18 @@ static int bcmgenet_rdma_disable(struct bcmgenet_priv *priv)
* with queue 1 being the highest priority queue.
*
* Queue 0 is the default Tx queue with
- * GENET_Q0_TX_BD_CNT = 256 - 4 * 32 = 128 descriptors.
+ * GENET_Q0_TX_BD_CNT = 256 - 4 * 32 - 32 = 96 descriptors.
+ *
+ * Ring 16 (DESC_INDEX) is used for XDP TX with
+ * GENET_Q16_TX_BD_CNT = 32 descriptors.
*
* The transmit control block pool is then partitioned as follows:
- * - Tx queue 0 uses tx_cbs[0..127]
- * - Tx queue 1 uses tx_cbs[128..159]
- * - Tx queue 2 uses tx_cbs[160..191]
- * - Tx queue 3 uses tx_cbs[192..223]
- * - Tx queue 4 uses tx_cbs[224..255]
+ * - Tx queue 0 uses tx_cbs[0..95]
+ * - Tx queue 1 uses tx_cbs[96..127]
+ * - Tx queue 2 uses tx_cbs[128..159]
+ * - Tx queue 3 uses tx_cbs[160..191]
+ * - Tx queue 4 uses tx_cbs[192..223]
+ * - Tx ring 16 uses tx_cbs[224..255]
*/
static void bcmgenet_init_tx_queues(struct net_device *dev)
{
@@ -3050,13 +3190,18 @@ static void bcmgenet_init_tx_queues(struct net_device *dev)
<< DMA_PRIO_REG_SHIFT(i);
}
+ /* Initialize ring 16 (descriptor ring) for XDP TX */
+ bcmgenet_init_tx_ring(priv, DESC_INDEX, GENET_Q16_TX_BD_CNT,
+ TOTAL_DESC - GENET_Q16_TX_BD_CNT, TOTAL_DESC);
+
/* Set Tx queue priorities */
bcmgenet_tdma_writel(priv, dma_priority[0], DMA_PRIORITY_0);
bcmgenet_tdma_writel(priv, dma_priority[1], DMA_PRIORITY_1);
bcmgenet_tdma_writel(priv, dma_priority[2], DMA_PRIORITY_2);
- /* Configure Tx queues as descriptor rings */
+ /* Configure Tx queues as descriptor rings, including ring 16 */
ring_mask = (1 << (priv->hw_params->tx_queues + 1)) - 1;
+ ring_mask |= BIT(DESC_INDEX);
bcmgenet_tdma_writel(priv, ring_mask, DMA_RING_CFG);
/* Enable Tx rings */
@@ -3770,6 +3915,21 @@ static void bcmgenet_get_stats64(struct net_device *dev,
stats->tx_dropped += tx_dropped;
}
+ /* Include XDP TX ring (DESC_INDEX) stats */
+ tx_stats = &priv->tx_rings[DESC_INDEX].stats64;
+ do {
+ start = u64_stats_fetch_begin(&tx_stats->syncp);
+ tx_bytes = u64_stats_read(&tx_stats->bytes);
+ tx_packets = u64_stats_read(&tx_stats->packets);
+ tx_errors = u64_stats_read(&tx_stats->errors);
+ tx_dropped = u64_stats_read(&tx_stats->dropped);
+ } while (u64_stats_fetch_retry(&tx_stats->syncp, start));
+
+ stats->tx_bytes += tx_bytes;
+ stats->tx_packets += tx_packets;
+ stats->tx_errors += tx_errors;
+ stats->tx_dropped += tx_dropped;
+
for (q = 0; q <= priv->hw_params->rx_queues; q++) {
rx_stats = &priv->rx_rings[q].stats64;
do {
@@ -4273,6 +4433,7 @@ static int bcmgenet_probe(struct platform_device *pdev)
u64_stats_init(&priv->rx_rings[i].stats64.syncp);
for (i = 0; i <= priv->hw_params->tx_queues; i++)
u64_stats_init(&priv->tx_rings[i].stats64.syncp);
+ u64_stats_init(&priv->tx_rings[DESC_INDEX].stats64.syncp);
/* libphy will determine the link state */
netif_carrier_off(dev);
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index 1459473ac1b0..fce598c29a96 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -472,6 +472,8 @@ struct bcmgenet_rx_stats64 {
struct enet_cb {
struct sk_buff *skb;
+ struct xdp_frame *xdpf;
+ bool xdp_dma_map;
struct page *rx_page;
unsigned int rx_page_offset;
void __iomem *bd_addr;
@@ -610,7 +612,7 @@ struct bcmgenet_priv {
struct enet_cb *tx_cbs;
unsigned int num_tx_bds;
- struct bcmgenet_tx_ring tx_rings[GENET_MAX_MQ_CNT + 1];
+ struct bcmgenet_tx_ring tx_rings[DESC_INDEX + 1];
/* receive variables */
void __iomem *rx_bds;
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH net-next v3 5/6] net: bcmgenet: add XDP_REDIRECT and ndo_xdp_xmit support
2026-03-19 11:53 [PATCH net-next v3 0/6] net: bcmgenet: add XDP support Nicolai Buchwitz
` (3 preceding siblings ...)
2026-03-19 11:53 ` [PATCH net-next v3 4/6] net: bcmgenet: add XDP_TX support Nicolai Buchwitz
@ 2026-03-19 11:53 ` Nicolai Buchwitz
2026-03-19 21:34 ` Justin Chen
2026-03-19 11:53 ` [PATCH net-next v3 6/6] net: bcmgenet: add XDP statistics counters Nicolai Buchwitz
5 siblings, 1 reply; 13+ messages in thread
From: Nicolai Buchwitz @ 2026-03-19 11:53 UTC (permalink / raw)
To: netdev
Cc: Nicolai Buchwitz, Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, linux-kernel, bpf
Add XDP_REDIRECT support and implement ndo_xdp_xmit for receiving
redirected frames from other devices.
XDP_REDIRECT calls xdp_do_redirect() in the RX path with
xdp_do_flush() once per NAPI poll cycle. ndo_xdp_xmit batches frames
into ring 16 under a single spinlock acquisition.
Advertise NETDEV_XDP_ACT_REDIRECT and NETDEV_XDP_ACT_NDO_XMIT in
xdp_features.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
.../net/ethernet/broadcom/genet/bcmgenet.c | 98 +++++++++++++++----
1 file changed, 80 insertions(+), 18 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 54df1694f1cd..a5ad25ccb148 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -2320,23 +2320,23 @@ static struct sk_buff *bcmgenet_xdp_build_skb(struct bcmgenet_rx_ring *ring,
return skb;
}
+/* Submit a single XDP frame to the TX ring. Caller must hold ring->lock.
+ * Returns true on success. Does not ring the doorbell - caller must
+ * write TDMA_PROD_INDEX after batching.
+ */
static bool
bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
+ struct bcmgenet_tx_ring *ring,
struct xdp_frame *xdpf, bool dma_map)
{
- struct bcmgenet_tx_ring *ring = &priv->tx_rings[DESC_INDEX];
struct device *kdev = &priv->pdev->dev;
struct enet_cb *tx_cb_ptr;
dma_addr_t mapping;
unsigned int dma_len;
u32 len_stat;
- spin_lock(&ring->lock);
-
- if (ring->free_bds < 1) {
- spin_unlock(&ring->lock);
+ if (ring->free_bds < 1)
return false;
- }
tx_cb_ptr = bcmgenet_get_txcb(priv, ring);
@@ -2350,7 +2350,6 @@ bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
*/
if (unlikely(xdpf->headroom < sizeof(struct status_64))) {
bcmgenet_put_txcb(priv, ring);
- spin_unlock(&ring->lock);
return false;
}
@@ -2364,7 +2363,6 @@ bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
tx_cb_ptr->skb = NULL;
tx_cb_ptr->xdpf = NULL;
bcmgenet_put_txcb(priv, ring);
- spin_unlock(&ring->lock);
return false;
}
} else {
@@ -2396,12 +2394,15 @@ bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
ring->prod_index++;
ring->prod_index &= DMA_P_INDEX_MASK;
+ return true;
+}
+
+static void
+bcmgenet_xdp_ring_doorbell(struct bcmgenet_priv *priv,
+ struct bcmgenet_tx_ring *ring)
+{
bcmgenet_tdma_ring_writel(priv, ring->index, ring->prod_index,
TDMA_PROD_INDEX);
-
- spin_unlock(&ring->lock);
-
- return true;
}
static unsigned int
@@ -2417,7 +2418,11 @@ bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring, struct bpf_prog *prog,
switch (act) {
case XDP_PASS:
return XDP_PASS;
- case XDP_TX:
+ case XDP_TX: {
+ struct bcmgenet_tx_ring *tx_ring;
+
+ tx_ring = &priv->tx_rings[DESC_INDEX];
+
/* Prepend a zeroed TSB (Transmit Status Block). The GENET
* MAC has TBUF_64B_EN set globally, so hardware expects every
* TX buffer to begin with a 64-byte struct status_64. Back
@@ -2429,14 +2434,26 @@ bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring, struct bpf_prog *prog,
memset(xdp->data, 0, sizeof(struct status_64));
xdpf = xdp_convert_buff_to_frame(xdp);
- if (unlikely(!xdpf) ||
- unlikely(!bcmgenet_xdp_xmit_frame(priv, xdpf, false))) {
- page_pool_put_full_page(ring->page_pool, rx_page,
- true);
+ if (unlikely(!xdpf))
+ goto drop_page;
+
+ spin_lock(&tx_ring->lock);
+ if (unlikely(!bcmgenet_xdp_xmit_frame(priv, tx_ring,
+ xdpf, false))) {
+ spin_unlock(&tx_ring->lock);
+ xdp_return_frame_rx_napi(xdpf);
return XDP_DROP;
}
+ bcmgenet_xdp_ring_doorbell(priv, tx_ring);
+ spin_unlock(&tx_ring->lock);
return XDP_TX;
+ }
+ case XDP_REDIRECT:
+ if (unlikely(xdp_do_redirect(priv->dev, xdp, prog)))
+ goto drop_page;
+ return XDP_REDIRECT;
case XDP_DROP:
+drop_page:
page_pool_put_full_page(ring->page_pool, rx_page, true);
return XDP_DROP;
default:
@@ -2459,6 +2476,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
struct bcmgenet_priv *priv = ring->priv;
struct net_device *dev = priv->dev;
struct bpf_prog *xdp_prog;
+ bool xdp_flush = false;
struct enet_cb *cb;
struct sk_buff *skb;
u32 dma_length_status;
@@ -2597,6 +2615,8 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
xdp_act = bcmgenet_run_xdp(ring, xdp_prog, &xdp,
rx_page);
+ if (xdp_act == XDP_REDIRECT)
+ xdp_flush = true;
if (xdp_act != XDP_PASS)
goto next;
@@ -2670,6 +2690,9 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
bcmgenet_rdma_ring_writel(priv, ring->index, ring->c_index, RDMA_CONS_INDEX);
}
+ if (xdp_flush)
+ xdp_do_flush();
+
ring->dim.bytes = bytes_processed;
ring->dim.packets = rxpktprocessed;
@@ -3995,10 +4018,16 @@ static int bcmgenet_xdp_setup(struct net_device *dev,
return -EOPNOTSUPP;
}
+ if (!prog)
+ xdp_features_clear_redirect_target(dev);
+
old_prog = xchg(&priv->xdp_prog, prog);
if (old_prog)
bpf_prog_put(old_prog);
+ if (prog)
+ xdp_features_set_redirect_target(dev, false);
+
return 0;
}
@@ -4012,6 +4041,37 @@ static int bcmgenet_xdp(struct net_device *dev, struct netdev_bpf *xdp)
}
}
+static int
+bcmgenet_xdp_xmit(struct net_device *dev, int num_frames,
+ struct xdp_frame **frames, u32 flags)
+{
+ struct bcmgenet_priv *priv = netdev_priv(dev);
+ struct bcmgenet_tx_ring *ring = &priv->tx_rings[DESC_INDEX];
+ int sent = 0;
+ int i;
+
+ if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK))
+ return -EINVAL;
+
+ if (unlikely(!netif_running(dev)))
+ return -ENETDOWN;
+
+ spin_lock(&ring->lock);
+
+ for (i = 0; i < num_frames; i++) {
+ if (!bcmgenet_xdp_xmit_frame(priv, ring, frames[i], true))
+ break;
+ sent++;
+ }
+
+ if (sent)
+ bcmgenet_xdp_ring_doorbell(priv, ring);
+
+ spin_unlock(&ring->lock);
+
+ return sent;
+}
+
static const struct net_device_ops bcmgenet_netdev_ops = {
.ndo_open = bcmgenet_open,
.ndo_stop = bcmgenet_close,
@@ -4024,6 +4084,7 @@ static const struct net_device_ops bcmgenet_netdev_ops = {
.ndo_get_stats64 = bcmgenet_get_stats64,
.ndo_change_carrier = bcmgenet_change_carrier,
.ndo_bpf = bcmgenet_xdp,
+ .ndo_xdp_xmit = bcmgenet_xdp_xmit,
};
/* GENET hardware parameters/characteristics */
@@ -4326,7 +4387,8 @@ static int bcmgenet_probe(struct platform_device *pdev)
NETIF_F_RXCSUM;
dev->hw_features |= dev->features;
dev->vlan_features |= dev->features;
- dev->xdp_features = NETDEV_XDP_ACT_BASIC;
+ dev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT |
+ NETDEV_XDP_ACT_NDO_XMIT;
netdev_sw_irq_coalesce_default_on(dev);
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH net-next v3 6/6] net: bcmgenet: add XDP statistics counters
2026-03-19 11:53 [PATCH net-next v3 0/6] net: bcmgenet: add XDP support Nicolai Buchwitz
` (4 preceding siblings ...)
2026-03-19 11:53 ` [PATCH net-next v3 5/6] net: bcmgenet: add XDP_REDIRECT and ndo_xdp_xmit support Nicolai Buchwitz
@ 2026-03-19 11:53 ` Nicolai Buchwitz
5 siblings, 0 replies; 13+ messages in thread
From: Nicolai Buchwitz @ 2026-03-19 11:53 UTC (permalink / raw)
To: netdev
Cc: Nicolai Buchwitz, Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, linux-kernel, bpf
Expose per-action XDP counters via ethtool -S: xdp_pass, xdp_drop,
xdp_tx, xdp_tx_err, xdp_redirect, and xdp_redirect_err.
These use the existing soft MIB infrastructure and are incremented in
bcmgenet_run_xdp() alongside the existing driver statistics.
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
---
drivers/net/ethernet/broadcom/genet/bcmgenet.c | 17 ++++++++++++++++-
drivers/net/ethernet/broadcom/genet/bcmgenet.h | 6 ++++++
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index a5ad25ccb148..f9e7ca1645a0 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -1170,6 +1170,13 @@ static const struct bcmgenet_stats bcmgenet_gstrings_stats[] = {
STAT_GENET_SOFT_MIB("tx_realloc_tsb", mib.tx_realloc_tsb),
STAT_GENET_SOFT_MIB("tx_realloc_tsb_failed",
mib.tx_realloc_tsb_failed),
+ /* XDP counters */
+ STAT_GENET_SOFT_MIB("xdp_pass", mib.xdp_pass),
+ STAT_GENET_SOFT_MIB("xdp_drop", mib.xdp_drop),
+ STAT_GENET_SOFT_MIB("xdp_tx", mib.xdp_tx),
+ STAT_GENET_SOFT_MIB("xdp_tx_err", mib.xdp_tx_err),
+ STAT_GENET_SOFT_MIB("xdp_redirect", mib.xdp_redirect),
+ STAT_GENET_SOFT_MIB("xdp_redirect_err", mib.xdp_redirect_err),
/* Per TX queues */
STAT_GENET_Q(0),
STAT_GENET_Q(1),
@@ -2417,6 +2424,7 @@ bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring, struct bpf_prog *prog,
switch (act) {
case XDP_PASS:
+ priv->mib.xdp_pass++;
return XDP_PASS;
case XDP_TX: {
struct bcmgenet_tx_ring *tx_ring;
@@ -2442,18 +2450,24 @@ bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring, struct bpf_prog *prog,
xdpf, false))) {
spin_unlock(&tx_ring->lock);
xdp_return_frame_rx_napi(xdpf);
+ priv->mib.xdp_tx_err++;
return XDP_DROP;
}
bcmgenet_xdp_ring_doorbell(priv, tx_ring);
spin_unlock(&tx_ring->lock);
+ priv->mib.xdp_tx++;
return XDP_TX;
}
case XDP_REDIRECT:
- if (unlikely(xdp_do_redirect(priv->dev, xdp, prog)))
+ if (unlikely(xdp_do_redirect(priv->dev, xdp, prog))) {
+ priv->mib.xdp_redirect_err++;
goto drop_page;
+ }
+ priv->mib.xdp_redirect++;
return XDP_REDIRECT;
case XDP_DROP:
drop_page:
+ priv->mib.xdp_drop++;
page_pool_put_full_page(ring->page_pool, rx_page, true);
return XDP_DROP;
default:
@@ -2461,6 +2475,7 @@ bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring, struct bpf_prog *prog,
fallthrough;
case XDP_ABORTED:
trace_xdp_exception(priv->dev, prog, act);
+ priv->mib.xdp_drop++;
page_pool_put_full_page(ring->page_pool, rx_page, true);
return XDP_ABORTED;
}
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index fce598c29a96..9e4f5bac38e7 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -156,6 +156,12 @@ struct bcmgenet_mib_counters {
u32 tx_dma_failed;
u32 tx_realloc_tsb;
u32 tx_realloc_tsb_failed;
+ u32 xdp_pass;
+ u32 xdp_drop;
+ u32 xdp_tx;
+ u32 xdp_tx_err;
+ u32 xdp_redirect;
+ u32 xdp_redirect_err;
};
struct bcmgenet_tx_stats64 {
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v3 2/6] net: bcmgenet: register xdp_rxq_info for each RX ring
2026-03-19 11:53 ` [PATCH net-next v3 2/6] net: bcmgenet: register xdp_rxq_info for each RX ring Nicolai Buchwitz
@ 2026-03-19 21:18 ` Justin Chen
0 siblings, 0 replies; 13+ messages in thread
From: Justin Chen @ 2026-03-19 21:18 UTC (permalink / raw)
To: Nicolai Buchwitz, netdev
Cc: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, linux-kernel, bpf
On 3/19/26 4:53 AM, Nicolai Buchwitz wrote:
> Register an xdp_rxq_info per RX ring and associate it with the ring's
> page_pool via MEM_TYPE_PAGE_POOL. This is required infrastructure for
> XDP program execution: the XDP framework needs to know the memory model
> backing each RX queue for correct page lifecycle management.
>
> No functional change - XDP programs are not yet attached or executed.
>
> Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
> ---
> .../net/ethernet/broadcom/genet/bcmgenet.c | 22 +++++++++++++++++--
> .../net/ethernet/broadcom/genet/bcmgenet.h | 2 ++
> 2 files changed, 22 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> index 7410034d9bdc..6e610e73e12f 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> @@ -2771,16 +2771,32 @@ static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv,
> .offset = GENET_XDP_HEADROOM,
> .max_len = RX_BUF_LENGTH,
> };
> + int err;
>
> ring->page_pool = page_pool_create(&pp_params);
> if (IS_ERR(ring->page_pool)) {
> - int err = PTR_ERR(ring->page_pool);
> -
> + err = PTR_ERR(ring->page_pool);
This should go in the patch before this.
> ring->page_pool = NULL;
> return err;
> }
>
> + err = xdp_rxq_info_reg(&ring->xdp_rxq, priv->dev, ring->index, 0);
> + if (err)
> + goto err_free_pp;
> +
> + err = xdp_rxq_info_reg_mem_model(&ring->xdp_rxq, MEM_TYPE_PAGE_POOL,
> + ring->page_pool);
> + if (err)
> + goto err_unreg_rxq;
> +
> return 0;
> +
> +err_unreg_rxq:
> + xdp_rxq_info_unreg(&ring->xdp_rxq);
> +err_free_pp:
> + page_pool_destroy(ring->page_pool);
> + ring->page_pool = NULL;
> + return err;
> }
>
> /* Initialize a RDMA ring */
> @@ -2807,6 +2823,7 @@ static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv,
>
> ret = bcmgenet_alloc_rx_buffers(priv, ring);
> if (ret) {
> + xdp_rxq_info_unreg(&ring->xdp_rxq);
> page_pool_destroy(ring->page_pool);
> ring->page_pool = NULL;
> return ret;
> @@ -3012,6 +3029,7 @@ static void bcmgenet_destroy_rx_page_pools(struct bcmgenet_priv *priv)
> for (i = 0; i <= priv->hw_params->rx_queues; ++i) {
> ring = &priv->rx_rings[i];
> if (ring->page_pool) {
> + xdp_rxq_info_unreg(&ring->xdp_rxq);
> page_pool_destroy(ring->page_pool);
> ring->page_pool = NULL;
> }
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
> index 11a0ec563a89..82a6d29f481d 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
> @@ -16,6 +16,7 @@
> #include <linux/dim.h>
> #include <linux/ethtool.h>
> #include <net/page_pool/helpers.h>
> +#include <net/xdp.h>
>
> #include "../unimac.h"
>
> @@ -579,6 +580,7 @@ struct bcmgenet_rx_ring {
> u32 rx_max_coalesced_frames;
> u32 rx_coalesce_usecs;
> struct page_pool *page_pool;
> + struct xdp_rxq_info xdp_rxq;
> struct bcmgenet_priv *priv;
> };
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v3 3/6] net: bcmgenet: add basic XDP support (PASS/DROP)
2026-03-19 11:53 ` [PATCH net-next v3 3/6] net: bcmgenet: add basic XDP support (PASS/DROP) Nicolai Buchwitz
@ 2026-03-19 21:23 ` Justin Chen
0 siblings, 0 replies; 13+ messages in thread
From: Justin Chen @ 2026-03-19 21:23 UTC (permalink / raw)
To: Nicolai Buchwitz, netdev
Cc: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, linux-kernel, bpf
On 3/19/26 4:53 AM, Nicolai Buchwitz wrote:
> Add XDP program attachment via ndo_bpf and execute XDP programs in the
> RX path. XDP_PASS builds an SKB from the xdp_buff (handling
> xdp_adjust_head/tail), XDP_DROP returns the page to page_pool without
> SKB allocation.
>
> XDP_TX and XDP_REDIRECT are not yet supported and return XDP_ABORTED.
>
> Advertise NETDEV_XDP_ACT_BASIC in xdp_features.
>
> Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
> ---
> .../net/ethernet/broadcom/genet/bcmgenet.c | 147 +++++++++++++++---
> .../net/ethernet/broadcom/genet/bcmgenet.h | 4 +
> 2 files changed, 133 insertions(+), 18 deletions(-)
>
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> index 6e610e73e12f..e8f90d6b16a2 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> @@ -35,6 +35,8 @@
> #include <linux/ip.h>
> #include <linux/ipv6.h>
> #include <linux/phy.h>
> +#include <linux/bpf_trace.h>
> +#include <linux/filter.h>
>
> #include <linux/unaligned.h>
>
> @@ -2274,6 +2276,53 @@ static int bcmgenet_rx_refill(struct bcmgenet_rx_ring *ring,
> return 0;
> }
>
> +static struct sk_buff *bcmgenet_xdp_build_skb(struct bcmgenet_rx_ring *ring,
> + struct xdp_buff *xdp,
> + struct page *rx_page)
> +{
> + unsigned int metasize;
> + struct sk_buff *skb;
> +
> + skb = napi_build_skb(xdp->data_hard_start, PAGE_SIZE);
> + if (unlikely(!skb))
> + return NULL;
> +
> + skb_mark_for_recycle(skb);
> +
> + metasize = xdp->data - xdp->data_meta;
> + skb_reserve(skb, xdp->data - xdp->data_hard_start);
> + __skb_put(skb, xdp->data_end - xdp->data);
> +
> + if (metasize)
> + skb_metadata_set(skb, metasize);
> +
> + return skb;
> +}
> +
> +static unsigned int
Perfer to not have a newline here. To match the formatting of the rest
of the driver.
Thanks,
Justin
> +bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring, struct bpf_prog *prog,
> + struct xdp_buff *xdp, struct page *rx_page)
> +{
> + unsigned int act;
> +
> + act = bpf_prog_run_xdp(prog, xdp);
> +
> + switch (act) {
> + case XDP_PASS:
> + return XDP_PASS;
> + case XDP_DROP:
> + page_pool_put_full_page(ring->page_pool, rx_page, true);
> + return XDP_DROP;
> + default:
> + bpf_warn_invalid_xdp_action(ring->priv->dev, prog, act);
> + fallthrough;
> + case XDP_ABORTED:
> + trace_xdp_exception(ring->priv->dev, prog, act);
> + page_pool_put_full_page(ring->page_pool, rx_page, true);
> + return XDP_ABORTED;
> + }
> +}
> +
> /* bcmgenet_desc_rx - descriptor based rx process.
> * this could be called from bottom half, or from NAPI polling method.
> */
> @@ -2283,6 +2332,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
> struct bcmgenet_rx_stats64 *stats = &ring->stats64;
> struct bcmgenet_priv *priv = ring->priv;
> struct net_device *dev = priv->dev;
> + struct bpf_prog *xdp_prog;
> struct enet_cb *cb;
> struct sk_buff *skb;
> u32 dma_length_status;
> @@ -2293,6 +2343,8 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
> unsigned int p_index, mask;
> unsigned int discards;
>
> + xdp_prog = READ_ONCE(priv->xdp_prog);
> +
> /* Clear status before servicing to reduce spurious interrupts */
> mask = 1 << (UMAC_IRQ1_RX_INTR_SHIFT + ring->index);
> bcmgenet_intrl2_1_writel(priv, mask, INTRL2_CPU_CLEAR);
> @@ -2403,26 +2455,52 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
> goto next;
> } /* error packet */
>
> - /* 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, PAGE_SIZE - GENET_XDP_HEADROOM);
> - if (unlikely(!skb)) {
> - BCMGENET_STATS64_INC(stats, dropped);
> - page_pool_put_full_page(ring->page_pool, rx_page,
> - true);
> - goto next;
> - }
> -
> - skb_mark_for_recycle(skb);
> + /* XDP: frame data starts after RSB + pad */
> + if (xdp_prog) {
> + struct xdp_buff xdp;
> + unsigned int xdp_act;
> + int pkt_len;
> +
> + pkt_len = len - GENET_RSB_PAD;
> + if (priv->crc_fwd_en)
> + pkt_len -= ETH_FCS_LEN;
> +
> + xdp_init_buff(&xdp, PAGE_SIZE, &ring->xdp_rxq);
> + xdp_prepare_buff(&xdp, page_address(rx_page),
> + GENET_RX_HEADROOM, pkt_len, true);
> +
> + xdp_act = bcmgenet_run_xdp(ring, xdp_prog, &xdp,
> + rx_page);
> + if (xdp_act != XDP_PASS)
> + goto next;
> +
> + /* XDP_PASS: build SKB from (possibly modified) xdp */
> + skb = bcmgenet_xdp_build_skb(ring, &xdp, rx_page);
> + if (unlikely(!skb)) {
> + BCMGENET_STATS64_INC(stats, dropped);
> + page_pool_put_full_page(ring->page_pool,
> + rx_page, true);
> + goto next;
> + }
> + } else {
> + /* Build SKB from the page - data starts at
> + * hard_start, frame begins after RSB(64) + pad(2).
> + */
> + skb = napi_build_skb(hard_start,
> + PAGE_SIZE - GENET_XDP_HEADROOM);
> + if (unlikely(!skb)) {
> + BCMGENET_STATS64_INC(stats, dropped);
> + page_pool_put_full_page(ring->page_pool,
> + rx_page, true);
> + goto next;
> + }
>
> - /* Reserve the RSB + pad, then set the data length */
> - skb_reserve(skb, GENET_RSB_PAD);
> - __skb_put(skb, len - GENET_RSB_PAD);
> + skb_mark_for_recycle(skb);
> + 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);
> - len -= ETH_FCS_LEN;
> + if (priv->crc_fwd_en)
> + skb_trim(skb, skb->len - ETH_FCS_LEN);
> }
>
> /* Set up checksum offload */
> @@ -3743,6 +3821,37 @@ static int bcmgenet_change_carrier(struct net_device *dev, bool new_carrier)
> return 0;
> }
>
> +static int bcmgenet_xdp_setup(struct net_device *dev,
> + struct netdev_bpf *xdp)
> +{
> + struct bcmgenet_priv *priv = netdev_priv(dev);
> + struct bpf_prog *old_prog;
> + struct bpf_prog *prog = xdp->prog;
> +
> + if (prog && dev->mtu > PAGE_SIZE - GENET_RX_HEADROOM -
> + SKB_DATA_ALIGN(sizeof(struct skb_shared_info))) {
> + NL_SET_ERR_MSG_MOD(xdp->extack,
> + "MTU too large for single-page XDP buffer");
> + return -EOPNOTSUPP;
> + }
> +
> + old_prog = xchg(&priv->xdp_prog, prog);
> + if (old_prog)
> + bpf_prog_put(old_prog);
> +
> + return 0;
> +}
> +
> +static int bcmgenet_xdp(struct net_device *dev, struct netdev_bpf *xdp)
> +{
> + switch (xdp->command) {
> + case XDP_SETUP_PROG:
> + return bcmgenet_xdp_setup(dev, xdp);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> static const struct net_device_ops bcmgenet_netdev_ops = {
> .ndo_open = bcmgenet_open,
> .ndo_stop = bcmgenet_close,
> @@ -3754,6 +3863,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_bpf = bcmgenet_xdp,
> };
>
> /* GENET hardware parameters/characteristics */
> @@ -4056,6 +4166,7 @@ static int bcmgenet_probe(struct platform_device *pdev)
> NETIF_F_RXCSUM;
> dev->hw_features |= dev->features;
> dev->vlan_features |= dev->features;
> + dev->xdp_features = NETDEV_XDP_ACT_BASIC;
>
> netdev_sw_irq_coalesce_default_on(dev);
>
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
> index 82a6d29f481d..1459473ac1b0 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
> @@ -16,6 +16,7 @@
> #include <linux/dim.h>
> #include <linux/ethtool.h>
> #include <net/page_pool/helpers.h>
> +#include <linux/bpf.h>
> #include <net/xdp.h>
>
> #include "../unimac.h"
> @@ -671,6 +672,9 @@ struct bcmgenet_priv {
> u8 sopass[SOPASS_MAX];
>
> struct bcmgenet_mib_counters mib;
> +
> + /* XDP */
> + struct bpf_prog *xdp_prog;
> };
>
> static inline bool bcmgenet_has_40bits(struct bcmgenet_priv *priv)
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v3 4/6] net: bcmgenet: add XDP_TX support
2026-03-19 11:53 ` [PATCH net-next v3 4/6] net: bcmgenet: add XDP_TX support Nicolai Buchwitz
@ 2026-03-19 21:31 ` Justin Chen
2026-03-20 17:02 ` Simon Horman
1 sibling, 0 replies; 13+ messages in thread
From: Justin Chen @ 2026-03-19 21:31 UTC (permalink / raw)
To: Nicolai Buchwitz, netdev
Cc: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, linux-kernel, bpf
On 3/19/26 4:53 AM, Nicolai Buchwitz wrote:
> Implement XDP_TX using ring 16 (DESC_INDEX), the hardware default
> descriptor ring, dedicated to XDP TX for isolation from SKB TX queues.
>
> Ring 16 gets 32 BDs carved from ring 0's allocation. TX completion is
> piggybacked on RX NAPI poll since ring 16's INTRL2_1 bit collides with
> RX ring 0, similar to how bnxt, ice, and other XDP drivers handle TX
> completion within the RX poll path.
>
> The GENET MAC has TBUF_64B_EN set globally, requiring every TX buffer
> to start with a 64-byte struct status_64 (TSB). For local XDP_TX, the
> TSB is prepended by backing xdp->data into the RSB area (unused after
> BPF execution) and zeroing it. For foreign frames (ndo_xdp_xmit), the
> TSB is written into the xdp_frame headroom.
>
> The page_pool DMA direction is changed from DMA_FROM_DEVICE to
> DMA_BIDIRECTIONAL to allow TX reuse of the existing DMA mapping.
>
> Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
> ---
> .../net/ethernet/broadcom/genet/bcmgenet.c | 195 ++++++++++++++++--
> .../net/ethernet/broadcom/genet/bcmgenet.h | 4 +-
> 2 files changed, 181 insertions(+), 18 deletions(-)
>
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> index e8f90d6b16a2..54df1694f1cd 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> @@ -48,8 +48,10 @@
>
> #define GENET_Q0_RX_BD_CNT \
> (TOTAL_DESC - priv->hw_params->rx_queues * priv->hw_params->rx_bds_per_q)
> +#define GENET_Q16_TX_BD_CNT 64
> #define GENET_Q0_TX_BD_CNT \
> - (TOTAL_DESC - priv->hw_params->tx_queues * priv->hw_params->tx_bds_per_q)
> + (TOTAL_DESC - priv->hw_params->tx_queues * priv->hw_params->tx_bds_per_q \
> + - GENET_Q16_TX_BD_CNT)
>
> #define RX_BUF_LENGTH 2048
> #define SKB_ALIGNMENT 32
> @@ -1893,6 +1895,14 @@ static struct sk_buff *bcmgenet_free_tx_cb(struct device *dev,
> if (cb == GENET_CB(skb)->last_cb)
> return skb;
>
> + } else if (cb->xdpf) {
> + if (cb->xdp_dma_map)
> + dma_unmap_single(dev, dma_unmap_addr(cb, dma_addr),
> + dma_unmap_len(cb, dma_len),
> + DMA_TO_DEVICE);
> + dma_unmap_addr_set(cb, dma_addr, 0);
> + xdp_return_frame(cb->xdpf);
> + cb->xdpf = NULL;
> } else if (dma_unmap_addr(cb, dma_addr)) {
> dma_unmap_page(dev,
> dma_unmap_addr(cb, dma_addr),
> @@ -1927,8 +1937,13 @@ static unsigned int __bcmgenet_tx_reclaim(struct net_device *dev,
> unsigned int c_index;
> struct sk_buff *skb;
>
> - /* Clear status before servicing to reduce spurious interrupts */
> - bcmgenet_intrl2_1_writel(priv, (1 << ring->index), INTRL2_CPU_CLEAR);
> + /* Clear status before servicing to reduce spurious interrupts.
> + * Ring DESC_INDEX (XDP TX) has no interrupt; skip the clear to
> + * avoid clobbering RX ring 0's bit at the same position.
> + */
> + if (ring->index != DESC_INDEX)
> + bcmgenet_intrl2_1_writel(priv, BIT(ring->index),
> + INTRL2_CPU_CLEAR);
>
> /* Compute how many buffers are transmitted since last xmit call */
> c_index = bcmgenet_tdma_ring_readl(priv, ring->index, TDMA_CONS_INDEX)
> @@ -1964,8 +1979,11 @@ static unsigned int __bcmgenet_tx_reclaim(struct net_device *dev,
> u64_stats_add(&stats->bytes, bytes_compl);
> u64_stats_update_end(&stats->syncp);
>
> - netdev_tx_completed_queue(netdev_get_tx_queue(dev, ring->index),
> - pkts_compl, bytes_compl);
> + /* Ring DESC_INDEX (XDP TX) has no netdev TX queue; skip BQL */
> + if (ring->index != DESC_INDEX)
> + netdev_tx_completed_queue(netdev_get_tx_queue(dev,
> + ring->index),
> + pkts_compl, bytes_compl);
>
> return txbds_processed;
> }
> @@ -2042,6 +2060,9 @@ static void bcmgenet_tx_reclaim_all(struct net_device *dev)
> do {
> bcmgenet_tx_reclaim(dev, &priv->tx_rings[i++], true);
> } while (i <= priv->hw_params->tx_queues && netif_is_multiqueue(dev));
> +
> + /* Also reclaim XDP TX ring */
> + bcmgenet_tx_reclaim(dev, &priv->tx_rings[DESC_INDEX], true);
> }
>
> /* Reallocate the SKB to put enough headroom in front of it and insert
> @@ -2299,10 +2320,96 @@ static struct sk_buff *bcmgenet_xdp_build_skb(struct bcmgenet_rx_ring *ring,
> return skb;
> }
>
> +static bool
Same as last patch. Perfer to remove newline here.
> +bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
> + struct xdp_frame *xdpf, bool dma_map)
> +{
> + struct bcmgenet_tx_ring *ring = &priv->tx_rings[DESC_INDEX];
> + struct device *kdev = &priv->pdev->dev;
> + struct enet_cb *tx_cb_ptr;
> + dma_addr_t mapping;
> + unsigned int dma_len;
> + u32 len_stat;
> +
> + spin_lock(&ring->lock);
> +
> + if (ring->free_bds < 1) {
> + spin_unlock(&ring->lock);
> + return false;
> + }
> +
> + tx_cb_ptr = bcmgenet_get_txcb(priv, ring);
> +
> + if (dma_map) {
> + void *tsb_start;
> +
> + /* The GENET MAC has TBUF_64B_EN set globally, so hardware
> + * expects a 64-byte TSB prefix on every TX buffer. For
> + * redirected frames (ndo_xdp_xmit) we prepend a zeroed TSB
> + * using the frame's headroom.
> + */
> + if (unlikely(xdpf->headroom < sizeof(struct status_64))) {
> + bcmgenet_put_txcb(priv, ring);
> + spin_unlock(&ring->lock);
> + return false;
> + }
> +
> + tsb_start = xdpf->data - sizeof(struct status_64);
> + memset(tsb_start, 0, sizeof(struct status_64));
> +
> + dma_len = xdpf->len + sizeof(struct status_64);
> + mapping = dma_map_single(kdev, tsb_start, dma_len,
> + DMA_TO_DEVICE);
> + if (dma_mapping_error(kdev, mapping)) {
> + tx_cb_ptr->skb = NULL;
> + tx_cb_ptr->xdpf = NULL;
> + bcmgenet_put_txcb(priv, ring);
> + spin_unlock(&ring->lock);
> + return false;
> + }
> + } else {
> + struct page *page = virt_to_page(xdpf->data);
> +
> + /* For local XDP_TX the caller already prepended the TSB
> + * into xdpf->data/len, so dma_len == xdpf->len.
> + */
> + dma_len = xdpf->len;
> + mapping = page_pool_get_dma_addr(page) +
> + sizeof(*xdpf) + xdpf->headroom;
> + dma_sync_single_for_device(kdev, mapping, dma_len,
> + DMA_BIDIRECTIONAL);
> + }
> +
> + dma_unmap_addr_set(tx_cb_ptr, dma_addr, mapping);
> + dma_unmap_len_set(tx_cb_ptr, dma_len, dma_len);
> + tx_cb_ptr->skb = NULL;
> + tx_cb_ptr->xdpf = xdpf;
> + tx_cb_ptr->xdp_dma_map = dma_map;
> +
> + len_stat = (dma_len << DMA_BUFLENGTH_SHIFT) |
> + (priv->hw_params->qtag_mask << DMA_TX_QTAG_SHIFT) |
> + DMA_TX_APPEND_CRC | DMA_SOP | DMA_EOP;
> +
> + dmadesc_set(priv, tx_cb_ptr->bd_addr, mapping, len_stat);
> +
> + ring->free_bds--;
> + ring->prod_index++;
> + ring->prod_index &= DMA_P_INDEX_MASK;
> +
> + bcmgenet_tdma_ring_writel(priv, ring->index, ring->prod_index,
> + TDMA_PROD_INDEX);
> +
> + spin_unlock(&ring->lock);
> +
> + return true;
> +}
> +
> static unsigned int
> bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring, struct bpf_prog *prog,
> struct xdp_buff *xdp, struct page *rx_page)
> {
> + struct bcmgenet_priv *priv = ring->priv;
> + struct xdp_frame *xdpf;
> unsigned int act;
>
> act = bpf_prog_run_xdp(prog, xdp);
> @@ -2310,14 +2417,33 @@ bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring, struct bpf_prog *prog,
> switch (act) {
> case XDP_PASS:
> return XDP_PASS;
> + case XDP_TX:
> + /* Prepend a zeroed TSB (Transmit Status Block). The GENET
> + * MAC has TBUF_64B_EN set globally, so hardware expects every
> + * TX buffer to begin with a 64-byte struct status_64. Back
> + * up xdp->data into the RSB area (which is no longer needed
> + * after the BPF program ran) and zero it.
> + */
> + xdp->data -= sizeof(struct status_64);
> + xdp->data_meta -= sizeof(struct status_64);
> + memset(xdp->data, 0, sizeof(struct status_64));
> +
> + xdpf = xdp_convert_buff_to_frame(xdp);
> + if (unlikely(!xdpf) ||
> + unlikely(!bcmgenet_xdp_xmit_frame(priv, xdpf, false))) {
> + page_pool_put_full_page(ring->page_pool, rx_page,
> + true);
> + return XDP_DROP;
> + }
> + return XDP_TX;
> case XDP_DROP:
> page_pool_put_full_page(ring->page_pool, rx_page, true);
> return XDP_DROP;
> default:
> - bpf_warn_invalid_xdp_action(ring->priv->dev, prog, act);
> + bpf_warn_invalid_xdp_action(priv->dev, prog, act);
> fallthrough;
> case XDP_ABORTED:
> - trace_xdp_exception(ring->priv->dev, prog, act);
> + trace_xdp_exception(priv->dev, prog, act);
> page_pool_put_full_page(ring->page_pool, rx_page, true);
> return XDP_ABORTED;
> }
> @@ -2555,9 +2681,15 @@ static int bcmgenet_rx_poll(struct napi_struct *napi, int budget)
> {
> struct bcmgenet_rx_ring *ring = container_of(napi,
> struct bcmgenet_rx_ring, napi);
> + struct bcmgenet_priv *priv = ring->priv;
> struct dim_sample dim_sample = {};
> unsigned int work_done;
>
> + /* Reclaim completed XDP TX frames (ring 16 has no interrupt) */
> + if (priv->xdp_prog)
> + bcmgenet_tx_reclaim(priv->dev,
> + &priv->tx_rings[DESC_INDEX], false);
> +
> work_done = bcmgenet_desc_rx(ring, budget);
>
> if (work_done < budget && napi_complete_done(napi, work_done))
> @@ -2832,8 +2964,11 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv,
> bcmgenet_tdma_ring_writel(priv, index, end_ptr * words_per_bd - 1,
> DMA_END_ADDR);
>
> - /* Initialize Tx NAPI */
> - netif_napi_add_tx(priv->dev, &ring->napi, bcmgenet_tx_poll);
> + /* Initialize Tx NAPI for priority queues only; ring DESC_INDEX
> + * (XDP TX) has its completions handled inline in RX NAPI.
> + */
> + if (index != DESC_INDEX)
> + netif_napi_add_tx(priv->dev, &ring->napi, bcmgenet_tx_poll);
> }
>
> static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv,
> @@ -2845,7 +2980,7 @@ static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv,
> .pool_size = ring->size,
> .nid = NUMA_NO_NODE,
> .dev = &priv->pdev->dev,
> - .dma_dir = DMA_FROM_DEVICE,
> + .dma_dir = DMA_BIDIRECTIONAL,
> .offset = GENET_XDP_HEADROOM,
> .max_len = RX_BUF_LENGTH,
> };
> @@ -2977,6 +3112,7 @@ static int bcmgenet_tdma_disable(struct bcmgenet_priv *priv)
>
> reg = bcmgenet_tdma_readl(priv, DMA_CTRL);
> mask = (1 << (priv->hw_params->tx_queues + 1)) - 1;
> + mask |= BIT(DESC_INDEX);
> mask = (mask << DMA_RING_BUF_EN_SHIFT) | DMA_EN;
> reg &= ~mask;
> bcmgenet_tdma_writel(priv, reg, DMA_CTRL);
> @@ -3022,14 +3158,18 @@ static int bcmgenet_rdma_disable(struct bcmgenet_priv *priv)
> * with queue 1 being the highest priority queue.
> *
> * Queue 0 is the default Tx queue with
> - * GENET_Q0_TX_BD_CNT = 256 - 4 * 32 = 128 descriptors.
> + * GENET_Q0_TX_BD_CNT = 256 - 4 * 32 - 32 = 96 descriptors.
> + *
> + * Ring 16 (DESC_INDEX) is used for XDP TX with
> + * GENET_Q16_TX_BD_CNT = 32 descriptors.
> *
> * The transmit control block pool is then partitioned as follows:
> - * - Tx queue 0 uses tx_cbs[0..127]
> - * - Tx queue 1 uses tx_cbs[128..159]
> - * - Tx queue 2 uses tx_cbs[160..191]
> - * - Tx queue 3 uses tx_cbs[192..223]
> - * - Tx queue 4 uses tx_cbs[224..255]
> + * - Tx queue 0 uses tx_cbs[0..95]
> + * - Tx queue 1 uses tx_cbs[96..127]
> + * - Tx queue 2 uses tx_cbs[128..159]
> + * - Tx queue 3 uses tx_cbs[160..191]
> + * - Tx queue 4 uses tx_cbs[192..223]
> + * - Tx ring 16 uses tx_cbs[224..255]
Can keep the term tx queue? Unless we are trying to highlight something
different here.
> */
> static void bcmgenet_init_tx_queues(struct net_device *dev)
> {
> @@ -3050,13 +3190,18 @@ static void bcmgenet_init_tx_queues(struct net_device *dev)
> << DMA_PRIO_REG_SHIFT(i);
> }
>
> + /* Initialize ring 16 (descriptor ring) for XDP TX */
> + bcmgenet_init_tx_ring(priv, DESC_INDEX, GENET_Q16_TX_BD_CNT,
> + TOTAL_DESC - GENET_Q16_TX_BD_CNT, TOTAL_DESC);
> +
> /* Set Tx queue priorities */
> bcmgenet_tdma_writel(priv, dma_priority[0], DMA_PRIORITY_0);
> bcmgenet_tdma_writel(priv, dma_priority[1], DMA_PRIORITY_1);
> bcmgenet_tdma_writel(priv, dma_priority[2], DMA_PRIORITY_2);
>
> - /* Configure Tx queues as descriptor rings */
> + /* Configure Tx queues as descriptor rings, including ring 16 */
> ring_mask = (1 << (priv->hw_params->tx_queues + 1)) - 1;
> + ring_mask |= BIT(DESC_INDEX);
> bcmgenet_tdma_writel(priv, ring_mask, DMA_RING_CFG);
>
> /* Enable Tx rings */
> @@ -3770,6 +3915,21 @@ static void bcmgenet_get_stats64(struct net_device *dev,
> stats->tx_dropped += tx_dropped;
> }
>
> + /* Include XDP TX ring (DESC_INDEX) stats */
> + tx_stats = &priv->tx_rings[DESC_INDEX].stats64;
> + do {
> + start = u64_stats_fetch_begin(&tx_stats->syncp);
> + tx_bytes = u64_stats_read(&tx_stats->bytes);
> + tx_packets = u64_stats_read(&tx_stats->packets);
> + tx_errors = u64_stats_read(&tx_stats->errors);
> + tx_dropped = u64_stats_read(&tx_stats->dropped);
> + } while (u64_stats_fetch_retry(&tx_stats->syncp, start));
> +
> + stats->tx_bytes += tx_bytes;
> + stats->tx_packets += tx_packets;
> + stats->tx_errors += tx_errors;
> + stats->tx_dropped += tx_dropped;
> +
> for (q = 0; q <= priv->hw_params->rx_queues; q++) {
> rx_stats = &priv->rx_rings[q].stats64;
> do {
> @@ -4273,6 +4433,7 @@ static int bcmgenet_probe(struct platform_device *pdev)
> u64_stats_init(&priv->rx_rings[i].stats64.syncp);
> for (i = 0; i <= priv->hw_params->tx_queues; i++)
> u64_stats_init(&priv->tx_rings[i].stats64.syncp);
> + u64_stats_init(&priv->tx_rings[DESC_INDEX].stats64.syncp);
>
> /* libphy will determine the link state */
> netif_carrier_off(dev);
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
> index 1459473ac1b0..fce598c29a96 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
> @@ -472,6 +472,8 @@ struct bcmgenet_rx_stats64 {
>
> struct enet_cb {
> struct sk_buff *skb;
> + struct xdp_frame *xdpf;
> + bool xdp_dma_map;
> struct page *rx_page;
> unsigned int rx_page_offset;
> void __iomem *bd_addr;
> @@ -610,7 +612,7 @@ struct bcmgenet_priv {
> struct enet_cb *tx_cbs;
> unsigned int num_tx_bds;
>
> - struct bcmgenet_tx_ring tx_rings[GENET_MAX_MQ_CNT + 1];
> + struct bcmgenet_tx_ring tx_rings[DESC_INDEX + 1];
This creates a bunch of tx_rings that aren't used. I don't see why we
need them in the same array. The logic above loops through the queue
rings, then hits the DESC_INDEX ring last. So not too much value of
keeping them in the same array.
Thanks,
Justin
>
> /* receive variables */
> void __iomem *rx_bds;
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v3 5/6] net: bcmgenet: add XDP_REDIRECT and ndo_xdp_xmit support
2026-03-19 11:53 ` [PATCH net-next v3 5/6] net: bcmgenet: add XDP_REDIRECT and ndo_xdp_xmit support Nicolai Buchwitz
@ 2026-03-19 21:34 ` Justin Chen
0 siblings, 0 replies; 13+ messages in thread
From: Justin Chen @ 2026-03-19 21:34 UTC (permalink / raw)
To: Nicolai Buchwitz, netdev
Cc: Doug Berger, Florian Fainelli,
Broadcom internal kernel review list, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, linux-kernel, bpf
On 3/19/26 4:53 AM, Nicolai Buchwitz wrote:
> Add XDP_REDIRECT support and implement ndo_xdp_xmit for receiving
> redirected frames from other devices.
>
> XDP_REDIRECT calls xdp_do_redirect() in the RX path with
> xdp_do_flush() once per NAPI poll cycle. ndo_xdp_xmit batches frames
> into ring 16 under a single spinlock acquisition.
>
> Advertise NETDEV_XDP_ACT_REDIRECT and NETDEV_XDP_ACT_NDO_XMIT in
> xdp_features.
>
> Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
> ---
> .../net/ethernet/broadcom/genet/bcmgenet.c | 98 +++++++++++++++----
> 1 file changed, 80 insertions(+), 18 deletions(-)
>
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> index 54df1694f1cd..a5ad25ccb148 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> @@ -2320,23 +2320,23 @@ static struct sk_buff *bcmgenet_xdp_build_skb(struct bcmgenet_rx_ring *ring,
> return skb;
> }
>
> +/* Submit a single XDP frame to the TX ring. Caller must hold ring->lock.
> + * Returns true on success. Does not ring the doorbell - caller must
> + * write TDMA_PROD_INDEX after batching.
> + */
> static bool
> bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
> + struct bcmgenet_tx_ring *ring,
> struct xdp_frame *xdpf, bool dma_map)
> {
> - struct bcmgenet_tx_ring *ring = &priv->tx_rings[DESC_INDEX];
> struct device *kdev = &priv->pdev->dev;
> struct enet_cb *tx_cb_ptr;
> dma_addr_t mapping;
> unsigned int dma_len;
> u32 len_stat;
>
> - spin_lock(&ring->lock);
> -
> - if (ring->free_bds < 1) {
> - spin_unlock(&ring->lock);
> + if (ring->free_bds < 1)
> return false;
> - }
>
> tx_cb_ptr = bcmgenet_get_txcb(priv, ring);
>
> @@ -2350,7 +2350,6 @@ bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
> */
> if (unlikely(xdpf->headroom < sizeof(struct status_64))) {
> bcmgenet_put_txcb(priv, ring);
> - spin_unlock(&ring->lock);
> return false;
> }
>
> @@ -2364,7 +2363,6 @@ bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
> tx_cb_ptr->skb = NULL;
> tx_cb_ptr->xdpf = NULL;
> bcmgenet_put_txcb(priv, ring);
> - spin_unlock(&ring->lock);
> return false;
> }
> } else {
> @@ -2396,12 +2394,15 @@ bcmgenet_xdp_xmit_frame(struct bcmgenet_priv *priv,
> ring->prod_index++;
> ring->prod_index &= DMA_P_INDEX_MASK;
>
> + return true;
> +}
> +
> +static void
Perfer to remove newline.
> +bcmgenet_xdp_ring_doorbell(struct bcmgenet_priv *priv,
> + struct bcmgenet_tx_ring *ring)
> +{
> bcmgenet_tdma_ring_writel(priv, ring->index, ring->prod_index,
> TDMA_PROD_INDEX);
> -
> - spin_unlock(&ring->lock);
> -
> - return true;
> }
>
> static unsigned int
> @@ -2417,7 +2418,11 @@ bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring, struct bpf_prog *prog,
> switch (act) {
> case XDP_PASS:
> return XDP_PASS;
> - case XDP_TX:
> + case XDP_TX: {
> + struct bcmgenet_tx_ring *tx_ring;
> +
> + tx_ring = &priv->tx_rings[DESC_INDEX];
> +
> /* Prepend a zeroed TSB (Transmit Status Block). The GENET
> * MAC has TBUF_64B_EN set globally, so hardware expects every
> * TX buffer to begin with a 64-byte struct status_64. Back
> @@ -2429,14 +2434,26 @@ bcmgenet_run_xdp(struct bcmgenet_rx_ring *ring, struct bpf_prog *prog,
> memset(xdp->data, 0, sizeof(struct status_64));
>
> xdpf = xdp_convert_buff_to_frame(xdp);
> - if (unlikely(!xdpf) ||
> - unlikely(!bcmgenet_xdp_xmit_frame(priv, xdpf, false))) {
> - page_pool_put_full_page(ring->page_pool, rx_page,
> - true);
> + if (unlikely(!xdpf))
> + goto drop_page;
> +
> + spin_lock(&tx_ring->lock);
> + if (unlikely(!bcmgenet_xdp_xmit_frame(priv, tx_ring,
> + xdpf, false))) {
> + spin_unlock(&tx_ring->lock);
> + xdp_return_frame_rx_napi(xdpf);
> return XDP_DROP;
> }
> + bcmgenet_xdp_ring_doorbell(priv, tx_ring);
> + spin_unlock(&tx_ring->lock);
> return XDP_TX;
> + }
Don't think we need this extra curly bracket here.
> + case XDP_REDIRECT:
> + if (unlikely(xdp_do_redirect(priv->dev, xdp, prog)))
> + goto drop_page;
> + return XDP_REDIRECT;
> case XDP_DROP:
> +drop_page:
Not a big fan of jumping like this within switch statements.
> page_pool_put_full_page(ring->page_pool, rx_page, true);
> return XDP_DROP;
> default:
> @@ -2459,6 +2476,7 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
> struct bcmgenet_priv *priv = ring->priv;
> struct net_device *dev = priv->dev;
> struct bpf_prog *xdp_prog;
> + bool xdp_flush = false;
> struct enet_cb *cb;
> struct sk_buff *skb;
> u32 dma_length_status;
> @@ -2597,6 +2615,8 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
>
> xdp_act = bcmgenet_run_xdp(ring, xdp_prog, &xdp,
> rx_page);
> + if (xdp_act == XDP_REDIRECT)
> + xdp_flush = true;
> if (xdp_act != XDP_PASS)
> goto next;
>
> @@ -2670,6 +2690,9 @@ static unsigned int bcmgenet_desc_rx(struct bcmgenet_rx_ring *ring,
> bcmgenet_rdma_ring_writel(priv, ring->index, ring->c_index, RDMA_CONS_INDEX);
> }
>
> + if (xdp_flush)
> + xdp_do_flush();
> +
> ring->dim.bytes = bytes_processed;
> ring->dim.packets = rxpktprocessed;
>
> @@ -3995,10 +4018,16 @@ static int bcmgenet_xdp_setup(struct net_device *dev,
> return -EOPNOTSUPP;
> }
>
> + if (!prog)
> + xdp_features_clear_redirect_target(dev);
> +
> old_prog = xchg(&priv->xdp_prog, prog);
> if (old_prog)
> bpf_prog_put(old_prog);
>
> + if (prog)
> + xdp_features_set_redirect_target(dev, false);
> +
> return 0;
> }
>
> @@ -4012,6 +4041,37 @@ static int bcmgenet_xdp(struct net_device *dev, struct netdev_bpf *xdp)
> }
> }
>
> +static int
Same as before
> +bcmgenet_xdp_xmit(struct net_device *dev, int num_frames,
> + struct xdp_frame **frames, u32 flags)
> +{
> + struct bcmgenet_priv *priv = netdev_priv(dev);
> + struct bcmgenet_tx_ring *ring = &priv->tx_rings[DESC_INDEX];
> + int sent = 0;
> + int i;
> +
> + if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK))
> + return -EINVAL;
> +
> + if (unlikely(!netif_running(dev)))
> + return -ENETDOWN;
> +
> + spin_lock(&ring->lock);
> +
> + for (i = 0; i < num_frames; i++) {
> + if (!bcmgenet_xdp_xmit_frame(priv, ring, frames[i], true))
> + break;
> + sent++;
> + }
> +
> + if (sent)
> + bcmgenet_xdp_ring_doorbell(priv, ring);
> +
> + spin_unlock(&ring->lock);
> +
> + return sent;
> +}
> +
> static const struct net_device_ops bcmgenet_netdev_ops = {
> .ndo_open = bcmgenet_open,
> .ndo_stop = bcmgenet_close,
> @@ -4024,6 +4084,7 @@ static const struct net_device_ops bcmgenet_netdev_ops = {
> .ndo_get_stats64 = bcmgenet_get_stats64,
> .ndo_change_carrier = bcmgenet_change_carrier,
> .ndo_bpf = bcmgenet_xdp,
> + .ndo_xdp_xmit = bcmgenet_xdp_xmit,
> };
>
> /* GENET hardware parameters/characteristics */
> @@ -4326,7 +4387,8 @@ static int bcmgenet_probe(struct platform_device *pdev)
> NETIF_F_RXCSUM;
> dev->hw_features |= dev->features;
> dev->vlan_features |= dev->features;
> - dev->xdp_features = NETDEV_XDP_ACT_BASIC;
> + dev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT |
> + NETDEV_XDP_ACT_NDO_XMIT;
>
> netdev_sw_irq_coalesce_default_on(dev);
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v3 1/6] net: bcmgenet: convert RX path to page_pool
2026-03-19 11:53 ` [PATCH net-next v3 1/6] net: bcmgenet: convert RX path to page_pool Nicolai Buchwitz
@ 2026-03-20 17:02 ` Simon Horman
0 siblings, 0 replies; 13+ messages in thread
From: Simon Horman @ 2026-03-20 17:02 UTC (permalink / raw)
To: nb
Cc: Simon Horman, davem, opendmb, john.fastabend, florian.fainelli,
daniel, arnd, bcm-kernel-feedback-list, vikas.gupta,
bhargava.marreddy, linux-kernel, ast, edumazet, kuba, sdf,
ebiggers, hawk, andrew+netdev, bpf, pabeni, rajashekar.hudumula,
netdev
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
net: bcmgenet: convert RX path to page_pool
Replace the per-packet __netdev_alloc_skb() + dma_map_single() in the
RX path with page_pool, which provides efficient page recycling and
DMA mapping management. This is a prerequisite for XDP support (which
requires stable page-backed buffers rather than SKB linear data).
Signed-off-by: Nicolai Buchwitz <nb@tipi-net.de>
> diff --git a/drivers/net/ethernet/broadcom/Kconfig b/drivers/net/ethernet/broadcom/Kconfig
> index cd7dddeb91dd..e3b9a5272406 100644
> --- a/drivers/net/ethernet/broadcom/Kconfig
> +++ b/drivers/net/ethernet/broadcom/Kconfig
[ ... ]
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> index 482a31e7b72b..7410034d9bdc 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
[ ... ]
> @@ -2747,6 +2758,31 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv,
> netif_napi_add_tx(priv->dev, &ring->napi, bcmgenet_tx_poll);
> }
>
> +static int bcmgenet_rx_ring_create_pool(struct bcmgenet_priv *priv,
> + struct bcmgenet_rx_ring *ring)
> +{
> + struct page_pool_params pp_params = {
> + .order = 0,
> + .flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
> + .pool_size = ring->size,
> + .nid = NUMA_NO_NODE,
> + .dev = &priv->pdev->dev,
> + .dma_dir = DMA_FROM_DEVICE,
> + .offset = GENET_XDP_HEADROOM,
> + .max_len = RX_BUF_LENGTH,
> + };
> +
> + ring->page_pool = page_pool_create(&pp_params);
> + if (IS_ERR(ring->page_pool)) {
> + int err = PTR_ERR(ring->page_pool);
> +
> + ring->page_pool = NULL;
> + return err;
> + }
> +
> + return 0;
> +}
> +
> /* Initialize a RDMA ring */
> static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv,
> unsigned int index, unsigned int size,
> @@ -2765,10 +2801,17 @@ static int bcmgenet_init_rx_ring(struct bcmgenet_priv *priv,
> ring->cb_ptr = start_ptr;
> ring->end_ptr = end_ptr - 1;
>
> - ret = bcmgenet_alloc_rx_buffers(priv, ring);
> + ret = bcmgenet_rx_ring_create_pool(priv, ring);
> if (ret)
> return ret;
>
> + ret = bcmgenet_alloc_rx_buffers(priv, ring);
> + if (ret) {
> + page_pool_destroy(ring->page_pool);
> + ring->page_pool = NULL;
> + return ret;
> + }
> +
Does this leak pages if bcmgenet_alloc_rx_buffers() fails partway through?
If bcmgenet_alloc_rx_buffers() successfully allocates pages for the first
k iterations but then page_pool_alloc_pages() fails on iteration k+1, the
pages stored in cb->rx_page for iterations 0 through k-1 are never
returned to the page pool.
When the error path calls page_pool_destroy(), the pool detects outstanding
pages and defers cleanup, triggering periodic 'stalled pool shutdown' kernel
warnings from page_pool_release_retry().
Then bcmgenet_init_dma's error path calls bcmgenet_free_rx_buffers(), which
skips this ring entirely because ring->page_pool is NULL:
bcmgenet_free_rx_buffers() {
for (q = 0; q <= priv->hw_params->rx_queues; q++) {
ring = &priv->rx_rings[q];
if (!ring->page_pool)
continue;
...
}
}
Should the error path iterate through the ring's cbs and call
bcmgenet_free_rx_cb() for each one before destroying the page pool?
The old code didn't have this issue because bcmgenet_free_rx_buffers()
iterated all rx_cbs globally without checking for a per-ring page_pool.
[ ... ]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net-next v3 4/6] net: bcmgenet: add XDP_TX support
2026-03-19 11:53 ` [PATCH net-next v3 4/6] net: bcmgenet: add XDP_TX support Nicolai Buchwitz
2026-03-19 21:31 ` Justin Chen
@ 2026-03-20 17:02 ` Simon Horman
1 sibling, 0 replies; 13+ messages in thread
From: Simon Horman @ 2026-03-20 17:02 UTC (permalink / raw)
To: nb
Cc: Simon Horman, davem, opendmb, john.fastabend, florian.fainelli,
daniel, arnd, bcm-kernel-feedback-list, vikas.gupta,
bhargava.marreddy, linux-kernel, ast, edumazet, kuba, sdf,
ebiggers, hawk, andrew+netdev, bpf, pabeni, rajashekar.hudumula,
netdev
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
net: bcmgenet: add XDP_TX support
Implement XDP_TX using ring 16 (DESC_INDEX), the hardware default
descriptor ring, dedicated to XDP TX for isolation from SKB TX queues.
Ring 16 gets 32 BDs carved from ring 0's allocation. TX completion is
piggybacked on RX NAPI poll since ring 16's INTRL2_1 bit collides with
RX ring 0, similar to how bnxt, ice, and other XDP drivers handle TX
completion within the RX poll path.
> diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> index e8f90d6b16a2..54df1694f1cd 100644
> --- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> +++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
> @@ -48,8 +48,10 @@
>
> #define GENET_Q0_RX_BD_CNT \
> (TOTAL_DESC - priv->hw_params->rx_queues * priv->hw_params->rx_bds_per_q)
> +#define GENET_Q16_TX_BD_CNT 64
> +#define GENET_Q0_TX_BD_CNT \
> - (TOTAL_DESC - priv->hw_params->tx_queues * priv->hw_params->tx_bds_per_q)
> + (TOTAL_DESC - priv->hw_params->tx_queues * priv->hw_params->tx_bds_per_q \
> + - GENET_Q16_TX_BD_CNT)
>
> #define RX_BUF_LENGTH 2048
> #define SKB_ALIGNMENT 32
[ ... ]
> @@ -3022,14 +3158,18 @@ static int bcmgenet_rdma_disable(struct bcmgenet_priv *priv)
> * with queue 1 being the highest priority queue.
> *
> * Queue 0 is the default Tx queue with
> - * GENET_Q0_TX_BD_CNT = 256 - 4 * 32 = 128 descriptors.
> + * GENET_Q0_TX_BD_CNT = 256 - 4 * 32 - 32 = 96 descriptors.
> + *
> + * Ring 16 (DESC_INDEX) is used for XDP TX with
> + * GENET_Q16_TX_BD_CNT = 32 descriptors.
> + *
> * The transmit control block pool is then partitioned as follows:
> - * - Tx queue 0 uses tx_cbs[0..127]
> - * - Tx queue 1 uses tx_cbs[128..159]
> - * - Tx queue 2 uses tx_cbs[160..191]
> - * - Tx queue 3 uses tx_cbs[192..223]
> - * - Tx queue 4 uses tx_cbs[224..255]
> + * - Tx queue 0 uses tx_cbs[0..95]
> + * - Tx queue 1 uses tx_cbs[96..127]
> + * - Tx queue 2 uses tx_cbs[128..159]
> + * - Tx queue 3 uses tx_cbs[160..191]
> + * - Tx queue 4 uses tx_cbs[192..223]
> + * - Tx ring 16 uses tx_cbs[224..255]
> */
> static void bcmgenet_init_tx_queues(struct net_device *dev)
> {
Does the GENET_Q16_TX_BD_CNT define match the comment?
The comment and the commit message both say "Ring 16 gets 32 BDs," but
the define at the top of the file says:
#define GENET_Q16_TX_BD_CNT 64
The comment math also says "256 - 4 * 32 - 32 = 96" which expects ring
16 to use 32 descriptors, and shows queue 0 getting 96 descriptors
(tx_cbs[0..95]). But with the define set to 64, queue 0 would actually
get only 64 descriptors (tx_cbs[0..63]) instead of 96:
GENET_Q0_TX_BD_CNT = TOTAL_DESC (256)
- priv->hw_params->tx_queues (4) * 32
- GENET_Q16_TX_BD_CNT (64)
= 256 - 128 - 64
= 64
and ring 16 would use tx_cbs[192..255] (64 entries) instead of
tx_cbs[224..255] (32 entries).
All of the intermediate queue ranges in the comment would also be
shifted by 32 entries from what's documented.
Which is correct, the define or the documentation?
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-03-20 17:02 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-19 11:53 [PATCH net-next v3 0/6] net: bcmgenet: add XDP support Nicolai Buchwitz
2026-03-19 11:53 ` [PATCH net-next v3 1/6] net: bcmgenet: convert RX path to page_pool Nicolai Buchwitz
2026-03-20 17:02 ` Simon Horman
2026-03-19 11:53 ` [PATCH net-next v3 2/6] net: bcmgenet: register xdp_rxq_info for each RX ring Nicolai Buchwitz
2026-03-19 21:18 ` Justin Chen
2026-03-19 11:53 ` [PATCH net-next v3 3/6] net: bcmgenet: add basic XDP support (PASS/DROP) Nicolai Buchwitz
2026-03-19 21:23 ` Justin Chen
2026-03-19 11:53 ` [PATCH net-next v3 4/6] net: bcmgenet: add XDP_TX support Nicolai Buchwitz
2026-03-19 21:31 ` Justin Chen
2026-03-20 17:02 ` Simon Horman
2026-03-19 11:53 ` [PATCH net-next v3 5/6] net: bcmgenet: add XDP_REDIRECT and ndo_xdp_xmit support Nicolai Buchwitz
2026-03-19 21:34 ` Justin Chen
2026-03-19 11:53 ` [PATCH net-next v3 6/6] net: bcmgenet: add XDP statistics counters Nicolai Buchwitz
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox