* [PATCH net-next v6 0/2] net: macb: implement ethtool set channels count operation
@ 2026-11-13 17:32 Théo Lebrun
2026-11-13 17:32 ` [PATCH net-next v6 1/2] net: macb: implement ethtool_ops.get|set_channels() Théo Lebrun
2026-11-13 17:32 ` [PATCH net-next v6 2/2] net: macb: distribute evenly Tx SRAM segments Théo Lebrun
0 siblings, 2 replies; 4+ messages in thread
From: Théo Lebrun @ 2026-11-13 17:32 UTC (permalink / raw)
To: Conor Dooley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Russell King
Cc: netdev, linux-kernel, Nicolai Buchwitz, Paolo Valerio,
Vladimir Kondratiev, Gregory CLEMENT, Benoît Monin,
Tawfik Bayouk, Thomas Petazzoni, Théo Lebrun
Add support for changing the active number of queues.
Tested on Mobileye EyeQ5.
Now that context swapping landed, update this series and use the feature
on ethtool_ops.set_channels() requests.
- We move num_queues from bp to ctx.
- All readers of the bp->num_queues value now need to pick between:
bp->max_num_queues / bp->configured_num_queues / bp->ctx->num_queues.
- The context swapping in set_channels can fail because
netif_set_real_num_queues can fail. Update macb_context_swap_end()
to allow taking NULL as new context argument. If that is received,
it restarts the interface with the same context as before.
This highlights a few callbacks that are pretty broken if accessed while
interface is down, because they access hardware registers while runtime
suspended. Most importantly stats and taprio. We don't attempt to fix
them in this series! I am mentioning this here to prompt Sashiko. :)
Have a nice day,
Thanks,
Théo
[0]: https://lore.kernel.org/netdev/20261113-macb-caps-v1-0-42e0984b2f09@bootlin.com/
---
Changes in v6:
- Rebase onto latest net-next/main (573af8015529). This means rebasing
onto context swapping, which is now used on set_channels requests.
- Move the QUEUE_DISABLE caps patch out into a separate series [0].
- Drop kunit tests. Thanks Jakub.
- Fix the TAPRIO gate test if qdisc is destroyed while the interface is
down (because bp->taprio_gated_queues was stall in that case).
Thanks Nicolai.
- Link to v5: https://patch.msgid.link/20261016-macb-set-channels-v5-0-90ce0c0b8e75@bootlin.com
Changes in v5:
- New patch: add missing MACB_CAPS_QUEUE_DISABLE to 3 compatibles based
on publicly available datasheets. That means they can benefit from
the set_channels implementation.
- set_channels patch:
- Refuse set_channels if it disables a queue with active TAPRIO.
- Use TBQP.QUEUE_DISABLE bit to also disable transmit; previously we
only disabled the receive engine.
- Migrate code to newly introduced macb_queue_isr_clear() helper.
- Adapt to newly introduced macb_quiesce_start(), where we want to
synchronize_irq() on all queues, even the disabled ones.
- IRQ handler now self-disarms on disabled queues.
- Drop `old_count` unused stack variable.
- Only allow TAPRIO rules on active queues.
- Fix comments style and text in macb_set_channels(); in particular
mention we'll use context-swapping once it is available.
- Distribute SRAM segments patch:
- Introduce GEM_TXQSEGALLOC_MASK(q) and use field_prep|get() macros.
- Introduce capability for this feature, as many publicly available
GEM datasheets don't document the registers.
- Remove upper register support. Lower register is enough to handle 8
queues, which is the max we support.
- Add edge case kunit for num_segments=128.
- Introduce static_assert(MACB_MAX_QUEUES == 8) in
gem_sram_distribute_segments() to signal that code will need
updating if/when the constant changes.
- Update To/Cc list: drop Nicolas and Claudiu, add Conor and Nicolai.
- Rebase onto latest net-next/main (6b3e73774815).
- Link to v4: https://patch.msgid.link/20260317-macb-set-channels-v4-0-1bd4f4ffcfca@bootlin.com
Changes in v4:
- macb_set_channels(): drop `count == old_count` check, it is redundant
with both netlink and ioctl codepaths. See early returns in
ethnl_set_channels() and ethtool_set_channels(). Discussion @ V2.
- Rebase to latest net-next (8737d7194d6d); nothing to report.
- Link to v3: https://patch.msgid.link/20260313-macb-set-channels-v3-0-c66b7781ddb8@bootlin.com
Changes in v3:
- Makefile: fix compilation of kunit tests. This bug was hidden before
because everything was builtin so it ended up linked together anyway.
Now macb.o is the amalgation of macb_{main,ptp,utils}.o and
macb_test.o is the amalgation of macb_{kunit,utils}.o.
- Kconfig CONFIG_MACB_KUNIT_TEST entry:
- Turn into tristate; no good reason to restrict to bool.
- Drop "depends on MACB". We do not need to compile the full MACB
driver to test gem_sram_distribute_segments().
- Add usual MODULE_*() macros to macb_kunit.c.
- Drop unused init value of `ret` in macb_set_channels().
- Rebase to latest net-next; nothing to report.
- Link to v2: https://patch.msgid.link/20260311-macb-set-channels-v2-0-982693a1f5fc@bootlin.com
Changes in v2:
- Move gem_sram_distribute_segments() from macb_main.c to macb_utils.c
and kunit tests from macb_main.c to macb_kunit.c.
- macb_set_channels():
- Add comment about MACB_CAPS_QUEUE_DISABLE.
- Refuse operation if netif_running(), with code comment.
- Drop useless sanity checks on ch->{combined,rx,tx}_count.
- Add help text to CONFIG_MACB_KUNIT_TEST.
- Fix `checkpatch --max-line-length=80` warnings.
- Rebase onto latest net-next; nothing to report.
- Link to v1: https://lore.kernel.org/r/20260305-macb-set-channels-v1-0-28e3a96a3dc3@bootlin.com
To: Conor Dooley <conor.dooley@microchip.com>
To: Andrew Lunn <andrew+netdev@lunn.ch>
To: David S. Miller <davem@davemloft.net>
To: Eric Dumazet <edumazet@kernel.org>
To: Jakub Kicinski <kuba@kernel.org>
To: Paolo Abeni <pabeni@redhat.com>
To: Russell King <linux@armlinux.org.uk>
Cc: netdev@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: Nicolai Buchwitz <nb@tipi-net.de>
Cc: Paolo Valerio <pvalerio@redhat.com>
Cc: Vladimir Kondratiev <vladimir.kondratiev@mobileye.com>
Cc: Gregory CLEMENT <gregory.clement@bootlin.com>
Cc: Benoît Monin <benoit.monin@bootlin.com>
Cc: Tawfik Bayouk <tawfik.bayouk@mobileye.com>
Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Signed-off-by: Théo Lebrun <theo.lebrun@bootlin.com>
---
Théo Lebrun (2):
net: macb: implement ethtool_ops.get|set_channels()
net: macb: distribute evenly Tx SRAM segments
drivers/net/ethernet/cadence/macb.h | 21 +-
drivers/net/ethernet/cadence/macb_main.c | 355 ++++++++++++++++++++++++-------
2 files changed, 294 insertions(+), 82 deletions(-)
---
base-commit: 573af801552999954caa8347e4490b04cf4763f0
change-id: 20260305-macb-set-channels-5bf6e07f3270
Best regards,
--
Théo Lebrun <theo.lebrun@bootlin.com>
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH net-next v6 1/2] net: macb: implement ethtool_ops.get|set_channels()
2026-11-13 17:32 [PATCH net-next v6 0/2] net: macb: implement ethtool set channels count operation Théo Lebrun
@ 2026-11-13 17:32 ` Théo Lebrun
2026-11-13 17:32 ` [PATCH net-next v6 2/2] net: macb: distribute evenly Tx SRAM segments Théo Lebrun
1 sibling, 0 replies; 4+ messages in thread
From: Théo Lebrun @ 2026-11-13 17:32 UTC (permalink / raw)
To: Conor Dooley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Russell King
Cc: netdev, linux-kernel, Nicolai Buchwitz, Paolo Valerio,
Vladimir Kondratiev, Gregory CLEMENT, Benoît Monin,
Tawfik Bayouk, Thomas Petazzoni, Théo Lebrun
bp->num_queues is the total number of queues and is constant from probe.
MACB/GEM does not know about Rx/Tx specific queues; it only has
combined queues. Move bp->num_queues to ctx->num_queues as it now
belongs to the currently active context (bp->ctx).
Also introduce bp->max_num_queues which takes the current role of
bp->num_queues and allow `0 < ctx->num_queues <= bp->max_num_queues`.
Lastly introduce bp->configured_num_queues to remember queue count
across interface down+up cycles.
With all that, implement the .set_channels() operation. It ends up only
being an assignment to bp->configured_num_queues plus a call into
netif_set_real_num_queues(). However if the interface is running, we
use a context swap window to safely realloc on the fly.
Note we refuse the operation if any TAPRIO rules are in place on queues
that would get disabled. If we allow the operation to take place, then
a transmission window would be reserved but unusable.
Nit: fix an alignment issue inside gem_ethtool_ops which does not
deserve its own patch.
Signed-off-by: Théo Lebrun <theo.lebrun@bootlin.com>
---
drivers/net/ethernet/cadence/macb.h | 7 +-
drivers/net/ethernet/cadence/macb_main.c | 279 ++++++++++++++++++++++---------
2 files changed, 206 insertions(+), 80 deletions(-)
diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h
index b7b4b6a38298..0879fb2b198b 100644
--- a/drivers/net/ethernet/cadence/macb.h
+++ b/drivers/net/ethernet/cadence/macb.h
@@ -1292,7 +1292,6 @@ struct macb_info {
struct platform_device *pdev;
struct net_device *netdev;
struct macb_or_gem_ops macbgem_ops;
- unsigned int num_queues;
u32 caps;
int rx_bd_rd_prefetch;
int tx_bd_rd_prefetch;
@@ -1323,6 +1322,7 @@ struct macb_context {
unsigned int rx_buffer_size;
unsigned int rx_ring_size;
unsigned int tx_ring_size;
+ unsigned int num_queues;
struct macb_rxq rxq[MACB_MAX_QUEUES];
struct macb_txq txq[MACB_MAX_QUEUES];
};
@@ -1352,6 +1352,11 @@ struct macb {
unsigned int configured_tx_ring_size;
struct macb_context *ctx;
+ unsigned int configured_num_queues;
+ unsigned int max_num_queues;
+ /* Queue mask gated by the current TAPRIO schedule (RTNL-protected). */
+ u32 taprio_gated_queues;
+
struct macb_dma_desc *rx_ring_tieoff;
dma_addr_t rx_ring_tieoff_dma;
diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index 5bd299fc85a9..e7325af4b399 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -504,11 +504,25 @@ static void macb_init_buffers(struct macb *bp)
macb_writel(bp, TBQPH, upper_32_bits(txq->ring_dma));
}
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
- rxq = &bp->ctx->rxq[q];
- txq = &bp->ctx->txq[q];
- queue_writel(queue, RBQP, lower_32_bits(rxq->ring_dma));
- queue_writel(queue, TBQP, lower_32_bits(txq->ring_dma));
+ for (q = 0, queue = bp->queues; q < bp->max_num_queues; ++q, ++queue) {
+ if (q < bp->ctx->num_queues) {
+ rxq = &bp->ctx->rxq[q];
+ txq = &bp->ctx->txq[q];
+ queue_writel(queue, RBQP, lower_32_bits(rxq->ring_dma));
+ queue_writel(queue, TBQP, lower_32_bits(txq->ring_dma));
+ } else {
+ /* macb_set_channels(), which is the only way of writing
+ * to bp->configured_num_queues, is only allowed if
+ * MACB_CAPS_QUEUE_DISABLE.
+ */
+ queue_writel(queue, RBQP, MACB_BIT(QUEUE_DISABLE));
+ queue_writel(queue, TBQP, MACB_BIT(QUEUE_DISABLE));
+
+ /* Disable all interrupts */
+ queue_writel(queue, IDR, -1);
+ queue_readl(queue, ISR);
+ macb_queue_isr_clear(bp, queue, -1);
+ }
}
}
@@ -695,7 +709,7 @@ static bool macb_tx_all_queues_idle(struct macb *bp)
struct macb_txq *txq;
unsigned int q;
- for (q = 0; q < bp->num_queues; ++q) {
+ for (q = 0; q < bp->ctx->num_queues; ++q) {
txq = &bp->ctx->txq[q];
if (READ_ONCE(txq->head) != READ_ONCE(txq->tail))
return false;
@@ -849,7 +863,8 @@ static void macb_mac_link_down(struct phylink_config *config, unsigned int mode,
bp->link_up = false;
if (!(bp->caps & MACB_CAPS_MACB_IS_EMAC))
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue)
+ for (q = 0, queue = bp->queues; q < bp->ctx->num_queues;
+ ++q, ++queue)
queue_writel(queue, IDR,
bp->rx_intr_mask | MACB_TX_INT_FLAGS | MACB_BIT(HRESP));
@@ -951,7 +966,7 @@ static void gem_shuffle_tx_rings(struct macb *bp)
struct macb_queue *queue;
unsigned int q;
- for (q = 0, queue = bp->queues; q < bp->num_queues; q++, queue++)
+ for (q = 0, queue = bp->queues; q < bp->ctx->num_queues; q++, queue++)
gem_shuffle_tx_one_ring(queue);
}
@@ -995,10 +1010,10 @@ static void macb_mac_link_up(struct phylink_config *config,
if (rx_pause)
ctrl |= MACB_BIT(PAE);
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ for (q = 0, queue = bp->queues; q < bp->ctx->num_queues;
+ ++q, ++queue)
queue_writel(queue, IER,
bp->rx_intr_mask | MACB_TX_INT_FLAGS | MACB_BIT(HRESP));
- }
}
macb_or_gem_writel(bp, NCFGR, ctrl);
@@ -2150,12 +2165,12 @@ static void macb_quiesce_start(struct macb *bp)
bp->irq_quiesced = true;
spin_unlock_irqrestore(&bp->lock, flags);
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue)
+ for (q = 0, queue = bp->queues; q < bp->max_num_queues; ++q, ++queue)
synchronize_irq(queue->irq);
cancel_work_sync(&bp->hresp_err_bh_work);
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ for (q = 0, queue = bp->queues; q < bp->ctx->num_queues; ++q, ++queue) {
/* Must be done before NAPI is disabled: the task ends with a
* napi_enable() call.
*/
@@ -2183,7 +2198,7 @@ static void macb_quiesce_end(struct macb *bp)
unsigned long flags;
unsigned int q;
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ for (q = 0, queue = bp->queues; q < bp->ctx->num_queues; ++q, ++queue) {
napi_enable(&queue->napi_rx);
napi_enable(&queue->napi_tx);
}
@@ -2202,7 +2217,7 @@ static void macb_hresp_error_task(struct work_struct *work)
unsigned int q;
u32 ctrl;
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ for (q = 0, queue = bp->queues; q < ctx->num_queues; ++q, ++queue) {
queue_writel(queue, IDR, bp->rx_intr_mask |
MACB_TX_INT_FLAGS |
MACB_BIT(HRESP));
@@ -2220,7 +2235,7 @@ static void macb_hresp_error_task(struct work_struct *work)
macb_init_buffers(bp);
/* Enable interrupts */
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue)
+ for (q = 0, queue = bp->queues; q < ctx->num_queues; ++q, ++queue)
queue_writel(queue, IER,
bp->rx_intr_mask |
MACB_TX_INT_FLAGS |
@@ -2321,7 +2336,9 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id)
{
struct macb_queue *queue = dev_id;
struct macb *bp = queue->bp;
+ unsigned int num_queues = 0;
struct net_device *netdev = bp->netdev;
+ unsigned int q = queue - bp->queues;
bool hresp_err = false;
u32 status;
@@ -2332,19 +2349,22 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id)
spin_lock(&bp->lock);
+ if (bp->ctx)
+ num_queues = READ_ONCE(bp->ctx->num_queues);
+
while (status) {
- /* self-disarm while the netdev is closed
- * or during context swap
+ /* self-disarm while the netdev is closed,
+ * during context swap, or when the current queue is disabled
*/
- if (unlikely(bp->irq_quiesced)) {
+ if (unlikely(bp->irq_quiesced ||
+ q >= num_queues)) {
queue_writel(queue, IDR, -1);
macb_queue_isr_clear(bp, queue, -1);
break;
}
netdev_vdbg(netdev, "queue = %u, isr = 0x%08lx\n",
- (unsigned int)(queue - bp->queues),
- (unsigned long)status);
+ q, (unsigned long)status);
if (status & bp->rx_intr_mask) {
/* There's no point taking any more interrupts
@@ -2402,7 +2422,7 @@ static void macb_poll_controller(struct net_device *netdev)
unsigned int q;
local_irq_save(flags);
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue)
+ for (q = 0, queue = bp->queues; q < bp->ctx->num_queues; ++q, ++queue)
macb_interrupt(netdev->irq, queue);
local_irq_restore(flags);
}
@@ -2845,7 +2865,7 @@ static void gem_free_rx_buffers(struct macb_context *ctx)
unsigned int q;
int i;
- for (q = 0; q < ctx->info->num_queues; ++q) {
+ for (q = 0; q < ctx->num_queues; ++q) {
rxq = &ctx->rxq[q];
if (!rxq->skbuff)
@@ -2907,14 +2927,14 @@ static void macb_free(struct macb_context *ctx)
ctx->info->macbgem_ops.mog_free_rx_buffers(ctx);
txq = &ctx->txq[0];
- size = ctx->info->num_queues * macb_tx_ring_size_per_queue(ctx);
+ size = ctx->num_queues * macb_tx_ring_size_per_queue(ctx);
dma_free_coherent(dev, size, txq->ring, txq->ring_dma);
rxq = &ctx->rxq[0];
- size = ctx->info->num_queues * macb_rx_ring_size_per_queue(ctx);
+ size = ctx->num_queues * macb_rx_ring_size_per_queue(ctx);
dma_free_coherent(dev, size, rxq->ring, rxq->ring_dma);
- for (q = 0; q < ctx->info->num_queues; ++q) {
+ for (q = 0; q < ctx->num_queues; ++q) {
txq = &ctx->txq[q];
rxq = &ctx->rxq[q];
@@ -2947,7 +2967,7 @@ static int gem_alloc_rx_buffers(struct macb_context *ctx)
unsigned int q;
int size;
- for (q = 0; q < ctx->info->num_queues; ++q) {
+ for (q = 0; q < ctx->num_queues; ++q) {
rxq = &ctx->rxq[q];
size = ctx->rx_ring_size * sizeof(struct sk_buff *);
rxq->skbuff = kzalloc(size, GFP_KERNEL);
@@ -2981,8 +3001,8 @@ static int macb_alloc_rx_buffers(struct macb_context *ctx)
static int macb_alloc(struct macb_context *ctx)
{
- unsigned int num_queues = ctx->info->num_queues;
struct device *dev = &ctx->info->pdev->dev;
+ unsigned int num_queues = ctx->num_queues;
dma_addr_t tx_dma, rx_dma;
struct macb_txq *txq;
struct macb_rxq *rxq;
@@ -3051,6 +3071,7 @@ static int macb_alloc(struct macb_context *ctx)
}
static struct macb_context *macb_context_alloc(struct macb *bp,
+ unsigned int num_queues,
unsigned int mtu,
unsigned int rx_ring_size,
unsigned int tx_ring_size)
@@ -3063,6 +3084,7 @@ static struct macb_context *macb_context_alloc(struct macb *bp,
return ERR_PTR(-ENOMEM);
ctx->info = &bp->info;
+ ctx->num_queues = num_queues;
ctx->rx_buffer_size = macb_rx_buffer_size(bp, mtu);
ctx->rx_ring_size = rx_ring_size;
ctx->tx_ring_size = tx_ring_size;
@@ -3110,7 +3132,7 @@ static int gem_init_rings(struct macb_context *ctx)
int err;
int i;
- for (q = 0; q < ctx->info->num_queues; ++q) {
+ for (q = 0; q < ctx->num_queues; ++q) {
txq = &ctx->txq[q];
for (i = 0; i < ctx->tx_ring_size; i++) {
desc = macb_tx_desc(ctx, q, i);
@@ -3173,7 +3195,7 @@ static void macb_reset_hw(struct macb *bp)
gem_writel(bp, PBUFRXCUT, 0);
/* Disable all interrupts */
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ for (q = 0, queue = bp->queues; q < bp->ctx->num_queues; ++q, ++queue) {
queue_writel(queue, IDR, -1);
queue_readl(queue, ISR);
macb_queue_isr_clear(bp, queue, -1);
@@ -3263,7 +3285,8 @@ static void macb_configure_dma(struct macb *bp)
buffer_size = bp->ctx->rx_buffer_size / RX_BUFFER_MULTIPLE;
if (macb_is_gem(&bp->info)) {
dmacfg = gem_readl(bp, DMACFG) & ~GEM_BF(RXBS, -1L);
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ for (q = 0, queue = bp->queues; q < bp->ctx->num_queues;
+ ++q, ++queue) {
if (q)
queue_writel(queue, RBQS, buffer_size);
else
@@ -3312,7 +3335,7 @@ static void macb_context_swap_start(struct macb *bp)
netif_tx_disable(bp->netdev);
/* Now that everything is stopped, clear DQL. */
- for (q = 0; q < bp->num_queues; ++q)
+ for (q = 0; q < bp->ctx->num_queues; ++q)
netdev_tx_reset_queue(netdev_get_tx_queue(bp->netdev, q));
/* Safe to call outside bp->lock because bp->irq_quiesced ensures the IRQ
@@ -3328,7 +3351,7 @@ static void macb_context_swap_start(struct macb *bp)
ctrl = macb_readl(bp, NCR);
macb_writel(bp, NCR, ctrl & ~(MACB_BIT(RE) | MACB_BIT(TE)));
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ for (q = 0, queue = bp->queues; q < bp->ctx->num_queues; ++q, ++queue) {
queue_writel(queue, IDR, -1);
queue_readl(queue, ISR);
macb_queue_isr_clear(bp, queue, -1);
@@ -3351,16 +3374,21 @@ static void macb_context_swap_end(struct macb *bp,
lockdep_assert_held(&bp->mac_cfg_lock);
- /* Swap contexts & give buffer pointers to HW. */
+ /* Swap contexts & give buffer pointers to HW.
+ * !new_ctx means that the swap failed and we keep the old context.
+ */
+
+ if (new_ctx) {
+ old_ctx = bp->ctx;
+ WRITE_ONCE(bp->ctx, new_ctx);
+ wmb(); /* ensure IRQ enabled below read the new context */
+ }
- old_ctx = bp->ctx;
- WRITE_ONCE(bp->ctx, new_ctx);
- wmb(); /* ensure IRQ enabled below read the new context */
macb_init_buffers(bp);
/* Start NAPI, HW Tx/Rx and software Tx. */
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ for (q = 0, queue = bp->queues; q < bp->ctx->num_queues; ++q, ++queue) {
napi_enable(&queue->napi_rx);
napi_enable(&queue->napi_tx);
}
@@ -3373,7 +3401,8 @@ static void macb_context_swap_end(struct macb *bp,
macb_configure_dma(bp);
if (bp->link_up) {
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ for (q = 0, queue = bp->queues; q < bp->ctx->num_queues;
+ ++q, ++queue) {
queue_writel(queue, IER,
bp->rx_intr_mask |
MACB_TX_INT_FLAGS |
@@ -3397,8 +3426,10 @@ static void macb_context_swap_end(struct macb *bp,
/* Free old context. */
- macb_free(old_ctx);
- kfree(old_ctx);
+ if (new_ctx) {
+ macb_free(old_ctx);
+ kfree(old_ctx);
+ }
}
static void macb_init_hw(struct macb *bp)
@@ -3572,7 +3603,7 @@ static int macb_open(struct net_device *netdev)
if (err < 0)
return err;
- ctx = macb_context_alloc(bp, netdev->mtu,
+ ctx = macb_context_alloc(bp, bp->configured_num_queues, netdev->mtu,
bp->configured_rx_ring_size,
bp->configured_tx_ring_size);
if (IS_ERR(ctx)) {
@@ -3637,7 +3668,7 @@ static int macb_close(struct net_device *netdev)
* and tx_error_task wake them up.
*/
netif_tx_stop_all_queues(netdev);
- for (q = 0; q < bp->num_queues; ++q)
+ for (q = 0; q < bp->ctx->num_queues; ++q)
netdev_tx_reset_queue(netdev_get_tx_queue(netdev, q));
phylink_stop(bp->phylink);
@@ -3673,7 +3704,8 @@ static int macb_change_mtu(struct net_device *netdev, int new_mtu)
if (bp->caps & MACB_CAPS_MACB_IS_EMAC)
return -EBUSY;
- new_ctx = macb_context_alloc(bp, new_mtu,
+ new_ctx = macb_context_alloc(bp, bp->configured_num_queues,
+ new_mtu,
bp->configured_rx_ring_size,
bp->configured_tx_ring_size);
if (IS_ERR(new_ctx))
@@ -3726,7 +3758,7 @@ static void gem_update_stats(struct macb *bp)
}
idx = GEM_STATS_LEN;
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue)
+ for (q = 0, queue = bp->queues; q < bp->max_num_queues; ++q, ++queue)
for (i = 0, stat = &queue->stats.first; i < QUEUE_STATS_LEN; ++i, ++stat)
bp->ethtool_stats[idx++] = *stat;
}
@@ -3777,7 +3809,8 @@ static void gem_get_ethtool_stats(struct net_device *netdev,
spin_lock_irq(&bp->stats_lock);
gem_update_stats(bp);
memcpy(data, &bp->ethtool_stats, sizeof(u64)
- * (GEM_STATS_LEN + QUEUE_STATS_LEN * bp->num_queues));
+ * (GEM_STATS_LEN + QUEUE_STATS_LEN *
+ bp->max_num_queues));
spin_unlock_irq(&bp->stats_lock);
}
@@ -3787,7 +3820,7 @@ static int gem_get_sset_count(struct net_device *netdev, int sset)
switch (sset) {
case ETH_SS_STATS:
- return GEM_STATS_LEN + bp->num_queues * QUEUE_STATS_LEN;
+ return GEM_STATS_LEN + bp->max_num_queues * QUEUE_STATS_LEN;
default:
return -EOPNOTSUPP;
}
@@ -3806,7 +3839,8 @@ static void gem_get_ethtool_strings(struct net_device *netdev, u32 sset, u8 *p)
memcpy(p, gem_statistics[i].stat_string,
ETH_GSTRING_LEN);
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ for (q = 0, queue = bp->queues; q < bp->max_num_queues;
+ ++q, ++queue) {
for (i = 0; i < QUEUE_STATS_LEN; i++)
ethtool_sprintf(&p, "q%u_%s", q, queue_statistics[i].stat_string);
}
@@ -4165,8 +4199,9 @@ static int macb_set_ringparam(struct net_device *netdev,
}
if (running) {
- new_ctx = macb_context_alloc(bp, netdev->mtu,
- new_rx_size, new_tx_size);
+ new_ctx = macb_context_alloc(bp, bp->configured_num_queues,
+ netdev->mtu, new_rx_size,
+ new_tx_size);
if (IS_ERR(new_ctx))
return PTR_ERR(new_ctx);
@@ -4513,7 +4548,7 @@ static u32 gem_get_rx_ring_count(struct net_device *netdev)
{
struct macb *bp = netdev_priv(netdev);
- return bp->num_queues;
+ return bp->max_num_queues;
}
static int gem_get_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd,
@@ -4544,6 +4579,7 @@ static int gem_get_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd,
static int gem_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd)
{
struct macb *bp = netdev_priv(netdev);
+ unsigned int num_queues = bp->configured_num_queues;
int ret;
if (!(netdev->hw_features & NETIF_F_NTUPLE))
@@ -4551,8 +4587,8 @@ static int gem_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd)
switch (cmd->cmd) {
case ETHTOOL_SRXCLSRLINS:
- if ((cmd->fs.location >= bp->max_tuples)
- || (cmd->fs.ring_cookie >= bp->num_queues)) {
+ if (cmd->fs.location >= bp->max_tuples ||
+ cmd->fs.ring_cookie >= num_queues) {
ret = -EINVAL;
break;
}
@@ -4570,6 +4606,75 @@ static int gem_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd)
return ret;
}
+static void macb_get_channels(struct net_device *netdev,
+ struct ethtool_channels *ch)
+{
+ struct macb *bp = netdev_priv(netdev);
+
+ ch->max_combined = bp->max_num_queues;
+ ch->combined_count = bp->configured_num_queues;
+}
+
+static int macb_set_channels(struct net_device *netdev,
+ struct ethtool_channels *ch)
+{
+ struct macb *bp = netdev_priv(netdev);
+ unsigned int num_queues = ch->combined_count;
+ bool running = netif_running(bp->netdev);
+ struct macb_context *new_ctx;
+ int ret;
+
+ /* MACB_CAPS_QUEUE_DISABLE means that the field QUEUE_DISABLE/BIT0 in
+ * the per-queue RBQP/TBQP registers disable Tx/Rx queues. If we don't
+ * have that capability we can have multiple queues but we must always
+ * run with all enabled.
+ */
+ if (!(bp->caps & MACB_CAPS_QUEUE_DISABLE))
+ return -EOPNOTSUPP;
+
+ if (num_queues > bp->max_num_queues)
+ return -EINVAL;
+
+ if (num_queues == bp->configured_num_queues)
+ return 0;
+
+ /* If we allow disable a queue which has TAPRIO active, we'll keep a
+ * transmission window which will be unusable.
+ */
+ if (bp->taprio_gated_queues >> num_queues) {
+ netdev_err(bp->netdev, "TAPRIO schedule gates queues >= %u; destroy the qdisc before reducing channels\n",
+ num_queues);
+ return -EBUSY;
+ }
+
+ if (running) {
+ new_ctx = macb_context_alloc(bp, num_queues, netdev->mtu,
+ bp->configured_rx_ring_size,
+ bp->configured_tx_ring_size);
+ if (IS_ERR(new_ctx))
+ return PTR_ERR(new_ctx);
+
+ macb_context_swap_start(bp);
+ }
+
+ ret = netif_set_real_num_queues(bp->netdev, num_queues, num_queues);
+ if (ret) {
+ if (running) {
+ macb_free(new_ctx);
+ kfree(new_ctx);
+ macb_context_swap_end(bp, NULL);
+ }
+ return ret;
+ }
+
+ bp->configured_num_queues = num_queues;
+
+ if (running)
+ macb_context_swap_end(bp, new_ctx);
+
+ return 0;
+}
+
static const struct ethtool_ops macb_ethtool_ops = {
.get_regs_len = macb_get_regs_len,
.get_regs = macb_get_regs,
@@ -4585,6 +4690,8 @@ static const struct ethtool_ops macb_ethtool_ops = {
.set_link_ksettings = macb_set_link_ksettings,
.get_ringparam = macb_get_ringparam,
.set_ringparam = macb_set_ringparam,
+ .get_channels = macb_get_channels,
+ .set_channels = macb_set_channels,
};
static int macb_get_eee(struct net_device *netdev, struct ethtool_keee *eee)
@@ -4619,12 +4726,14 @@ static const struct ethtool_ops gem_ethtool_ops = {
.set_link_ksettings = macb_set_link_ksettings,
.get_ringparam = macb_get_ringparam,
.set_ringparam = macb_set_ringparam,
- .get_rxnfc = gem_get_rxnfc,
- .set_rxnfc = gem_set_rxnfc,
- .get_rx_ring_count = gem_get_rx_ring_count,
- .nway_reset = phy_ethtool_nway_reset,
+ .get_rxnfc = gem_get_rxnfc,
+ .set_rxnfc = gem_set_rxnfc,
+ .get_rx_ring_count = gem_get_rx_ring_count,
+ .nway_reset = phy_ethtool_nway_reset,
.get_eee = macb_get_eee,
.set_eee = macb_set_eee,
+ .get_channels = macb_get_channels,
+ .set_channels = macb_set_channels,
};
static int macb_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd)
@@ -4765,9 +4874,9 @@ static int macb_taprio_setup_replace(struct net_device *netdev,
size_t i, q;
int err;
- if (conf->num_entries > bp->num_queues) {
- netdev_err(netdev, "Too many TAPRIO entries: %zu > %d queues\n",
- conf->num_entries, bp->num_queues);
+ if (conf->num_entries > bp->configured_num_queues) {
+ netdev_err(netdev, "Too many TAPRIO entries: %zu > %u active queues\n",
+ conf->num_entries, bp->configured_num_queues);
return -EINVAL;
}
@@ -4790,7 +4899,7 @@ static int macb_taprio_setup_replace(struct net_device *netdev,
return -EINVAL;
}
- enst_queue = kzalloc_objs(*enst_queue, bp->num_queues);
+ enst_queue = kzalloc_objs(*enst_queue, bp->configured_num_queues);
if (unlikely(!enst_queue))
return -ENOMEM;
@@ -4815,9 +4924,10 @@ static int macb_taprio_setup_replace(struct net_device *netdev,
/* gate_mask must not select queues outside the valid queues */
queue_id = order_base_2(entry->gate_mask);
- if (queue_id >= bp->num_queues) {
- netdev_err(netdev, "Entry %zu: gate_mask 0x%x exceeds queue range (max_queues=%d)\n",
- i, entry->gate_mask, bp->num_queues);
+ if (queue_id >= bp->configured_num_queues) {
+ netdev_err(netdev, "Entry %zu: gate_mask 0x%x exceeds active queue range (num_queues=%u)\n",
+ i, entry->gate_mask,
+ bp->configured_num_queues);
err = -EINVAL;
goto cleanup;
}
@@ -4876,11 +4986,12 @@ static int macb_taprio_setup_replace(struct net_device *netdev,
/* All validations passed - proceed with hardware configuration */
scoped_guard(spinlock_irqsave, &bp->lock) {
/* Disable ENST queues if running before configuring */
- queue_mask = BIT_U32(bp->num_queues) - 1;
+ queue_mask = BIT_U32(bp->max_num_queues) - 1;
gem_writel(bp, ENST_CONTROL,
queue_mask << GEM_ENST_DISABLE_QUEUE_OFFSET);
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ for (q = 0, queue = bp->queues; q < bp->configured_num_queues;
+ ++q, ++queue) {
/* Configure queue timing registers */
queue_writel(queue, ENST_START_TIME,
enst_queue[q].start_time_mask);
@@ -4894,6 +5005,9 @@ static int macb_taprio_setup_replace(struct net_device *netdev,
gem_writel(bp, ENST_CONTROL, configured_queues);
}
+ /* Remember which queues are gated, to validate .set_channels(). */
+ bp->taprio_gated_queues = configured_queues;
+
netdev_info(netdev, "TAPRIO configuration completed successfully: %zu entries, %d queues configured\n",
conf->num_entries, hweight32(configured_queues));
@@ -4910,7 +5024,12 @@ static void macb_taprio_destroy(struct net_device *netdev)
unsigned int q;
netdev_reset_tc(netdev);
- queue_mask = BIT_U32(bp->num_queues) - 1;
+ bp->taprio_gated_queues = 0;
+
+ if (unlikely(pm_runtime_suspended(&bp->pdev->dev)))
+ return;
+
+ queue_mask = BIT_U32(bp->max_num_queues) - 1;
scoped_guard(spinlock_irqsave, &bp->lock) {
/* Single disable command for all queues */
@@ -4918,7 +5037,8 @@ static void macb_taprio_destroy(struct net_device *netdev)
queue_mask << GEM_ENST_DISABLE_QUEUE_OFFSET);
/* Clear all queue ENST registers in batch */
- for (q = 0, queue = bp->queues; q < bp->num_queues; ++q, ++queue) {
+ for (q = 0, queue = bp->queues; q < bp->max_num_queues;
+ ++q, ++queue) {
queue_writel(queue, ENST_START_TIME, 0);
queue_writel(queue, ENST_ON_TIME, 0);
queue_writel(queue, ENST_OFF_TIME, 0);
@@ -4936,15 +5056,14 @@ static int macb_setup_taprio(struct net_device *netdev,
if (unlikely(!(netdev->hw_features & NETIF_F_HW_TC)))
return -EOPNOTSUPP;
- /* Check if Device is in runtime suspend */
- if (unlikely(pm_runtime_suspended(&bp->pdev->dev))) {
- netdev_err(netdev, "Device is in runtime suspend\n");
- return -EOPNOTSUPP;
- }
-
switch (taprio->cmd) {
case TAPRIO_CMD_REPLACE:
- err = macb_taprio_setup_replace(netdev, taprio);
+ if (unlikely(pm_runtime_suspended(&bp->pdev->dev))) {
+ netdev_err(netdev, "Device is in runtime suspend\n");
+ err = -EOPNOTSUPP;
+ } else {
+ err = macb_taprio_setup_replace(netdev, taprio);
+ }
break;
case TAPRIO_CMD_DESTROY:
macb_taprio_destroy(netdev);
@@ -5196,7 +5315,7 @@ static int macb_init_dflt(struct platform_device *pdev)
* register mapping but we don't want to test the queue index then
* compute the corresponding register offset at run time.
*/
- for (hw_q = 0, q = 0; hw_q < bp->num_queues; ++hw_q) {
+ for (hw_q = 0, q = 0; hw_q < bp->max_num_queues; ++hw_q) {
queue = &bp->queues[q];
queue->bp = bp;
spin_lock_init(&queue->tx_ptr_lock);
@@ -6308,7 +6427,8 @@ static int macb_probe(struct platform_device *pdev)
bp->macb_reg_readl = hw_readl;
bp->macb_reg_writel = hw_writel;
}
- bp->num_queues = num_queues;
+ bp->max_num_queues = num_queues;
+ bp->configured_num_queues = num_queues;
bp->dma_burst_length = macb_config->dma_burst_length;
bp->pclk = pclk;
bp->hclk = hclk;
@@ -6517,6 +6637,7 @@ static int __maybe_unused macb_suspend(struct device *dev)
{
struct net_device *netdev = dev_get_drvdata(dev);
struct macb *bp = netdev_priv(netdev);
+ struct macb_context *ctx = bp->ctx;
struct in_ifaddr *ifa = NULL;
struct macb_queue *queue;
struct in_device *idev;
@@ -6558,7 +6679,7 @@ static int __maybe_unused macb_suspend(struct device *dev)
macb_writel(bp, RBQPH,
upper_32_bits(bp->rx_ring_tieoff_dma));
#endif
- for (q = 0, queue = bp->queues; q < bp->num_queues;
+ for (q = 0, queue = bp->queues; q < ctx->num_queues;
++q, ++queue) {
/* Disable RX queues */
if (bp->caps & MACB_CAPS_QUEUE_DISABLE) {
@@ -6599,7 +6720,7 @@ static int __maybe_unused macb_suspend(struct device *dev)
}
netif_device_detach(netdev);
- for (q = 0, queue = bp->queues; q < bp->num_queues;
+ for (q = 0, queue = bp->queues; q < ctx->num_queues;
++q, ++queue) {
napi_disable(&queue->napi_rx);
napi_disable(&queue->napi_tx);
@@ -6674,7 +6795,7 @@ static int __maybe_unused macb_resume(struct device *dev)
if (!(bp->caps & MACB_CAPS_MACB_IS_EMAC))
macb_init_buffers(bp);
- for (q = 0, queue = bp->queues; q < bp->num_queues;
+ for (q = 0, queue = bp->queues; q < ctx->num_queues;
++q, ++queue) {
if (!(bp->caps & MACB_CAPS_MACB_IS_EMAC)) {
if (macb_is_gem(&bp->info))
--
2.56.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH net-next v6 2/2] net: macb: distribute evenly Tx SRAM segments
2026-11-13 17:32 [PATCH net-next v6 0/2] net: macb: implement ethtool set channels count operation Théo Lebrun
2026-11-13 17:32 ` [PATCH net-next v6 1/2] net: macb: implement ethtool_ops.get|set_channels() Théo Lebrun
@ 2026-11-13 17:32 ` Théo Lebrun
2026-11-13 18:18 ` Théo Lebrun
1 sibling, 1 reply; 4+ messages in thread
From: Théo Lebrun @ 2026-11-13 17:32 UTC (permalink / raw)
To: Conor Dooley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Russell King
Cc: netdev, linux-kernel, Nicolai Buchwitz, Paolo Valerio,
Vladimir Kondratiev, Gregory CLEMENT, Benoît Monin,
Tawfik Bayouk, Thomas Petazzoni, Théo Lebrun
GEM has registers to configure the Tx SRAM segments distribution across
queues. The reset value is apprioriate (even spread) but we need to
care if/when number of active queues is modified (or if we inherited
unevenly initialised hardware from bootloader).
To distribute segments, we take as input the number of queues
(ctx->num_queues) and the number of segments (found inside DCFG6).
Its output is a number of segments for each queue, formatted as
powers-of-two (eg 2 for queue 0 means it has 2^2=4 segments).
Signed-off-by: Théo Lebrun <theo.lebrun@bootlin.com>
---
drivers/net/ethernet/cadence/macb.h | 14 ++++++
drivers/net/ethernet/cadence/macb_main.c | 76 +++++++++++++++++++++++++++++++-
2 files changed, 88 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h
index 0879fb2b198b..cc57071c869a 100644
--- a/drivers/net/ethernet/cadence/macb.h
+++ b/drivers/net/ethernet/cadence/macb.h
@@ -194,6 +194,10 @@
/* Screener Type 2 match registers */
#define GEM_SCRT2 0x540
+/* Tx queue segment allocation */
+#define GEM_TXQSEGALLOC_LOWER 0x05A0
+#define GEM_TXQSEGALLOC_UPPER 0x05A4
+
/* EtherType registers */
#define GEM_ETHT 0x06E0
@@ -545,6 +549,8 @@
#define GEM_PBUF_CUTTHRU_SIZE 1
#define GEM_DAW64_OFFSET 23
#define GEM_DAW64_SIZE 1
+#define GEM_SEGMENTS_BIT_SIZE_OFFSET 16
+#define GEM_SEGMENTS_BIT_SIZE_SIZE 3
/* Bitfields in DCFG8. */
#define GEM_T1SCR_OFFSET 24
@@ -657,6 +663,13 @@
#define GEM_CMPCEN_OFFSET 30 /* Compare C Enable */
#define GEM_CMPCEN_SIZE 1
+/* Bitfields in TXQSEGALLOC (Q0 at 2:0, Q1 at 6:4, etc.) */
+#define GEM_TXQSEGALLOC_MASK(q_) \
+ ({ \
+ typeof(q_) q__ = (q_); \
+ GENMASK(4 * (q__) + 2, 4 * (q__)); \
+ })
+
/* Bitfields in ETHT */
#define GEM_ETHTCMP_OFFSET 0 /* EtherType compare value */
#define GEM_ETHTCMP_SIZE 16
@@ -785,6 +798,7 @@
#define MACB_CAPS_USRIO_HAS_MII BIT(26)
#define MACB_CAPS_USRIO_HAS_REFCLK_SOURCE BIT(27)
#define MACB_CAPS_USRIO_HAS_TSUCLK_SOURCE BIT(28)
+#define MACB_CAPS_TXQSEGALLOC BIT(29)
/* LSO settings */
#define MACB_LSO_UFO_ENABLE 0x01
diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index e7325af4b399..dc1624815f71 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -3432,6 +3432,63 @@ static void macb_context_swap_end(struct macb *bp,
}
}
+/*
+ * Distribute evenly available segments across queues. The computation is
+ * complex because (1) segments are counted in powers of two and (2) a queue
+ * can only use up to 8 segments. There are four types of cases:
+ * - Sharing all segments equally is doable. Take num_queues=4 and
+ * num_segments=16. Each queue will get 2^2=4 segments.
+ * - Sharing all segments is doable. Take num_queues=5 and num_segments=16.
+ * Three queues will get 2^2=4 segments and two will get 2^1=2 segments.
+ * - Sharing all segments is not doable because not enough queues are
+ * available. Take num_queues=1 and num_segments=16; queue 0 can only have 8
+ * segments.
+ * - Sharing all segments is not doable because not enough segments are
+ * available. Take num_queues=4 and num_segments=2.
+ *
+ * We start by computing the power each queue will have. For num_queues=5 and
+ * num_segments=16, each queue will have at least 2^1 segments. That leaves us
+ * with remaining_segments=6. If we increase the power for a queue, we get a
+ * delta of 2 (2^2-2^1). The first three queues will therefore be advantaged
+ * and each have 2^2 segments. The remaining 2 queues will only have 2^1
+ * segments.
+ */
+static u32 gem_sram_distribute_segments(unsigned int num_queues,
+ unsigned int num_segments)
+{
+ unsigned int pow, remaining_segments, i;
+ unsigned int num_advantaged_queues = 0;
+ u32 val = 0;
+
+ /* When growing the max # of queues, make sure to return a u64 here and
+ * to write into GEM_TXQSEGALLOC_UPPER.
+ */
+ static_assert(MACB_MAX_QUEUES == 8);
+
+ /* pow=0 for all queues. ilog2(0) is dangerous. */
+ if (num_queues >= num_segments)
+ return 0;
+
+ pow = min_t(unsigned int, ilog2(num_segments / num_queues), 3);
+ remaining_segments = num_segments - num_queues * (1U << pow);
+
+ /* We can only distribute remaining segments if (1) there are remaining
+ * segments and (2) we did not reach the max segments per queue (2^3).
+ */
+ if (remaining_segments != 0 && pow != 3) {
+ unsigned int delta = (1U << (pow + 1)) - (1U << pow);
+
+ num_advantaged_queues = remaining_segments / delta;
+ }
+
+ for (i = 0; i < num_advantaged_queues; i++)
+ val |= field_prep(GEM_TXQSEGALLOC_MASK(i), pow + 1);
+ for (i = num_advantaged_queues; i < num_queues; i++)
+ val |= field_prep(GEM_TXQSEGALLOC_MASK(i), pow);
+
+ return val;
+}
+
static void macb_init_hw(struct macb *bp)
{
u32 config;
@@ -3465,6 +3522,20 @@ static void macb_init_hw(struct macb *bp)
if (bp->caps & MACB_CAPS_JUMBO)
bp->rx_frm_len_mask = MACB_RX_JFRMLEN_MASK;
+ /* Distribute Tx SRAM segments evenly based on active number of queues.
+ * TXQSEGALLOC is only documented for some GEM integrations.
+ */
+ if (bp->caps & MACB_CAPS_TXQSEGALLOC) {
+ unsigned int num_segments;
+ u32 val;
+
+ num_segments = 1U << GEM_BFEXT(SEGMENTS_BIT_SIZE,
+ gem_readl(bp, DCFG6));
+ val = gem_sram_distribute_segments(bp->ctx->num_queues,
+ num_segments);
+ gem_writel(bp, TXQSEGALLOC_LOWER, val);
+ }
+
macb_configure_dma(bp);
/* Enable RX partial store and forward and set watermark */
@@ -6234,6 +6305,7 @@ static const struct macb_config zynqmp_config = {
.caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE |
MACB_CAPS_JUMBO |
MACB_CAPS_GEM_HAS_PTP | MACB_CAPS_BD_RD_PREFETCH |
+ MACB_CAPS_TXQSEGALLOC |
MACB_CAPS_USRIO_HAS_MII,
.dma_burst_length = 16,
.init = init_reset_optional,
@@ -6285,7 +6357,7 @@ static const struct macb_config versal_config = {
.caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_JUMBO |
MACB_CAPS_GEM_HAS_PTP | MACB_CAPS_BD_RD_PREFETCH |
MACB_CAPS_NEED_TSUCLK | MACB_CAPS_QUEUE_DISABLE |
- MACB_CAPS_QBV |
+ MACB_CAPS_QBV | MACB_CAPS_TXQSEGALLOC |
MACB_CAPS_USRIO_HAS_MII,
.dma_burst_length = 16,
.init = init_reset_optional,
@@ -6296,7 +6368,7 @@ static const struct macb_config versal_config = {
static const struct macb_config eyeq5_config = {
.caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_JUMBO |
MACB_CAPS_GEM_HAS_PTP | MACB_CAPS_QUEUE_DISABLE |
- MACB_CAPS_NO_LSO | MACB_CAPS_EEE,
+ MACB_CAPS_NO_LSO | MACB_CAPS_EEE | MACB_CAPS_TXQSEGALLOC,
.dma_burst_length = 16,
.init = eyeq5_init,
.jumbo_max_len = 10240,
--
2.56.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH net-next v6 2/2] net: macb: distribute evenly Tx SRAM segments
2026-11-13 17:32 ` [PATCH net-next v6 2/2] net: macb: distribute evenly Tx SRAM segments Théo Lebrun
@ 2026-11-13 18:18 ` Théo Lebrun
0 siblings, 0 replies; 4+ messages in thread
From: Théo Lebrun @ 2026-11-13 18:18 UTC (permalink / raw)
To: Conor Dooley, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Russell King
Cc: netdev, linux-kernel, Nicolai Buchwitz, Paolo Valerio,
Vladimir Kondratiev, Gregory CLEMENT, Benoît Monin,
Tawfik Bayouk, Thomas Petazzoni, Théo Lebrun
On Fri Nov 13, 2026 at 6:32 PM CET, Théo Lebrun wrote:
> +/*
> + * Distribute evenly available segments across queues. The computation is
> + * complex because (1) segments are counted in powers of two and (2) a queue
> + * can only use up to 8 segments. There are four types of cases:
> + * - Sharing all segments equally is doable. Take num_queues=4 and
> + * num_segments=16. Each queue will get 2^2=4 segments.
> + * - Sharing all segments is doable. Take num_queues=5 and num_segments=16.
> + * Three queues will get 2^2=4 segments and two will get 2^1=2 segments.
> + * - Sharing all segments is not doable because not enough queues are
> + * available. Take num_queues=1 and num_segments=16; queue 0 can only have 8
> + * segments.
> + * - Sharing all segments is not doable because not enough segments are
> + * available. Take num_queues=4 and num_segments=2.
> + *
> + * We start by computing the power each queue will have. For num_queues=5 and
> + * num_segments=16, each queue will have at least 2^1 segments. That leaves us
> + * with remaining_segments=6. If we increase the power for a queue, we get a
> + * delta of 2 (2^2-2^1). The first three queues will therefore be advantaged
> + * and each have 2^2 segments. The remaining 2 queues will only have 2^1
> + * segments.
> + */
> +static u32 gem_sram_distribute_segments(unsigned int num_queues,
> + unsigned int num_segments)
> +{
> + unsigned int pow, remaining_segments, i;
> + unsigned int num_advantaged_queues = 0;
> + u32 val = 0;
> +
> + /* When growing the max # of queues, make sure to return a u64 here and
> + * to write into GEM_TXQSEGALLOC_UPPER.
> + */
> + static_assert(MACB_MAX_QUEUES == 8);
> +
> + /* pow=0 for all queues. ilog2(0) is dangerous. */
> + if (num_queues >= num_segments)
> + return 0;
> +
> + pow = min_t(unsigned int, ilog2(num_segments / num_queues), 3);
> + remaining_segments = num_segments - num_queues * (1U << pow);
> +
> + /* We can only distribute remaining segments if (1) there are remaining
> + * segments and (2) we did not reach the max segments per queue (2^3).
> + */
> + if (remaining_segments != 0 && pow != 3) {
> + unsigned int delta = (1U << (pow + 1)) - (1U << pow);
> +
> + num_advantaged_queues = remaining_segments / delta;
> + }
> +
> + for (i = 0; i < num_advantaged_queues; i++)
> + val |= field_prep(GEM_TXQSEGALLOC_MASK(i), pow + 1);
> + for (i = num_advantaged_queues; i < num_queues; i++)
> + val |= field_prep(GEM_TXQSEGALLOC_MASK(i), pow);
> +
> + return val;
> +}
This implementation doesn't use all segments if
num_segments > 8*num_queues. Reported by Nicolai and discussed here [0].
I don't think any hardware lands in that case, but let's fix the bug.
I will let some time pass before iterating.
[0]: https://lore.kernel.org/all/1113dac19771ff7881b60bfdd14b0af6@tipi-net.de/
Thanks,
--
Théo Lebrun, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-13 18:18 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-13 17:32 [PATCH net-next v6 0/2] net: macb: implement ethtool set channels count operation Théo Lebrun
2026-11-13 17:32 ` [PATCH net-next v6 1/2] net: macb: implement ethtool_ops.get|set_channels() Théo Lebrun
2026-11-13 17:32 ` [PATCH net-next v6 2/2] net: macb: distribute evenly Tx SRAM segments Théo Lebrun
2026-11-13 18:18 ` Théo Lebrun
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.