* [PATCH net-next v3 0/5] fbnic: Support larger RX pages
@ 2026-09-15 18:20 Björn Töpel
2026-09-15 18:20 ` [PATCH net-next v3 1/5] net: Add netdev_config helpers Björn Töpel
` (4 more replies)
0 siblings, 5 replies; 10+ messages in thread
From: Björn Töpel @ 2026-09-15 18:20 UTC (permalink / raw)
To: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Shuah Khan, netdev, linux-kernel, linux-kselftest,
Daniel Borkmann
Cc: Björn Töpel, Mike Marciniszyn (Meta),
Mohsin Bashir, Stanislav Fomichev, Bobby Eshleman,
Dimitri Daskalakis, Weiming Shi, Maxime Chevallier, Jacob Keller,
Breno Leitao, Tao Cui, Pavel Begunkov, David Wei
Hi!
This series adds support for large io_uring zero-copy RX buffers to
fbnic.
fbnic hardware consumes receive memory in 4 KiB device pages, while
io_uring may expose a larger software buffer, such as 256 KiB. Track
the device-page geometry per BDQ, represent each large buffer with
consecutive descriptors, and decode completions to the corresponding
offset within the buffer. HPQ keeps its existing PAGE_SIZE geometry.
Larger buffers also reduce pressure on the memory provider. One
provider allocation supplies multiple 4 KiB device pages, so the same
amount of receive memory needs fewer net_iov objects and fewer
allocation, refill, and recycle operations. Hardware descriptor use
remains unchanged.
Ring depth and buffer size must be validated together. An ethtool ring
change can otherwise make an active memory-provider configuration
invalid. Store accepted ring depths in netdev_config, stage proposed
values in cfg_pending, and validate the default and each RX queue
before committing the change. Both netlink and ioctl use the same
transaction helpers.
The selftest requests a power-of-two buffer larger than twice the MTU,
checks the reported buffer length, and verifies received data. It does
not assume how packets are placed within the buffer.
Sashiko found, as usual, a number of bugs. I've sent the relevant ones
to net.
Changes in v3
=============
- Carry Breno's Reviewed-by on the netdev_config helper patch.
- Clarify that the fbnic ring-depth consumer follows the core patch.
- Document initialization, override, normalization, and stored ring
depth semantics.
- Drop the CQE placement assertion from the large-buffer test.
- Reject a zero RX-jumbo depth before validating large RX pages.
- Rebase on net-next.
Changes in v2
=============
- Add generic netdev_config lifecycle helpers for the netlink and ioctl
paths.
- Store ring parameters in netdev_config and revalidate RX queues before
committing a ring change.
- Preserve the distinction between accepted and applied HDS state.
- Track fbnic device-page geometry per BDQ.
- Validate the fbnic RX page size, fragment count, and PPQ depth.
- Add automated large-buffer test coverage.
v2: https://lore.kernel.org/netdev/20260910180908.1506533-1-bjorn@kernel.org/
v1: https://lore.kernel.org/netdev/20260522113225.241337-1-bjorn@kernel.org/
Björn Töpel (4):
fbnic: Track BDQ device-page geometry per ring
net: Revalidate queue config for ringparam changes
fbnic: Support larger memory-provider RX pages
selftests: drv-net: Request larger zcrx buffers
Jakub Kicinski (1):
net: Add netdev_config helpers
drivers/net/ethernet/meta/fbnic/fbnic_csr.h | 34 +---
.../net/ethernet/meta/fbnic/fbnic_debugfs.c | 5 +-
.../net/ethernet/meta/fbnic/fbnic_netdev.c | 5 +
drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 173 ++++++++++++++----
drivers/net/ethernet/meta/fbnic/fbnic_txrx.h | 7 +
include/net/netdev_queues.h | 49 ++++-
net/core/dev.c | 7 +-
net/core/dev.h | 7 +
net/core/netdev_config.c | 75 +++++++-
net/ethtool/common.c | 9 +
net/ethtool/common.h | 2 +
net/ethtool/ioctl.c | 24 ++-
net/ethtool/netlink.c | 15 +-
net/ethtool/rings.c | 13 +-
.../selftests/drivers/net/hw/iou-zcrx.py | 6 +-
15 files changed, 345 insertions(+), 86 deletions(-)
base-commit: b8e9e7d82e7eefd5d2d528469d94ec20e96b38c3
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v3 1/5] net: Add netdev_config helpers
2026-09-15 18:20 [PATCH net-next v3 0/5] fbnic: Support larger RX pages Björn Töpel
@ 2026-09-15 18:20 ` Björn Töpel
2026-09-15 18:21 ` [PATCH net-next v3 2/5] fbnic: Track BDQ device-page geometry per ring Björn Töpel
` (3 subsequent siblings)
4 siblings, 0 replies; 10+ messages in thread
From: Björn Töpel @ 2026-09-15 18:20 UTC (permalink / raw)
To: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Shuah Khan, netdev, linux-kernel, linux-kselftest,
Daniel Borkmann
Cc: Mike Marciniszyn (Meta),
Mohsin Bashir, Stanislav Fomichev, Bobby Eshleman,
Dimitri Daskalakis, Weiming Shi, Maxime Chevallier, Jacob Keller,
Breno Leitao, Tao Cui, Pavel Begunkov, David Wei,
Björn Töpel
From: Jakub Kicinski <kuba@kernel.org>
netdev_config manipulation will become slightly more complicated
soon and will be used by both ethtool and the queue API.
Encapsulate the logic in helper functions.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Björn Töpel <bjorn@kernel.org>
Reviewed-by: Breno Leitao <leitao@debian.org>
---
net/core/dev.c | 7 ++-----
net/core/dev.h | 5 +++++
net/core/netdev_config.c | 37 +++++++++++++++++++++++++++++++++++++
net/ethtool/netlink.c | 15 +++++++--------
4 files changed, 51 insertions(+), 13 deletions(-)
diff --git a/net/core/dev.c b/net/core/dev.c
index ecfbd72d5d1a..b113f412bbf1 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -12195,10 +12195,8 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
if (!dev->ethtool)
goto free_all;
- dev->cfg = kzalloc_obj(*dev->cfg, GFP_KERNEL_ACCOUNT);
- if (!dev->cfg)
+ if (netdev_alloc_config(dev))
goto free_all;
- dev->cfg_pending = dev->cfg;
dev->num_napi_configs = maxqs;
napi_config_sz = array_size(maxqs, sizeof(*dev->napi_config));
@@ -12270,8 +12268,7 @@ void free_netdev(struct net_device *dev)
return;
}
- WARN_ON(dev->cfg != dev->cfg_pending);
- kfree(dev->cfg);
+ netdev_free_config(dev);
kfree(dev->ethtool);
netif_free_tx_queues(dev);
netif_free_rx_queues(dev);
diff --git a/net/core/dev.h b/net/core/dev.h
index b757faead4d1..4b52ff779cba 100644
--- a/net/core/dev.h
+++ b/net/core/dev.h
@@ -102,6 +102,11 @@ extern struct rw_semaphore dev_addr_sem;
extern struct list_head net_todo_list;
void netdev_run_todo(void);
+int netdev_alloc_config(struct net_device *dev);
+void __netdev_free_config(struct netdev_config *cfg);
+void netdev_free_config(struct net_device *dev);
+int netdev_reconfig_start(struct net_device *dev);
+
int netdev_queue_config_validate(struct net_device *dev, int rxq_idx,
struct netdev_queue_config *qcfg,
struct netlink_ext_ack *extack);
diff --git a/net/core/netdev_config.c b/net/core/netdev_config.c
index f14af365d5cd..b101341e3251 100644
--- a/net/core/netdev_config.c
+++ b/net/core/netdev_config.c
@@ -6,6 +6,43 @@
#include "dev.h"
+int netdev_alloc_config(struct net_device *dev)
+{
+ struct netdev_config *cfg;
+
+ cfg = kzalloc_obj(*dev->cfg, GFP_KERNEL_ACCOUNT);
+ if (!cfg)
+ return -ENOMEM;
+
+ dev->cfg = cfg;
+ dev->cfg_pending = cfg;
+ return 0;
+}
+
+void __netdev_free_config(struct netdev_config *cfg)
+{
+ kfree(cfg);
+}
+
+void netdev_free_config(struct net_device *dev)
+{
+ WARN_ON(dev->cfg != dev->cfg_pending);
+ __netdev_free_config(dev->cfg);
+}
+
+int netdev_reconfig_start(struct net_device *dev)
+{
+ struct netdev_config *cfg;
+
+ WARN_ON(dev->cfg != dev->cfg_pending);
+ cfg = kmemdup(dev->cfg, sizeof(*dev->cfg), GFP_KERNEL_ACCOUNT);
+ if (!cfg)
+ return -ENOMEM;
+
+ dev->cfg_pending = cfg;
+ return 0;
+}
+
static int netdev_nop_validate_qcfg(struct net_device *dev,
struct netdev_queue_config *qcfg,
struct netlink_ext_ack *extack)
diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 1af395b54330..383e911f50f7 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -11,6 +11,8 @@
#include "module_fw.h"
#include "netlink.h"
+#include "../core/dev.h"
+
static struct genl_family ethtool_genl_family;
static bool ethnl_ok __read_mostly;
@@ -934,12 +936,9 @@ static int ethnl_default_set_doit(struct sk_buff *skb, struct genl_info *info)
if (need_rtnl)
rtnl_lock();
netdev_lock_ops(dev);
- dev->cfg_pending = kmemdup(dev->cfg, sizeof(*dev->cfg),
- GFP_KERNEL_ACCOUNT);
- if (!dev->cfg_pending) {
- ret = -ENOMEM;
- goto out_tie_cfg;
- }
+ ret = netdev_reconfig_start(dev);
+ if (ret)
+ goto out_unlock;
ret = ethnl_ops_begin(dev);
if (ret < 0)
@@ -958,9 +957,9 @@ static int ethnl_default_set_doit(struct sk_buff *skb, struct genl_info *info)
out_ops:
ethnl_ops_complete(dev);
out_free_cfg:
- kfree(dev->cfg_pending);
-out_tie_cfg:
+ __netdev_free_config(dev->cfg_pending);
dev->cfg_pending = dev->cfg;
+out_unlock:
netdev_unlock_ops(dev);
if (need_rtnl)
rtnl_unlock();
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v3 2/5] fbnic: Track BDQ device-page geometry per ring
2026-09-15 18:20 [PATCH net-next v3 0/5] fbnic: Support larger RX pages Björn Töpel
2026-09-15 18:20 ` [PATCH net-next v3 1/5] net: Add netdev_config helpers Björn Töpel
@ 2026-09-15 18:21 ` Björn Töpel
2026-09-16 14:57 ` Breno Leitao
2026-09-15 18:21 ` [PATCH net-next v3 3/5] net: Revalidate queue config for ringparam changes Björn Töpel
` (2 subsequent siblings)
4 siblings, 1 reply; 10+ messages in thread
From: Björn Töpel @ 2026-09-15 18:21 UTC (permalink / raw)
To: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Shuah Khan, netdev, linux-kernel, linux-kselftest,
Daniel Borkmann
Cc: Björn Töpel, Mike Marciniszyn (Meta),
Mohsin Bashir, Stanislav Fomichev, Bobby Eshleman,
Dimitri Daskalakis, Weiming Shi, Maxime Chevallier, Jacob Keller,
Breno Leitao, Tao Cui, Pavel Begunkov, David Wei
fbnic derives the BDQ buffer layout from PAGE_SIZE. That decides at
build time how a posted page is split into 4 KiB device pages, and
uses the same completion decoding for HPQ and PPQ. That is wrong once
the queues use different posted-page sizes; completions must be
decoded with the geometry of the queue that produced them.
Keep PAGE_SIZE as the posted-page size for both queues in this change,
so the descriptor format and runtime behavior stay unchanged.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
drivers/net/ethernet/meta/fbnic/fbnic_csr.h | 34 ++------
.../net/ethernet/meta/fbnic/fbnic_debugfs.c | 5 +-
drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 83 ++++++++++++-------
drivers/net/ethernet/meta/fbnic/fbnic_txrx.h | 6 ++
4 files changed, 72 insertions(+), 56 deletions(-)
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_csr.h b/drivers/net/ethernet/meta/fbnic/fbnic_csr.h
index 64b958df7774..2b9f8644b132 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_csr.h
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_csr.h
@@ -109,34 +109,19 @@ enum {
/* Rx Buffer Descriptor Format
*
- * The layout of this can vary depending on the page size of the system.
+ * Buffer descriptors describe 4 KiB device pages. A posted page larger than
+ * 4 KiB is represented by consecutive device-page descriptors.
*
- * If the page size is 4K then the layout will simply consist of ID for
- * the 16 most significant bits, and the lower 46 are essentially the page
- * address with the lowest 12 bits being reserved 0 due to the fact that
- * a page will be aligned.
- *
- * If the page size is larger than 4K then the lower n bits of the ID and
- * page address will be reserved for the fragment ID. This fragment will
- * be 4K in size and will be used to index both the DMA address and the ID
- * by the same amount.
+ * The address field stores the 4 KiB-aligned DMA address. The ID field stores
+ * the software page ID, with the low n bits used as the device-page ID when a
+ * posted page spans multiple device pages. The driver increments both the
+ * address and ID by one device page for each descriptor belonging to a posted
+ * page.
*/
#define FBNIC_BD_DESC_ADDR_MASK DESC_GENMASK(45, 12)
#define FBNIC_BD_DESC_ID_MASK DESC_GENMASK(63, 48)
-#define FBNIC_BD_FRAG_SIZE \
+#define FBNIC_BD_PAGE_SIZE \
(FBNIC_BD_DESC_ADDR_MASK & ~(FBNIC_BD_DESC_ADDR_MASK - 1))
-#define FBNIC_BD_FRAG_COUNT \
- (PAGE_SIZE / FBNIC_BD_FRAG_SIZE)
-#define FBNIC_BD_FRAG_ADDR_MASK \
- (FBNIC_BD_DESC_ADDR_MASK & \
- ~(FBNIC_BD_DESC_ADDR_MASK * FBNIC_BD_FRAG_COUNT))
-#define FBNIC_BD_FRAG_ID_MASK \
- (FBNIC_BD_DESC_ID_MASK & \
- ~(FBNIC_BD_DESC_ID_MASK * FBNIC_BD_FRAG_COUNT))
-#define FBNIC_BD_PAGE_ADDR_MASK \
- (FBNIC_BD_DESC_ADDR_MASK & ~FBNIC_BD_FRAG_ADDR_MASK)
-#define FBNIC_BD_PAGE_ID_MASK \
- (FBNIC_BD_DESC_ID_MASK & ~FBNIC_BD_FRAG_ID_MASK)
/* Rx Completion Queue Descriptors */
#define FBNIC_RCD_TYPE_MASK DESC_GENMASK(62, 61)
@@ -151,9 +136,6 @@ enum {
/* Address/Length Completion Descriptors */
#define FBNIC_RCD_AL_BUFF_ID_MASK DESC_GENMASK(15, 0)
-#define FBNIC_RCD_AL_BUFF_FRAG_MASK (FBNIC_BD_FRAG_COUNT - 1)
-#define FBNIC_RCD_AL_BUFF_PAGE_MASK \
- (FBNIC_RCD_AL_BUFF_ID_MASK & ~FBNIC_RCD_AL_BUFF_FRAG_MASK)
#define FBNIC_RCD_AL_BUFF_LEN_MASK DESC_GENMASK(28, 16)
#define FBNIC_RCD_AL_BUFF_OFF_MASK DESC_GENMASK(43, 32)
#define FBNIC_RCD_AL_PAGE_FIN DESC_BIT(60)
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c b/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c
index 3c4563c8f403..7f23a0f97e63 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_debugfs.c
@@ -181,8 +181,8 @@ static int fbnic_dbg_tcq_desc_seq_show(struct seq_file *s, void *v)
static int fbnic_dbg_bdq_desc_seq_show(struct seq_file *s, void *v)
{
struct fbnic_ring *ring = s->private;
+ unsigned int i, desc_count;
char hdr[80];
- int i;
/* Generate header on first entry */
fbnic_dbg_ring_show(s);
@@ -197,7 +197,8 @@ static int fbnic_dbg_bdq_desc_seq_show(struct seq_file *s, void *v)
return 0;
}
- for (i = 0; i < (ring->size_mask + 1) * FBNIC_BD_FRAG_COUNT; i++) {
+ desc_count = (ring->size_mask + 1) * fbnic_bd_page_count(ring);
+ for (i = 0; i < desc_count; i++) {
u64 bd = le64_to_cpu(ring->desc[i]);
seq_printf(s, "%04x %#04llx %#014llx\n", i,
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
index 4cf87b39829d..fef51d454102 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
@@ -872,19 +872,31 @@ static void fbnic_clean_bdq(struct fbnic_ring *ring, unsigned int hw_head,
ring->head = head;
}
+static u16 fbnic_rcd_bd_idx(const struct fbnic_ring *bdq, u64 rcd)
+{
+ return FIELD_GET(FBNIC_RCD_AL_BUFF_ID_MASK, rcd) >> bdq->bd_page_shift;
+}
+
+static unsigned int fbnic_rcd_bd_page_offset(const struct fbnic_ring *bdq,
+ u64 rcd)
+{
+ u16 id = FIELD_GET(FBNIC_RCD_AL_BUFF_ID_MASK, rcd);
+ u16 page_id = id & (fbnic_bd_page_count(bdq) - 1);
+
+ return page_id * FBNIC_BD_PAGE_SIZE;
+}
+
static void fbnic_bd_prep(struct fbnic_ring *bdq, u16 id, netmem_ref netmem)
{
- __le64 *bdq_desc = &bdq->desc[id * FBNIC_BD_FRAG_COUNT];
+ __le64 *bdq_desc = &bdq->desc[id * fbnic_bd_page_count(bdq)];
dma_addr_t dma = page_pool_get_dma_addr_netmem(netmem);
- u64 bd, i = FBNIC_BD_FRAG_COUNT;
+ u64 bd, i = fbnic_bd_page_count(bdq);
- bd = (FBNIC_BD_PAGE_ADDR_MASK & dma) |
- FIELD_PREP(FBNIC_BD_PAGE_ID_MASK, id);
+ bd = (FBNIC_BD_DESC_ADDR_MASK & dma) |
+ FIELD_PREP(FBNIC_BD_DESC_ID_MASK, (u64)id << bdq->bd_page_shift);
- /* In the case that a page size is larger than 4K we will map a
- * single page to multiple fragments. The fragments will be
- * FBNIC_BD_FRAG_COUNT in size and the lower n bits will be use
- * to indicate the individual fragment IDs.
+ /* Posted pages larger than 4 KiB use consecutive device-page IDs in
+ * the low bits of the software page ID.
*/
do {
*bdq_desc = cpu_to_le64(bd);
@@ -929,7 +941,7 @@ static void fbnic_fill_bdq(struct fbnic_ring *bdq)
/* Force DMA writes to flush before writing to tail */
dma_wmb();
- writel(i * FBNIC_BD_FRAG_COUNT, bdq->doorbell);
+ writel(i * fbnic_bd_page_count(bdq), bdq->doorbell);
}
}
@@ -960,26 +972,27 @@ static void fbnic_pkt_prepare(struct fbnic_napi_vector *nv, u64 rcd,
struct fbnic_pkt_buff *pkt,
struct fbnic_q_triad *qt)
{
- unsigned int hdr_pg_idx = FIELD_GET(FBNIC_RCD_AL_BUFF_PAGE_MASK, rcd);
unsigned int hdr_pg_off = FIELD_GET(FBNIC_RCD_AL_BUFF_OFF_MASK, rcd);
- struct page *page = fbnic_page_pool_get_head(qt, hdr_pg_idx);
unsigned int len = FIELD_GET(FBNIC_RCD_AL_BUFF_LEN_MASK, rcd);
+ unsigned int hdr_pg_idx = fbnic_rcd_bd_idx(&qt->sub0, rcd);
unsigned int frame_sz, hdr_pg_start, hdr_pg_end, headroom;
unsigned char *hdr_start;
+ struct page *page;
/* data_hard_start should always be NULL when this is called */
WARN_ON_ONCE(pkt->buff.data_hard_start);
+ page = fbnic_page_pool_get_head(qt, hdr_pg_idx);
+
/* Short-cut the end calculation if we know page is fully consumed */
hdr_pg_end = FIELD_GET(FBNIC_RCD_AL_PAGE_FIN, rcd) ?
- FBNIC_BD_FRAG_SIZE : fbnic_hdr_pg_end(hdr_pg_off, len);
+ FBNIC_BD_PAGE_SIZE : fbnic_hdr_pg_end(hdr_pg_off, len);
hdr_pg_start = fbnic_hdr_pg_start(hdr_pg_off);
headroom = hdr_pg_off - hdr_pg_start + FBNIC_RX_PAD;
frame_sz = hdr_pg_end - hdr_pg_start;
xdp_init_buff(&pkt->buff, frame_sz, &qt->xdp_rxq);
- hdr_pg_start += (FBNIC_RCD_AL_BUFF_FRAG_MASK & rcd) *
- FBNIC_BD_FRAG_SIZE;
+ hdr_pg_start += fbnic_rcd_bd_page_offset(&qt->sub0, rcd);
/* Sync DMA buffer */
dma_sync_single_range_for_cpu(nv->dev, page_pool_get_dma_addr(page),
@@ -1000,18 +1013,19 @@ static void fbnic_add_rx_frag(struct fbnic_napi_vector *nv, u64 rcd,
struct fbnic_pkt_buff *pkt,
struct fbnic_q_triad *qt)
{
- unsigned int pg_idx = FIELD_GET(FBNIC_RCD_AL_BUFF_PAGE_MASK, rcd);
unsigned int pg_off = FIELD_GET(FBNIC_RCD_AL_BUFF_OFF_MASK, rcd);
unsigned int len = FIELD_GET(FBNIC_RCD_AL_BUFF_LEN_MASK, rcd);
- netmem_ref netmem = fbnic_page_pool_get_data(qt, pg_idx);
+ unsigned int pg_idx = fbnic_rcd_bd_idx(&qt->sub1, rcd);
unsigned int truesize;
+ netmem_ref netmem;
bool added;
- truesize = FIELD_GET(FBNIC_RCD_AL_PAGE_FIN, rcd) ?
- FBNIC_BD_FRAG_SIZE - pg_off : ALIGN(len, 128);
+ netmem = fbnic_page_pool_get_data(qt, pg_idx);
- pg_off += (FBNIC_RCD_AL_BUFF_FRAG_MASK & rcd) *
- FBNIC_BD_FRAG_SIZE;
+ truesize = FIELD_GET(FBNIC_RCD_AL_PAGE_FIN, rcd) ?
+ FBNIC_BD_PAGE_SIZE - pg_off : ALIGN(len, 128);
+
+ pg_off += fbnic_rcd_bd_page_offset(&qt->sub1, rcd);
/* Sync DMA buffer */
page_pool_dma_sync_netmem_for_cpu(qt->sub1.page_pool, netmem,
@@ -1258,12 +1272,12 @@ static int fbnic_clean_rcq(struct fbnic_napi_vector *nv,
switch (FIELD_GET(FBNIC_RCD_TYPE_MASK, rcd)) {
case FBNIC_RCD_TYPE_HDR_AL:
- head0 = FIELD_GET(FBNIC_RCD_AL_BUFF_PAGE_MASK, rcd);
+ head0 = fbnic_rcd_bd_idx(&qt->sub0, rcd);
fbnic_pkt_prepare(nv, rcd, pkt, qt);
break;
case FBNIC_RCD_TYPE_PAY_AL:
- head1 = FIELD_GET(FBNIC_RCD_AL_BUFF_PAGE_MASK, rcd);
+ head1 = fbnic_rcd_bd_idx(&qt->sub1, rcd);
fbnic_add_rx_frag(nv, rcd, pkt, qt);
break;
@@ -1604,6 +1618,16 @@ fbnic_alloc_qt_page_pools(struct fbnic_net *fbn, struct fbnic_q_triad *qt,
return PTR_ERR(pp);
}
+static u8 fbnic_bdq_page_shift(u32 page_size)
+{
+ return ilog2(page_size / FBNIC_BD_PAGE_SIZE);
+}
+
+static void fbnic_bdq_set_page_size(struct fbnic_ring *bdq, u32 page_size)
+{
+ bdq->bd_page_shift = fbnic_bdq_page_shift(page_size);
+}
+
static void fbnic_ring_init(struct fbnic_ring *ring, u32 __iomem *doorbell,
int q_idx, u8 flags)
{
@@ -1611,6 +1635,7 @@ static void fbnic_ring_init(struct fbnic_ring *ring, u32 __iomem *doorbell,
ring->doorbell = doorbell;
ring->q_idx = q_idx;
ring->flags = flags;
+ fbnic_bdq_set_page_size(ring, PAGE_SIZE);
ring->deferred_head = -1;
}
@@ -1895,12 +1920,12 @@ static int fbnic_alloc_rx_ring_desc(struct fbnic_net *fbn,
switch (rxr->doorbell - fbnic_ring_csr_base(rxr)) {
case FBNIC_QUEUE_BDQ_HPQ_TAIL:
- rxq_size = fbn->hpq_size / FBNIC_BD_FRAG_COUNT;
- desc_size *= FBNIC_BD_FRAG_COUNT;
+ rxq_size = fbn->hpq_size / fbnic_bd_page_count(rxr);
+ desc_size *= fbnic_bd_page_count(rxr);
break;
case FBNIC_QUEUE_BDQ_PPQ_TAIL:
- rxq_size = fbn->ppq_size / FBNIC_BD_FRAG_COUNT;
- desc_size *= FBNIC_BD_FRAG_COUNT;
+ rxq_size = fbn->ppq_size / fbnic_bd_page_count(rxr);
+ desc_size *= fbnic_bd_page_count(rxr);
break;
case FBNIC_QUEUE_RCQ_HEAD:
rxq_size = fbn->rcq_size;
@@ -2566,7 +2591,7 @@ static void fbnic_enable_bdq(struct fbnic_ring *hpq, struct fbnic_ring *ppq)
hpq->tail = 0;
hpq->head = 0;
- log_size = fls(hpq->size_mask) + ilog2(FBNIC_BD_FRAG_COUNT);
+ log_size = fls(hpq->size_mask) + hpq->bd_page_shift;
/* Store descriptor ring address and size */
fbnic_ring_wr32(hpq, FBNIC_QUEUE_BDQ_HPQ_BAL, lower_32_bits(hpq->dma));
@@ -2578,7 +2603,7 @@ static void fbnic_enable_bdq(struct fbnic_ring *hpq, struct fbnic_ring *ppq)
if (!ppq->size_mask)
goto write_ctl;
- log_size = fls(ppq->size_mask) + ilog2(FBNIC_BD_FRAG_COUNT);
+ log_size = fls(ppq->size_mask) + ppq->bd_page_shift;
/* Add enabling of PPQ to BDQ control */
bdq_ctl |= FBNIC_QUEUE_BDQ_CTL_PPQ_ENABLE;
@@ -2863,8 +2888,10 @@ static int fbnic_queue_mem_alloc(struct net_device *dev,
fbnic_ring_init(&qt->sub0, real->sub0.doorbell, real->sub0.q_idx,
real->sub0.flags);
+ qt->sub0.bd_page_shift = real->sub0.bd_page_shift;
fbnic_ring_init(&qt->sub1, real->sub1.doorbell, real->sub1.q_idx,
real->sub1.flags);
+ qt->sub1.bd_page_shift = real->sub1.bd_page_shift;
fbnic_ring_init(&qt->cmpl, real->cmpl.doorbell, real->cmpl.q_idx,
real->cmpl.flags);
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h
index 98bb73765587..1f4e9150f2ef 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h
@@ -131,6 +131,7 @@ struct fbnic_ring {
u16 size_mask; /* Size of ring in descriptors - 1 */
u8 q_idx; /* Logical netdev ring index */
u8 flags; /* Ring flags (FBNIC_RING_F_*) */
+ u8 bd_page_shift; /* BDQ: ilog2(page_size / 4096) */
u32 head, tail; /* Head/Tail of ring */
@@ -172,6 +173,11 @@ struct fbnic_napi_vector {
extern const struct netdev_queue_mgmt_ops fbnic_queue_mgmt_ops;
+static inline u16 fbnic_bd_page_count(const struct fbnic_ring *bdq)
+{
+ return 1U << bdq->bd_page_shift;
+}
+
netdev_tx_t fbnic_xmit_frame(struct sk_buff *skb, struct net_device *dev);
netdev_features_t
fbnic_features_check(struct sk_buff *skb, struct net_device *dev,
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v3 3/5] net: Revalidate queue config for ringparam changes
2026-09-15 18:20 [PATCH net-next v3 0/5] fbnic: Support larger RX pages Björn Töpel
2026-09-15 18:20 ` [PATCH net-next v3 1/5] net: Add netdev_config helpers Björn Töpel
2026-09-15 18:21 ` [PATCH net-next v3 2/5] fbnic: Track BDQ device-page geometry per ring Björn Töpel
@ 2026-09-15 18:21 ` Björn Töpel
2026-09-18 9:21 ` netdev-bot+sashiko
2026-09-15 18:21 ` [PATCH net-next v3 4/5] fbnic: Support larger memory-provider RX pages Björn Töpel
2026-09-15 18:21 ` [PATCH net-next v3 5/5] selftests: drv-net: Request larger zcrx buffers Björn Töpel
4 siblings, 1 reply; 10+ messages in thread
From: Björn Töpel @ 2026-09-15 18:21 UTC (permalink / raw)
To: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Shuah Khan, netdev, linux-kernel, linux-kselftest,
Daniel Borkmann
Cc: Björn Töpel, Mike Marciniszyn (Meta),
Mohsin Bashir, Stanislav Fomichev, Bobby Eshleman,
Dimitri Daskalakis, Weiming Shi, Maxime Chevallier, Jacob Keller,
Breno Leitao, Tao Cui, Pavel Begunkov, David Wei
Memory-provider queue configuration is validated when the provider is
bound. A later ethtool ring change may invalidate it because drivers
can size queue memory from both ring depth and RX page size. The fbnic
consumer is added in the following patch.
Keep accepted ring depths in netdev_config and stage proposed values
in cfg_pending. Validate every RX queue before calling the driver.
Each check validates the device defaults, then the queue's
memory-provider override when present. Commit the values only after
the driver accepts them.
The callback receives a rendered configuration rather than a queue ID.
Validation should depend on the configuration, not queue identity.
Checking defaults also covers the case where every queue has a
memory-provider override.
Drivers may normalize ring depths when applying them. Require the
validation callback to use the same normalization and record any
adjustments reported through ethtool_ringparam.
Use the same transaction for ioctl and netlink. Drivers without
ndo_validate_qcfg skip the new validation.
Link: https://lore.kernel.org/all/20250421222827.283737-14-kuba@kernel.org/
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
include/net/netdev_queues.h | 49 ++++++++++++++++++++++++++++++++++---
net/core/dev.h | 2 ++
net/core/netdev_config.c | 38 +++++++++++++++++++++++++---
net/ethtool/common.c | 9 +++++++
net/ethtool/common.h | 2 ++
net/ethtool/ioctl.c | 24 ++++++++++++++++--
net/ethtool/rings.c | 13 +++++++++-
7 files changed, 127 insertions(+), 10 deletions(-)
diff --git a/include/net/netdev_queues.h b/include/net/netdev_queues.h
index 70c9fe9e83cc..6f6f6d74de2e 100644
--- a/include/net/netdev_queues.h
+++ b/include/net/netdev_queues.h
@@ -4,18 +4,58 @@
#include <linux/netdevice.h>
+/**
+ * struct netdev_ring_config - accepted RX/TX ring depth configuration
+ * @rx_pending: Size of the regular RX ring.
+ * @rx_mini_pending: Size of the RX mini ring.
+ * @rx_jumbo_pending: Size of the RX jumbo ring.
+ * @tx_pending: Size of the TX ring.
+ *
+ * This stores only persistent configuration values. Capability fields,
+ * such as max ring sizes, are reported by drivers but are not part of the
+ * accepted configuration.
+ *
+ * These values are only used for queue-configuration validation today.
+ * Drivers may normalize ring sizes without reporting the result through
+ * struct ethtool_ringparam, so they are not necessarily the applied
+ * hardware depths.
+ */
+struct netdev_ring_config {
+ u32 rx_pending;
+ u32 rx_mini_pending;
+ u32 rx_jumbo_pending;
+ u32 tx_pending;
+};
+
/**
* struct netdev_config - queue-related configuration for a netdev
* @hds_thresh: HDS Threshold value.
* @hds_config: HDS value from userspace.
+ * @rings: Accepted RX/TX ring depths.
+ *
+ * Direct values, such as @hds_thresh and @rings, hold the accepted
+ * configuration and always override callback-provided defaults, including
+ * when zero. Drivers which use them for queue rendering must initialize them.
*/
struct netdev_config {
u32 hds_thresh;
u8 hds_config;
+
+ struct netdev_ring_config rings;
};
+/**
+ * struct netdev_queue_config - rendered configuration for an RX queue
+ * @rx_page_size: Size of one RX page-pool allocation.
+ * @rx_ring_size: Configured size of the regular RX ring.
+ * @rx_mini_ring_size: Configured size of the RX mini ring.
+ * @rx_jumbo_ring_size: Configured size of the RX jumbo ring.
+ */
struct netdev_queue_config {
u32 rx_page_size;
+ u32 rx_ring_size;
+ u32 rx_mini_ring_size;
+ u32 rx_jumbo_ring_size;
};
/* See the netdev.yaml spec for definition of each statistic */
@@ -145,10 +185,11 @@ enum {
*
* @ndo_validate_qcfg: (Optional) Check if queue config is supported.
* Called when configuration affecting a queue may be
- * changing, either due to NIC-wide config, or config
- * scoped to the queue at a specified index.
- * When NIC-wide config is changed the callback will
- * be invoked for all queues.
+ * changing. When NIC-wide config is changed the
+ * callback will be invoked for the defaults and all
+ * queue overrides. Drivers which normalize device-wide
+ * values when applying them must use the same
+ * normalization during validation.
*
* @ndo_queue_create: Create a new RX queue on a virtual device that will
* be paired with a physical device's queue via leasing.
diff --git a/net/core/dev.h b/net/core/dev.h
index 4b52ff779cba..567e7b82ef24 100644
--- a/net/core/dev.h
+++ b/net/core/dev.h
@@ -110,6 +110,8 @@ int netdev_reconfig_start(struct net_device *dev);
int netdev_queue_config_validate(struct net_device *dev, int rxq_idx,
struct netdev_queue_config *qcfg,
struct netlink_ext_ack *extack);
+int netdev_queue_config_revalidate(struct net_device *dev,
+ struct netlink_ext_ack *extack);
bool netif_rxq_has_mp(struct net_device *dev, unsigned int rxq_idx);
bool netif_rxq_is_leased(struct net_device *dev, unsigned int rxq_idx);
diff --git a/net/core/netdev_config.c b/net/core/netdev_config.c
index b101341e3251..1975de42a60d 100644
--- a/net/core/netdev_config.c
+++ b/net/core/netdev_config.c
@@ -50,6 +50,15 @@ static int netdev_nop_validate_qcfg(struct net_device *dev,
return 0;
}
+static void netdev_qcfg_apply_dev(struct netdev_queue_config *qcfg,
+ const struct netdev_config *cfg)
+{
+ /* Device config overrides callback-provided fallbacks. */
+ qcfg->rx_ring_size = cfg->rings.rx_pending;
+ qcfg->rx_mini_ring_size = cfg->rings.rx_mini_pending;
+ qcfg->rx_jumbo_ring_size = cfg->rings.rx_jumbo_pending;
+}
+
static int __netdev_queue_config(struct net_device *dev, int rxq_idx,
struct netdev_queue_config *qcfg,
struct netlink_ext_ack *extack,
@@ -70,6 +79,7 @@ static int __netdev_queue_config(struct net_device *dev, int rxq_idx,
/* Get defaults from the driver, in case user config not set */
if (dev->queue_mgmt_ops->ndo_default_qcfg)
dev->queue_mgmt_ops->ndo_default_qcfg(dev, qcfg);
+ netdev_qcfg_apply_dev(qcfg, dev->cfg_pending);
err = validate_cb(dev, qcfg, extack);
if (err)
return err;
@@ -91,9 +101,11 @@ static int __netdev_queue_config(struct net_device *dev, int rxq_idx,
* @rxq_idx: index of the queue of interest
* @qcfg: queue configuration struct (output)
*
- * Render the configuration for a given queue. This helper should be used
- * by drivers which support queue configuration to retrieve config for
- * a particular queue.
+ * Render the configuration for a given queue. During a configuration
+ * transaction this includes the proposed device-wide values in
+ * @dev->cfg_pending; otherwise @dev->cfg_pending points to the accepted
+ * configuration. This helper should be used by drivers which support queue
+ * configuration to retrieve config for a particular queue.
*
* @qcfg is an output parameter and is always fully initialized by this
* function. Some values may not be set by the user, drivers may either
@@ -113,3 +125,23 @@ int netdev_queue_config_validate(struct net_device *dev, int rxq_idx,
{
return __netdev_queue_config(dev, rxq_idx, qcfg, extack, true);
}
+
+int netdev_queue_config_revalidate(struct net_device *dev,
+ struct netlink_ext_ack *extack)
+{
+ const struct netdev_queue_mgmt_ops *qops = dev->queue_mgmt_ops;
+ struct netdev_queue_config qcfg;
+ unsigned int i;
+ int err;
+
+ if (!qops || !qops->ndo_validate_qcfg)
+ return 0;
+
+ for (i = 0; i < dev->real_num_rx_queues; i++) {
+ err = netdev_queue_config_validate(dev, i, &qcfg, extack);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 23db40618fed..05ed22fd1f90 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -956,6 +956,15 @@ void ethtool_ringparam_get_cfg(struct net_device *dev,
kparam->hds_thresh = dev->cfg->hds_thresh;
}
+void ethtool_ringparam_set_cfg(struct netdev_config *cfg,
+ const struct ethtool_ringparam *param)
+{
+ cfg->rings.rx_pending = param->rx_pending;
+ cfg->rings.rx_mini_pending = param->rx_mini_pending;
+ cfg->rings.rx_jumbo_pending = param->rx_jumbo_pending;
+ cfg->rings.tx_pending = param->tx_pending;
+}
+
static void ethtool_init_tsinfo(struct kernel_ethtool_ts_info *info)
{
memset(info, 0, sizeof(*info));
diff --git a/net/ethtool/common.h b/net/ethtool/common.h
index 4e5356e26f40..a27944d4cbf8 100644
--- a/net/ethtool/common.h
+++ b/net/ethtool/common.h
@@ -53,6 +53,8 @@ void ethtool_ringparam_get_cfg(struct net_device *dev,
struct ethtool_ringparam *param,
struct kernel_ethtool_ringparam *kparam,
struct netlink_ext_ack *extack);
+void ethtool_ringparam_set_cfg(struct netdev_config *cfg,
+ const struct ethtool_ringparam *param);
int ethtool_get_rx_ring_count(struct net_device *dev);
diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c
index 4b0bc503f930..dad5a5412f48 100644
--- a/net/ethtool/ioctl.c
+++ b/net/ethtool/ioctl.c
@@ -35,6 +35,7 @@
#include <net/netdev_queues.h>
#include "common.h"
+#include "../core/dev.h"
/* State held across locks and calls for commands which have devlink fallback */
struct ethtool_devlink_compat {
@@ -2239,10 +2240,29 @@ static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr)
ringparam.tx_pending > max.tx_max_pending)
return -EINVAL;
+ ret = netdev_reconfig_start(dev);
+ if (ret)
+ return ret;
+
+ ethtool_ringparam_set_cfg(dev->cfg_pending, &ringparam);
+
+ ret = netdev_queue_config_revalidate(dev, NULL);
+ if (ret)
+ goto out_free_cfg;
+
ret = dev->ethtool_ops->set_ringparam(dev, &ringparam,
&kernel_ringparam, NULL);
- if (!ret)
- ethtool_notify(dev, ETHTOOL_MSG_RINGS_NTF);
+ if (ret)
+ goto out_free_cfg;
+
+ /* Capture ring depth adjustments reported by the driver. */
+ ethtool_ringparam_set_cfg(dev->cfg_pending, &ringparam);
+ swap(dev->cfg, dev->cfg_pending);
+ ethtool_notify(dev, ETHTOOL_MSG_RINGS_NTF);
+
+out_free_cfg:
+ __netdev_free_config(dev->cfg_pending);
+ dev->cfg_pending = dev->cfg;
return ret;
}
diff --git a/net/ethtool/rings.c b/net/ethtool/rings.c
index 9054c89c5d7b..e3810c0320e3 100644
--- a/net/ethtool/rings.c
+++ b/net/ethtool/rings.c
@@ -4,6 +4,7 @@
#include "common.h"
#include "netlink.h"
+#include "../core/dev.h"
struct rings_req_info {
struct ethnl_req_info base;
@@ -299,10 +300,20 @@ ethnl_set_rings(struct ethnl_req_info *req_info, struct genl_info *info)
dev->cfg_pending->hds_config = kernel_ringparam.tcp_data_split;
dev->cfg_pending->hds_thresh = kernel_ringparam.hds_thresh;
+ ethtool_ringparam_set_cfg(dev->cfg_pending, &ringparam);
+
+ ret = netdev_queue_config_revalidate(dev, info->extack);
+ if (ret)
+ return ret;
ret = dev->ethtool_ops->set_ringparam(dev, &ringparam,
&kernel_ringparam, info->extack);
- return ret < 0 ? ret : 1;
+ if (ret < 0)
+ return ret;
+
+ /* Capture ring depth adjustments reported by the driver. */
+ ethtool_ringparam_set_cfg(dev->cfg_pending, &ringparam);
+ return 1;
}
const struct ethnl_request_ops ethnl_rings_request_ops = {
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v3 4/5] fbnic: Support larger memory-provider RX pages
2026-09-15 18:20 [PATCH net-next v3 0/5] fbnic: Support larger RX pages Björn Töpel
` (2 preceding siblings ...)
2026-09-15 18:21 ` [PATCH net-next v3 3/5] net: Revalidate queue config for ringparam changes Björn Töpel
@ 2026-09-15 18:21 ` Björn Töpel
2026-09-18 9:21 ` netdev-bot+sashiko
2026-09-15 18:21 ` [PATCH net-next v3 5/5] selftests: drv-net: Request larger zcrx buffers Björn Töpel
4 siblings, 1 reply; 10+ messages in thread
From: Björn Töpel @ 2026-09-15 18:21 UTC (permalink / raw)
To: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Shuah Khan, netdev, linux-kernel, linux-kselftest,
Daniel Borkmann
Cc: Björn Töpel, Mike Marciniszyn (Meta),
Mohsin Bashir, Stanislav Fomichev, Bobby Eshleman,
Dimitri Daskalakis, Weiming Shi, Maxime Chevallier, Jacob Keller,
Breno Leitao, Tao Cui, Pavel Begunkov, David Wei
Memory providers can set the receive page size for a queue through
QCFG_RX_PAGE_SIZE. fbnic creates PAGE_SIZE PPQ page-pool allocations,
so larger rx_buf_len values are not reflected in the PPQ BDQ geometry.
Use the rendered rx_page_size for PPQ allocations and completion
decoding. The NIC still consumes 4 KiB device pages; a larger PPQ page
is represented by consecutive device-page descriptors, and completion
IDs decode to offsets within the same netmem allocation.
Validate rx_page_size against fbnic's fragment-reference budget,
device-page geometry, and rendered PPQ depth. The page size must be a
power-of-two of at least one device page, its worst-case payload
fragment count must fit the page-pool reference bias, and the PPQ must
retain usable software depth after expansion.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
.../net/ethernet/meta/fbnic/fbnic_netdev.c | 5 +
drivers/net/ethernet/meta/fbnic/fbnic_txrx.c | 96 +++++++++++++++++--
drivers/net/ethernet/meta/fbnic/fbnic_txrx.h | 1 +
3 files changed, 93 insertions(+), 9 deletions(-)
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
index 10bf99be3f24..8bc5e6e5c59e 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c
@@ -773,6 +773,11 @@ struct net_device *fbnic_netdev_alloc(struct fbnic_dev *fbd)
fbn->ppq_size = FBNIC_PPQ_SIZE_DEFAULT;
fbn->rcq_size = FBNIC_RCQ_SIZE_DEFAULT;
+ netdev->cfg->rings.rx_pending = fbn->rcq_size;
+ netdev->cfg->rings.rx_mini_pending = fbn->hpq_size;
+ netdev->cfg->rings.rx_jumbo_pending = fbn->ppq_size;
+ netdev->cfg->rings.tx_pending = fbn->txq_size;
+
fbn->tx_usecs = FBNIC_TX_USECS_DEFAULT;
fbn->rx_usecs = FBNIC_RX_USECS_DEFAULT;
fbn->rx_max_frames = FBNIC_RX_FRAMES_DEFAULT;
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
index fef51d454102..615b74fc7bef 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
@@ -1023,7 +1023,8 @@ static void fbnic_add_rx_frag(struct fbnic_napi_vector *nv, u64 rcd,
netmem = fbnic_page_pool_get_data(qt, pg_idx);
truesize = FIELD_GET(FBNIC_RCD_AL_PAGE_FIN, rcd) ?
- FBNIC_BD_PAGE_SIZE - pg_off : ALIGN(len, 128);
+ FBNIC_BD_PAGE_SIZE - pg_off :
+ ALIGN(len, FBNIC_RX_PAYLD_ALIGN);
pg_off += fbnic_rcd_bd_page_offset(&qt->sub1, rcd);
@@ -1031,6 +1032,9 @@ static void fbnic_add_rx_frag(struct fbnic_napi_vector *nv, u64 rcd,
page_pool_dma_sync_netmem_for_cpu(qt->sub1.page_pool, netmem,
pg_off, truesize);
+ /* Consecutive device-page completions from one PPQ page are adjacent
+ * ranges in the same netmem.
+ */
added = xdp_buff_add_frag(&pkt->buff, netmem, pg_off, len, truesize);
if (unlikely(!added)) {
pkt->add_frag_failed = true;
@@ -1565,7 +1569,7 @@ void fbnic_free_napi_vectors(struct fbnic_net *fbn)
static int
fbnic_alloc_qt_page_pools(struct fbnic_net *fbn, struct fbnic_q_triad *qt,
- unsigned int rxq_idx)
+ unsigned int rxq_idx, u32 rx_page_size)
{
struct page_pool_params pp_params = {
.order = 0,
@@ -1600,6 +1604,8 @@ fbnic_alloc_qt_page_pools(struct fbnic_net *fbn, struct fbnic_q_triad *qt,
qt->sub0.page_pool = pp;
if (netif_rxq_has_unreadable_mp(fbn->netdev, rxq_idx)) {
+ pp_params.order = get_order(rx_page_size);
+ pp_params.max_len = rx_page_size;
pp_params.flags |= PP_FLAG_ALLOW_UNREADABLE_NETMEM;
pp_params.dma_dir = DMA_FROM_DEVICE;
@@ -2029,15 +2035,18 @@ static int fbnic_alloc_tx_qt_resources(struct fbnic_net *fbn,
static int fbnic_alloc_rx_qt_resources(struct fbnic_net *fbn,
struct fbnic_napi_vector *nv,
- struct fbnic_q_triad *qt)
+ struct fbnic_q_triad *qt,
+ u32 rx_page_size)
{
struct device *dev = fbn->netdev->dev.parent;
int err;
- err = fbnic_alloc_qt_page_pools(fbn, qt, qt->cmpl.q_idx);
+ err = fbnic_alloc_qt_page_pools(fbn, qt, qt->cmpl.q_idx, rx_page_size);
if (err)
return err;
+ fbnic_bdq_set_page_size(&qt->sub1, rx_page_size);
+
err = xdp_rxq_info_reg(&qt->xdp_rxq, fbn->netdev, qt->sub0.q_idx,
nv->napi.napi_id);
if (err)
@@ -2098,7 +2107,11 @@ static int fbnic_alloc_nv_resources(struct fbnic_net *fbn,
/* Allocate Rx Resources */
for (j = 0; j < nv->rxt_count; j++, i++) {
- err = fbnic_alloc_rx_qt_resources(fbn, nv, &nv->qt[i]);
+ struct netdev_queue_config qcfg;
+
+ netdev_queue_config(fbn->netdev, nv->qt[i].cmpl.q_idx, &qcfg);
+ err = fbnic_alloc_rx_qt_resources(fbn, nv, &nv->qt[i],
+ qcfg.rx_page_size);
if (err)
goto free_qt_resources;
}
@@ -2881,21 +2894,83 @@ static int fbnic_queue_mem_alloc(struct net_device *dev,
struct fbnic_napi_vector *nv;
if (!netif_running(dev))
- return fbnic_alloc_qt_page_pools(fbn, qt, idx);
+ return fbnic_alloc_qt_page_pools(fbn, qt, idx,
+ qcfg->rx_page_size);
real = container_of(fbn->rx[idx], struct fbnic_q_triad, cmpl);
nv = fbn->napi[idx % fbn->num_napi];
fbnic_ring_init(&qt->sub0, real->sub0.doorbell, real->sub0.q_idx,
real->sub0.flags);
- qt->sub0.bd_page_shift = real->sub0.bd_page_shift;
fbnic_ring_init(&qt->sub1, real->sub1.doorbell, real->sub1.q_idx,
real->sub1.flags);
- qt->sub1.bd_page_shift = real->sub1.bd_page_shift;
fbnic_ring_init(&qt->cmpl, real->cmpl.doorbell, real->cmpl.q_idx,
real->cmpl.flags);
- return fbnic_alloc_rx_qt_resources(fbn, nv, qt);
+ return fbnic_alloc_rx_qt_resources(fbn, nv, qt, qcfg->rx_page_size);
+}
+
+static void fbnic_default_qcfg(struct net_device *dev,
+ struct netdev_queue_config *qcfg)
+{
+ qcfg->rx_page_size = PAGE_SIZE;
+}
+
+static int fbnic_validate_qcfg(struct net_device *dev,
+ struct netdev_queue_config *qcfg,
+ struct netlink_ext_ack *extack)
+{
+ u32 bd_page_count, ppq_entries, frag_count;
+ u32 rx_page_size = qcfg->rx_page_size;
+ u32 ppq_size;
+
+ if (!qcfg->rx_jumbo_ring_size) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "rx-jumbo ring size must be nonzero");
+ return -EINVAL;
+ }
+
+ ppq_size = roundup_pow_of_two(qcfg->rx_jumbo_ring_size);
+
+ if (!is_power_of_2(rx_page_size)) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "rx_page_size must be a power of 2");
+ return -EINVAL;
+ }
+
+ if (rx_page_size < FBNIC_BD_PAGE_SIZE) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "rx_page_size must be at least 4K");
+ return -EINVAL;
+ }
+
+ /* Payload fragments occupy multiples of FBNIC_RX_PAYLD_ALIGN bytes.
+ * Keep at least one reference in the bias until fbnic_clean_bdq()
+ * observes a completion from a subsequent allocation.
+ */
+ frag_count = rx_page_size / FBNIC_RX_PAYLD_ALIGN;
+ if (frag_count >= FBNIC_PAGECNT_BIAS_MAX) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "rx_page_size can produce too many fragments");
+ return -EINVAL;
+ }
+
+ bd_page_count = rx_page_size / FBNIC_BD_PAGE_SIZE;
+ ppq_entries = ppq_size / bd_page_count;
+ /* The PPQ is sized in 4 KiB device pages. One software entry tracks
+ * each page-pool allocation. In addition to the unused entry for
+ * empty/full accounting, cleanup retains the current allocation
+ * until a completion identifies a subsequent allocation. A two-entry
+ * ring can only post one allocation and cannot make progress.
+ * Require at least four entries, since ring sizes are powers of two.
+ */
+ if (ppq_entries < 4) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "rx-jumbo ring size too small for rx_page_size");
+ return -EINVAL;
+ }
+
+ return 0;
}
static void fbnic_queue_mem_free(struct net_device *dev, void *qmem)
@@ -2997,4 +3072,7 @@ const struct netdev_queue_mgmt_ops fbnic_queue_mgmt_ops = {
.ndo_queue_mem_free = fbnic_queue_mem_free,
.ndo_queue_start = fbnic_queue_start,
.ndo_queue_stop = fbnic_queue_stop,
+ .ndo_default_qcfg = fbnic_default_qcfg,
+ .ndo_validate_qcfg = fbnic_validate_qcfg,
+ .supported_params = QCFG_RX_PAGE_SIZE,
};
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h
index 1f4e9150f2ef..cdc1cb1610e9 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.h
@@ -65,6 +65,7 @@ struct fbnic_net;
#define FBNIC_RX_HROOM \
(ALIGN(FBNIC_RX_TROOM + FBNIC_RX_HROOM_PAD, 128) - FBNIC_RX_TROOM)
#define FBNIC_RX_PAD 0
+#define FBNIC_RX_PAYLD_ALIGN 128
#define FBNIC_RX_PAYLD_OFFSET 0
#define FBNIC_RX_PAYLD_PG_CL 0
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH net-next v3 5/5] selftests: drv-net: Request larger zcrx buffers
2026-09-15 18:20 [PATCH net-next v3 0/5] fbnic: Support larger RX pages Björn Töpel
` (3 preceding siblings ...)
2026-09-15 18:21 ` [PATCH net-next v3 4/5] fbnic: Support larger memory-provider RX pages Björn Töpel
@ 2026-09-15 18:21 ` Björn Töpel
2026-09-17 8:45 ` Breno Leitao
4 siblings, 1 reply; 10+ messages in thread
From: Björn Töpel @ 2026-09-15 18:21 UTC (permalink / raw)
To: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Shuah Khan, netdev, linux-kernel, linux-kselftest,
Daniel Borkmann
Cc: Björn Töpel, Mike Marciniszyn (Meta),
Mohsin Bashir, Stanislav Fomichev, Bobby Eshleman,
Dimitri Daskalakis, Weiming Shi, Maxime Chevallier, Jacob Keller,
Breno Leitao, Tao Cui, Pavel Begunkov, David Wei
The large-chunk test always requests two base pages. On interfaces with
a large MTU, that may not exceed two maximum-sized frames.
Request a power-of-two buffer larger than twice the MTU. This makes the
existing rx_buf_len check and data-integrity traffic exercise the larger
layout.
Signed-off-by: Björn Töpel <bjorn@kernel.org>
---
tools/testing/selftests/drivers/net/hw/iou-zcrx.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/drivers/net/hw/iou-zcrx.py b/tools/testing/selftests/drivers/net/hw/iou-zcrx.py
index c833535d8a03..5047fdb56005 100755
--- a/tools/testing/selftests/drivers/net/hw/iou-zcrx.py
+++ b/tools/testing/selftests/drivers/net/hw/iou-zcrx.py
@@ -221,9 +221,13 @@ def test_zcrx_large_chunks(cfg) -> None:
single(cfg)
page_size = resource.getpagesize()
+ mtu = cfg.dev["mtu"]
nr_pages = 2
+ while nr_pages * page_size <= 2 * mtu:
+ nr_pages *= 2
rx_buf_len = nr_pages * page_size
- rx_cmd = f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} -q {cfg.target} -x {nr_pages}"
+ rx_cmd = (f"{cfg.bin_local} -s -p {cfg.port} -i {cfg.ifname} "
+ f"-q {cfg.target} -x {nr_pages}")
tx_cmd = f"{cfg.bin_remote} -c -h {cfg.addr_v['6']} -p {cfg.port} -l 12840"
probe = cmd(rx_cmd + " -d", fail=False)
--
2.55.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v3 2/5] fbnic: Track BDQ device-page geometry per ring
2026-09-15 18:21 ` [PATCH net-next v3 2/5] fbnic: Track BDQ device-page geometry per ring Björn Töpel
@ 2026-09-16 14:57 ` Breno Leitao
0 siblings, 0 replies; 10+ messages in thread
From: Breno Leitao @ 2026-09-16 14:57 UTC (permalink / raw)
To: Björn Töpel
Cc: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Shuah Khan, netdev, linux-kernel, linux-kselftest,
Daniel Borkmann, Mike Marciniszyn (Meta),
Mohsin Bashir, Stanislav Fomichev, Bobby Eshleman,
Dimitri Daskalakis, Weiming Shi, Maxime Chevallier, Jacob Keller,
Tao Cui, Pavel Begunkov, David Wei
On Tue, Sep 15, 2026 at 08:21:00PM +0200, Björn Töpel wrote:
> fbnic derives the BDQ buffer layout from PAGE_SIZE. That decides at
> build time how a posted page is split into 4 KiB device pages, and
> uses the same completion decoding for HPQ and PPQ. That is wrong once
> the queues use different posted-page sizes; completions must be
> decoded with the geometry of the queue that produced them.
>
> Keep PAGE_SIZE as the posted-page size for both queues in this change,
> so the descriptor format and runtime behavior stay unchanged.
>
> Signed-off-by: Björn Töpel <bjorn@kernel.org>
Reviewed-by: Breno Leitao <leitao@debian.org>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v3 5/5] selftests: drv-net: Request larger zcrx buffers
2026-09-15 18:21 ` [PATCH net-next v3 5/5] selftests: drv-net: Request larger zcrx buffers Björn Töpel
@ 2026-09-17 8:45 ` Breno Leitao
0 siblings, 0 replies; 10+ messages in thread
From: Breno Leitao @ 2026-09-17 8:45 UTC (permalink / raw)
To: Björn Töpel
Cc: Alexander Duyck, Jakub Kicinski, kernel-team, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Shuah Khan, netdev, linux-kernel, linux-kselftest,
Daniel Borkmann, Mike Marciniszyn (Meta),
Mohsin Bashir, Stanislav Fomichev, Bobby Eshleman,
Dimitri Daskalakis, Weiming Shi, Maxime Chevallier, Jacob Keller,
Tao Cui, Pavel Begunkov, David Wei
On Tue, Sep 15, 2026 at 08:21:03PM +0200, Björn Töpel wrote:
> The large-chunk test always requests two base pages. On interfaces with
> a large MTU, that may not exceed two maximum-sized frames.
>
> Request a power-of-two buffer larger than twice the MTU. This makes the
> existing rx_buf_len check and data-integrity traffic exercise the larger
> layout.
>
> Signed-off-by: Björn Töpel <bjorn@kernel.org>
Tested-by: Breno Leitao <leitao@debian.org>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v3 3/5] net: Revalidate queue config for ringparam changes
2026-09-15 18:21 ` [PATCH net-next v3 3/5] net: Revalidate queue config for ringparam changes Björn Töpel
@ 2026-09-18 9:21 ` netdev-bot+sashiko
0 siblings, 0 replies; 10+ messages in thread
From: netdev-bot+sashiko @ 2026-09-18 9:21 UTC (permalink / raw)
To: bjorn
Cc: alexanderduyck, kuba, kernel-team, andrew+netdev, davem,
edumazet, pabeni, horms, shuah, netdev, linux-kernel,
linux-kselftest, daniel, mike.marciniszyn, mohsin.bashr, sdf,
bobbyeshleman, daskald, bestswngs, maxime.chevallier,
jacob.e.keller, leitao, cuitao, asml.silence, dw
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 0 · Low: 3
- [Low] netdev_qcfg_apply_dev() (net/core/netdev_config.c:53-60)
unconditionally copies cfg->rings.{rx,rx_mini,rx_jumbo}_pending over…
- [Low] ethtool_ringparam_get_cfg() (net/ethtool/common.c:943-956)
deliberately restores kparam->tcp_data_split / hds_thresh from…
- [Low] The struct netdev_ring_config kernel-doc
(include/net/netdev_queues.h:18-21) says "These values are only used…
--
net: Revalidate queue config for ringparam changes
This adds accepted RX/TX ring depths to struct netdev_config, stages
proposed depths in cfg_pending, and revalidates every RX queue through
ndo_validate_qcfg before the driver applies a ringparam change. The same
transaction is used for both the ioctl and netlink ring-set paths.
Link: https://lore.kernel.org/all/20250421222827.283737-14-kuba@kernel.org/
> diff --git a/include/net/netdev_queues.h b/include/net/netdev_queues.h
> index 70c9fe9e83cc4..6f6f6d74de2e5 100644
> --- a/include/net/netdev_queues.h
> +++ b/include/net/netdev_queues.h
> @@ -4,18 +4,58 @@
>
> #include <linux/netdevice.h>
>
> +/**
> + * struct netdev_ring_config - accepted RX/TX ring depth configuration
> + * @rx_pending: Size of the regular RX ring.
> + * @rx_mini_pending: Size of the RX mini ring.
> + * @rx_jumbo_pending: Size of the RX jumbo ring.
> + * @tx_pending: Size of the TX ring.
> + *
> + * This stores only persistent configuration values. Capability fields,
> + * such as max ring sizes, are reported by drivers but are not part of the
> + * accepted configuration.
> + *
> + * These values are only used for queue-configuration validation today.
[Severity: Low]
Is this sentence accurate for all four members? netdev_qcfg_apply_dev() is
the only place that transfers netdev_config.rings into the struct
netdev_queue_config handed to ndo_validate_qcfg(), and it copies only the
three RX depths:
qcfg->rx_ring_size = cfg->rings.rx_pending;
qcfg->rx_mini_ring_size = cfg->rings.rx_mini_pending;
qcfg->rx_jumbo_ring_size = cfg->rings.rx_jumbo_pending;
struct netdev_queue_config has no TX member, so rings.tx_pending, which
ethtool_ringparam_set_cfg() writes on both the ioctl and netlink paths, can
never reach a validation callback and is write-only state today.
Would it be worth rewording this to say the RX depths feed validation and
that tx_pending is recorded for completeness?
> + * Drivers may normalize ring sizes without reporting the result through
> + * struct ethtool_ringparam, so they are not necessarily the applied
> + * hardware depths.
> + */
> +struct netdev_ring_config {
> + u32 rx_pending;
> + u32 rx_mini_pending;
> + u32 rx_jumbo_pending;
> + u32 tx_pending;
> +};
> +
> /**
> * struct netdev_config - queue-related configuration for a netdev
> * @hds_thresh: HDS Threshold value.
> * @hds_config: HDS value from userspace.
> + * @rings: Accepted RX/TX ring depths.
> + *
> + * Direct values, such as @hds_thresh and @rings, hold the accepted
> + * configuration and always override callback-provided defaults, including
> + * when zero. Drivers which use them for queue rendering must initialize them.
[Severity: Low]
Is this "must initialize them" requirement stated anywhere a driver author
would find it? It appears only here, and not in the commit message or in
the @ndo_default_qcfg documentation, which still reads that the queue config
struct is passed to the callback "before the user-requested settings are
applied".
netdev_alloc_config() only kzalloc()s the struct, and the only core writer of
cfg->rings is ethtool_ringparam_set_cfg() on the two ring-set paths. So
until userspace performs a ring change, cfg->rings is all zeroes and
netdev_qcfg_apply_dev() copies those zeroes over whatever ndo_default_qcfg()
supplied.
Would an "unset" guard, similar to the memory-provider override just below
it, or a core-side seeding step from the driver's current ringparam be
preferable to the per-driver requirement?
For what it's worth, this does not misbehave in-tree at the end of the
series: no in-tree ndo_default_qcfg populates ring depths (bnxt, mlx5e and
fbnic only set qcfg->rx_page_size), the only reader of the new
rx_*_ring_size fields is fbnic_validate_qcfg(), and the following fbnic
patch seeds netdev->cfg->rings from fbn->{rcq,hpq,ppq,txq}_size in
fbnic_netdev_alloc() before registration. The concern here is the doc
contradiction and the fragile contract rather than an observable failure.
> */
> struct netdev_config {
> u32 hds_thresh;
> u8 hds_config;
> +
> + struct netdev_ring_config rings;
> };
>
> +/**
> + * struct netdev_queue_config - rendered configuration for an RX queue
> + * @rx_page_size: Size of one RX page-pool allocation.
> + * @rx_ring_size: Configured size of the regular RX ring.
> + * @rx_mini_ring_size: Configured size of the RX mini ring.
> + * @rx_jumbo_ring_size: Configured size of the RX jumbo ring.
> + */
> struct netdev_queue_config {
> u32 rx_page_size;
> + u32 rx_ring_size;
> + u32 rx_mini_ring_size;
> + u32 rx_jumbo_ring_size;
> };
>
> /* See the netdev.yaml spec for definition of each statistic */
[ ... ]
> diff --git a/net/core/netdev_config.c b/net/core/netdev_config.c
> index b101341e32513..1975de42a60d9 100644
> --- a/net/core/netdev_config.c
> +++ b/net/core/netdev_config.c
> @@ -50,6 +50,15 @@ static int netdev_nop_validate_qcfg(struct net_device *dev,
> return 0;
> }
>
> +static void netdev_qcfg_apply_dev(struct netdev_queue_config *qcfg,
> + const struct netdev_config *cfg)
> +{
> + /* Device config overrides callback-provided fallbacks. */
> + qcfg->rx_ring_size = cfg->rings.rx_pending;
> + qcfg->rx_mini_ring_size = cfg->rings.rx_mini_pending;
> + qcfg->rx_jumbo_ring_size = cfg->rings.rx_jumbo_pending;
> +}
> +
> static int __netdev_queue_config(struct net_device *dev, int rxq_idx,
> struct netdev_queue_config *qcfg,
> struct netlink_ext_ack *extack,
> @@ -70,6 +79,7 @@ static int __netdev_queue_config(struct net_device *dev, int rxq_idx,
> /* Get defaults from the driver, in case user config not set */
> if (dev->queue_mgmt_ops->ndo_default_qcfg)
> dev->queue_mgmt_ops->ndo_default_qcfg(dev, qcfg);
> + netdev_qcfg_apply_dev(qcfg, dev->cfg_pending);
[Severity: Low]
Does the existing comment still describe what happens here? It says the
driver defaults are fetched "in case user config not set", but the new call
replaces the three ring fields unconditionally, whether or not the user ever
set them.
Compare with the memory-provider override a few lines further down, which is
guarded:
mpp = &__netif_get_rx_queue(dev, rxq_idx)->mp_params;
if (mpp->rx_page_size)
qcfg->rx_page_size = mpp->rx_page_size;
Should netdev_qcfg_apply_dev() skip zero values in the same way, or should
the comment be updated to say the ring fields are always taken from
cfg_pending?
> err = validate_cb(dev, qcfg, extack);
> if (err)
> return err;
[ ... ]
> diff --git a/net/ethtool/common.c b/net/ethtool/common.c
> index 23db40618fed1..05ed22fd1f906 100644
> --- a/net/ethtool/common.c
> +++ b/net/ethtool/common.c
> @@ -956,6 +956,15 @@ void ethtool_ringparam_get_cfg(struct net_device *dev,
> kparam->hds_thresh = dev->cfg->hds_thresh;
> }
>
> +void ethtool_ringparam_set_cfg(struct netdev_config *cfg,
> + const struct ethtool_ringparam *param)
> +{
> + cfg->rings.rx_pending = param->rx_pending;
> + cfg->rings.rx_mini_pending = param->rx_mini_pending;
> + cfg->rings.rx_jumbo_pending = param->rx_jumbo_pending;
> + cfg->rings.tx_pending = param->tx_pending;
> +}
[Severity: Low]
Should the get side be extended to match this? ethtool_ringparam_get_cfg()
deliberately overrides two fields with the accepted config:
/* Driver gives us current state, we want to return current config */
kparam->tcp_data_split = dev->cfg->hds_config;
kparam->hds_thresh = dev->cfg->hds_thresh;
but it leaves the four ring depths exactly as reported by
get_ringparam(), i.e. the driver's current hardware state.
ethnl_set_rings() seeds struct ethtool_ringparam from that helper and then
calls ethtool_ringparam_set_cfg() for all four fields, so a request carrying
only ETHTOOL_A_RINGS_TX re-stages and commits RX depths sourced from the
driver's reported state rather than from the previously accepted config.
Which side is meant to be authoritative for the ring depths?
For the record, this does not diverge for the only in-tree reader of
cfg->rings: fbnic_get_ringparam() reports fbn->{rcq,hpq,ppq,txq}_size, which
are the normalized values the core already recorded, and the bnxt and mlx5e
validation callbacks only look at rx_page_size.
>
> static void ethtool_init_tsinfo(struct kernel_ethtool_ts_info *info)
> {
> memset(info, 0, sizeof(*info));
[ ... ]
> diff --git a/net/ethtool/rings.c b/net/ethtool/rings.c
> index 9054c89c5d7bb..e3810c0320e35 100644
> --- a/net/ethtool/rings.c
> +++ b/net/ethtool/rings.c
> @@ -299,10 +300,20 @@ ethnl_set_rings(struct ethnl_req_info *req_info, struct genl_info *info)
>
> dev->cfg_pending->hds_config = kernel_ringparam.tcp_data_split;
> dev->cfg_pending->hds_thresh = kernel_ringparam.hds_thresh;
> + ethtool_ringparam_set_cfg(dev->cfg_pending, &ringparam);
> +
> + ret = netdev_queue_config_revalidate(dev, info->extack);
> + if (ret)
> + return ret;
>
> ret = dev->ethtool_ops->set_ringparam(dev, &ringparam,
> &kernel_ringparam, info->extack);
> - return ret < 0 ? ret : 1;
> + if (ret < 0)
> + return ret;
> +
> + /* Capture ring depth adjustments reported by the driver. */
> + ethtool_ringparam_set_cfg(dev->cfg_pending, &ringparam);
> + return 1;
> }
Thanks for looking at these.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915182105.4162425-1-bjorn%40kernel.org
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v3 4/5] fbnic: Support larger memory-provider RX pages
2026-09-15 18:21 ` [PATCH net-next v3 4/5] fbnic: Support larger memory-provider RX pages Björn Töpel
@ 2026-09-18 9:21 ` netdev-bot+sashiko
0 siblings, 0 replies; 10+ messages in thread
From: netdev-bot+sashiko @ 2026-09-18 9:21 UTC (permalink / raw)
To: bjorn
Cc: alexanderduyck, kuba, kernel-team, andrew+netdev, davem,
edumazet, pabeni, horms, shuah, netdev, linux-kernel,
linux-kselftest, daniel, mike.marciniszyn, mohsin.bashr, sdf,
bobbyeshleman, daskald, bestswngs, maxime.chevallier,
jacob.e.keller, leitao, cuitao, asml.silence, dw
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 1 (1) · Medium: 0 · Low: 0
- [High] fbnic_alloc_qt_page_pools() and fbnic_alloc_rx_qt_resources()
use two non-equivalent predicates for the same decision, which lets…
Pre-existing issues:
- [High] In fbnic_alloc_qt_page_pools() the error label err_destroy_sub0
calls page_pool_destroy(pp) after `pp` has been overwritten with the…
--
fbnic: Support larger memory-provider RX pages
Renders the per-queue rx_page_size and uses it for the PPQ page-pool
allocation order and the PPQ BDQ page geometry, so one page-pool
allocation is described by consecutive 4 KiB device-page descriptors.
Also adds ndo_default_qcfg/ndo_validate_qcfg and advertises
QCFG_RX_PAGE_SIZE.
A couple of questions about the two predicates used for the page-pool
geometry, and about the existing error label in
fbnic_alloc_qt_page_pools().
> diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
> index fef51d4541026..615b74fc7bef4 100644
> --- a/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
> +++ b/drivers/net/ethernet/meta/fbnic/fbnic_txrx.c
[ ... ]
> @@ -1600,6 +1604,8 @@ fbnic_alloc_qt_page_pools(struct fbnic_net *fbn, struct fbnic_q_triad *qt,
>
> qt->sub0.page_pool = pp;
> if (netif_rxq_has_unreadable_mp(fbn->netdev, rxq_idx)) {
> + pp_params.order = get_order(rx_page_size);
> + pp_params.max_len = rx_page_size;
> pp_params.flags |= PP_FLAG_ALLOW_UNREADABLE_NETMEM;
> pp_params.dma_dir = DMA_FROM_DEVICE;
>
[Severity: High]
This isn't a bug introduced by this patch, but the error label reached by
the second page_pool_create() in this function looks broken, and the new
order/max_len parameters add failure modes to that call:
drivers/net/ethernet/meta/fbnic/fbnic_txrx.c:fbnic_alloc_qt_page_pools() {
...
pp = page_pool_create(&pp_params);
if (IS_ERR(pp))
goto err_destroy_sub0;
...
err_destroy_sub0:
page_pool_destroy(pp);
return PTR_ERR(pp);
}
At the label, pp is the ERR_PTR from the failed create, not
qt->sub0.page_pool. page_pool_destroy() only guards against NULL:
net/core/page_pool.c:page_pool_destroy() {
if (!pool)
return;
if (!page_pool_put(pool))
return;
...
}
so page_pool_put() does refcount_dec_and_test(&pool->user_cnt) on an error
pointer. Can this oops, and does it also leak the sub0 page_pool together
with its DMA mappings, given the caller propagates the error and
netdev_rx_queue_reconfig() goes to err_free_old_mem without calling
ndo_queue_mem_free?
Should the label be something like:
err_destroy_sub0:
page_pool_destroy(qt->sub0.page_pool);
return PTR_ERR(pp);
The reason this comes up here is that pp_params.order is now derived from
a user-supplied value, and the memory provider validates it:
net/core/devmem.c:mp_dmabuf_devmem_init() {
...
if (pool->p.order != binding->niov_shift - PAGE_SHIFT)
return -E2BIG;
...
}
fbnic_validate_qcfg() accepts any power-of-two rx_page_size >= 4 KiB, so on
a host with PAGE_SIZE larger than 4 KiB get_order(rx_page_size) can differ
from the provider's chunk order and this create fails deterministically,
not just on -ENOMEM. The label exists verbatim at b8e9e7d82e7e and is
still present at dadc755bdd0c.
> @@ -2029,15 +2035,18 @@ static int fbnic_alloc_tx_qt_resources(struct fbnic_net *fbn,
>
> static int fbnic_alloc_rx_qt_resources(struct fbnic_net *fbn,
> struct fbnic_napi_vector *nv,
> - struct fbnic_q_triad *qt)
> + struct fbnic_q_triad *qt,
> + u32 rx_page_size)
> {
> struct device *dev = fbn->netdev->dev.parent;
> int err;
>
> - err = fbnic_alloc_qt_page_pools(fbn, qt, qt->cmpl.q_idx);
> + err = fbnic_alloc_qt_page_pools(fbn, qt, qt->cmpl.q_idx, rx_page_size);
> if (err)
> return err;
>
> + fbnic_bdq_set_page_size(&qt->sub1, rx_page_size);
> +
[Severity: High]
Can the sub1 BDQ geometry end up describing more memory than the pool
actually allocates here?
The pool order is only raised inside the memory-provider branch of
fbnic_alloc_qt_page_pools():
if (netif_rxq_has_unreadable_mp(fbn->netdev, rxq_idx)) {
pp_params.order = get_order(rx_page_size);
pp_params.max_len = rx_page_size;
but fbnic_bdq_set_page_size(&qt->sub1, rx_page_size) above runs
unconditionally. The two are not the same test:
net/core/netdev_rx_queue.c:netif_rxq_has_unreadable_mp() {
if (rxq_idx < dev->real_num_rx_queues)
return __netif_get_rx_queue(dev, rxq_idx)->mp_params.mp_ops;
return false;
}
net/core/netdev_config.c:__netdev_queue_config() {
...
mpp = &__netif_get_rx_queue(dev, rxq_idx)->mp_params;
if (mpp->rx_page_size)
qcfg->rx_page_size = mpp->rx_page_size;
...
}
So for rxq_idx >= dev->real_num_rx_queues the rendered rx_page_size still
reflects the installed mp_params while netif_rxq_has_unreadable_mp() is
false. sub1 then shares sub0's order-0, PAGE_SIZE, DMA_BIDIRECTIONAL pool
while bd_page_shift claims rx_page_size / 4096 device pages, and
fbnic_bd_prep() posts that many consecutive addresses off one netmem:
drivers/net/ethernet/meta/fbnic/fbnic_txrx.c:fbnic_bd_prep() {
do {
*bdq_desc = cpu_to_le64(bd);
bd += FIELD_PREP(FBNIC_BD_DESC_ADDR_MASK, 1) |
FIELD_PREP(FBNIC_BD_DESC_ID_MASK, 1);
bdq_desc++;
} while (--i);
}
Does that hand the NIC up to rx_page_size - PAGE_SIZE bytes of addresses
past the end of the allocation?
The sequence I think reaches this, all with CAP_NET_ADMIN:
- with the interface down, dev->real_num_rx_queues is FBNIC_MAX_RXQS,
since alloc_etherdev_mq() sets it and only fbnic_set_netif_queues()
(called from __fbnic_open()) narrows it
- bind a dmabuf with rx-page-size 32768 to a high queue index, say 64;
netif_mp_open_rxq() only checks rxq_idx < real_num_rx_queues, and
the !netif_running() path of fbnic_queue_mem_alloc() just creates and
frees page pools, leaving mp_params installed
- bring the link up; fbnic_set_netif_queues() calls
netif_set_real_num_rx_queues(8), and rx_queue_release() does not clear
mp_params at index 64
- ethtool -L combined 128 while up; growing the queue count is not
guarded, and fbnic_alloc_nv_resources() then does
netdev_queue_config(fbn->netdev, nv->qt[i].cmpl.q_idx, &qcfg);
which returns rx_page_size 32768 for index 64 while
netif_rxq_has_unreadable_mp(dev, 64) is false
Would keying both the page-pool parameters and fbnic_bdq_set_page_size()
off the same condition (or clamping rx_page_size to PAGE_SIZE when the
queue has no memory provider) close this?
> @@ -2098,7 +2107,11 @@ static int fbnic_alloc_nv_resources(struct fbnic_net *fbn,
>
> /* Allocate Rx Resources */
> for (j = 0; j < nv->rxt_count; j++, i++) {
> - err = fbnic_alloc_rx_qt_resources(fbn, nv, &nv->qt[i]);
> + struct netdev_queue_config qcfg;
> +
> + netdev_queue_config(fbn->netdev, nv->qt[i].cmpl.q_idx, &qcfg);
> + err = fbnic_alloc_rx_qt_resources(fbn, nv, &nv->qt[i],
> + qcfg.rx_page_size);
> if (err)
> goto free_qt_resources;
> }
[ ... ]
> @@ -2997,4 +3072,7 @@ const struct netdev_queue_mgmt_ops fbnic_queue_mgmt_ops = {
> .ndo_queue_mem_free = fbnic_queue_mem_free,
> .ndo_queue_start = fbnic_queue_start,
> .ndo_queue_stop = fbnic_queue_stop,
> + .ndo_default_qcfg = fbnic_default_qcfg,
> + .ndo_validate_qcfg = fbnic_validate_qcfg,
> + .supported_params = QCFG_RX_PAGE_SIZE,
> };
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915182105.4162425-1-bjorn%40kernel.org
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-09-18 9:21 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-15 18:20 [PATCH net-next v3 0/5] fbnic: Support larger RX pages Björn Töpel
2026-09-15 18:20 ` [PATCH net-next v3 1/5] net: Add netdev_config helpers Björn Töpel
2026-09-15 18:21 ` [PATCH net-next v3 2/5] fbnic: Track BDQ device-page geometry per ring Björn Töpel
2026-09-16 14:57 ` Breno Leitao
2026-09-15 18:21 ` [PATCH net-next v3 3/5] net: Revalidate queue config for ringparam changes Björn Töpel
2026-09-18 9:21 ` netdev-bot+sashiko
2026-09-15 18:21 ` [PATCH net-next v3 4/5] fbnic: Support larger memory-provider RX pages Björn Töpel
2026-09-18 9:21 ` netdev-bot+sashiko
2026-09-15 18:21 ` [PATCH net-next v3 5/5] selftests: drv-net: Request larger zcrx buffers Björn Töpel
2026-09-17 8:45 ` Breno Leitao
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®