* [PATCH net-next v2 10/13] net: mana: release EQs left idle by a channel-count reduction
2026-08-11 6:35 [PATCH net-next v2 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
@ 2026-08-11 6:35 ` Long Li
0 siblings, 0 replies; 15+ messages in thread
From: Long Li @ 2026-08-11 6:35 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
The shared EQ pool introduced by the previous patch is grow-only, so it
sits at the high-water mark of every channel count the port has ever
used. After "ethtool -L ens1 combined 32" followed by "combined 4" the
port keeps 32 EQs and 32 MSI-X vectors while using four:
# ethtool -L ens1 combined 4
# grep -c mana /proc/interrupts
33
Before the queue-set swap this did not happen: mana_detach() destroyed
every EQ and mana_attach() created exactly num_queues of them, so the
interrupt count tracked the configured channel count.
Add mana_shrink_eqs() and call it from mana_free_qset(), which is the
one place where a queue set has just been torn down and the live count
is already known. Both callers benefit: on success the retiring set is
the old one and the pool shrinks to the new count, and if
mana_publish_qset() failed and rolled back, the new set is discarded and
the pool shrinks back to the restored count.
Shrinking is only safe there. A CQ holds the gdma_queue pointer of its
parent EQ, so an EQ may only be destroyed once the set referencing it is
gone.
mana_free_qset() therefore takes the live port context as well, since
the pool belongs to it rather than to the scratch context.
While here, fix mana_create_eq_debugfs(), which stored the new dentry in
a stack copy of the mana_eq rather than in apc->eqs[i]. Nothing noticed
because mana_destroy_eq() removes the whole "EQs" directory recursively,
but per-EQ removal is needed to shrink without leaving stale nodes
behind, which would then collide when the pool grows again.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 4 +-
drivers/net/ethernet/microsoft/mana/mana_en.c | 79 ++++++++++++++++---
.../ethernet/microsoft/mana/mana_ethtool.c | 12 +--
include/net/mana/mana.h | 3 +-
4 files changed, 78 insertions(+), 20 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index 1bae4174e268f7f53b0880c5d1098cd0ef687f25..7031ecb4da2e0fd5b83aeb07aeb8d7ae56e16d65 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
@@ -235,7 +235,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
if (err) {
NL_SET_ERR_MSG_MOD(extack,
"XDP: Re-config failed at publish");
- mana_free_qset(scratch, &newq);
+ mana_free_qset(apc, scratch, &newq);
/* After the cleanup above: closing destroys the EQ pool
* those queues' CQs were attached to.
*/
@@ -244,7 +244,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
return err;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(apc, scratch, &oldq);
mana_qset_scratch_free(scratch);
} else {
/* No queues to rebuild; mana_open() will size the RX buffers
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 71cbdebc5f3f7126495b2b11f0c673955fe0f8df..0d87440fbfbee7ac5729945d101eaa0c37745fbe 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -931,11 +931,11 @@ static int mana_change_mtu(struct net_device *ndev, int new_mtu)
err = mana_publish_qset(mpc, &newq, &oldq);
if (err) {
- mana_free_qset(scratch, &newq);
+ mana_free_qset(mpc, scratch, &newq);
goto free_scratch;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(mpc, scratch, &oldq);
free_scratch:
/* After the caller-side cleanup above, so the EQ pool outlives the
@@ -1847,6 +1847,9 @@ void mana_destroy_eq(struct mana_port_context *apc)
msi = eq->eq.msix_index;
mana_gd_destroy_queue(gc, eq);
mana_gd_put_gic(gc, !gc->msi_sharing, msi);
+ apc->eqs[i].eq = NULL;
+ /* Freed with the parent by debugfs_remove_recursive() above. */
+ apc->eqs[i].mana_eq_debugfs = NULL;
}
kfree(apc->eqs);
@@ -1857,15 +1860,16 @@ EXPORT_SYMBOL_NS(mana_destroy_eq, "NET_MANA");
static void mana_create_eq_debugfs(struct mana_port_context *apc, int i)
{
- struct mana_eq eq = apc->eqs[i];
+ struct mana_eq *eq = &apc->eqs[i];
char eqnum[32];
sprintf(eqnum, "eq%d", i);
- eq.mana_eq_debugfs = debugfs_create_dir(eqnum, apc->mana_eqs_debugfs);
- debugfs_create_u32("head", 0400, eq.mana_eq_debugfs, &eq.eq->head);
- debugfs_create_u32("tail", 0400, eq.mana_eq_debugfs, &eq.eq->tail);
- debugfs_create_u32("irq", 0400, eq.mana_eq_debugfs, &eq.eq->eq.irq);
- debugfs_create_file("eq_dump", 0400, eq.mana_eq_debugfs, eq.eq, &mana_dbg_q_fops);
+ eq->mana_eq_debugfs = debugfs_create_dir(eqnum, apc->mana_eqs_debugfs);
+ debugfs_create_u32("head", 0400, eq->mana_eq_debugfs, &eq->eq->head);
+ debugfs_create_u32("tail", 0400, eq->mana_eq_debugfs, &eq->eq->tail);
+ debugfs_create_u32("irq", 0400, eq->mana_eq_debugfs, &eq->eq->eq.irq);
+ debugfs_create_file("eq_dump", 0400, eq->mana_eq_debugfs, eq->eq,
+ &mana_dbg_q_fops);
}
int mana_create_eq(struct mana_port_context *apc)
@@ -1999,6 +2003,43 @@ static int mana_grow_eqs(struct mana_port_context *apc, unsigned int need)
return err;
}
+/**
+ * mana_shrink_eqs - release EQs above @keep
+ * @apc: port context
+ * @keep: number of EQs the live queue set still uses
+ *
+ * Returns the MSI-X vectors of the EQs a shrink left behind. Must only be
+ * called once no queue set references them, i.e. after the retiring set has
+ * been torn down by mana_free_qset(), otherwise a live CQ would be left
+ * pointing at a destroyed EQ.
+ */
+static void mana_shrink_eqs(struct mana_port_context *apc, unsigned int keep)
+{
+ struct gdma_context *gc = apc->ac->gdma_dev->gdma_context;
+ struct gdma_queue *eq;
+ unsigned int msi;
+ unsigned int i;
+
+ if (!apc->eqs || keep >= apc->num_eqs)
+ return;
+
+ for (i = keep; i < apc->num_eqs; i++) {
+ eq = apc->eqs[i].eq;
+ if (!eq)
+ continue;
+
+ debugfs_remove_recursive(apc->eqs[i].mana_eq_debugfs);
+ apc->eqs[i].mana_eq_debugfs = NULL;
+
+ msi = eq->eq.msix_index;
+ mana_gd_destroy_queue(gc, eq);
+ mana_gd_put_gic(gc, !gc->msi_sharing, msi);
+ apc->eqs[i].eq = NULL;
+ }
+
+ apc->num_eqs = keep;
+}
+
static int mana_fence_rq(struct mana_port_context *apc, struct mana_rxq *rxq)
{
struct mana_fence_rq_resp resp = {};
@@ -4224,7 +4265,7 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
* Every queue is built in @scratch, so the queue set currently installed on
* @apc keeps serving traffic throughout. @apc is touched only to grow the
* shared EQ pool, which both sets reference while they are both live. On
- * error no queue is left allocated.
+ * error no queue is left allocated, and any EQ this call added is released.
*/
int mana_alloc_qset(struct mana_port_context *apc,
struct mana_port_context *scratch, unsigned int num_queues,
@@ -4294,6 +4335,13 @@ int mana_alloc_qset(struct mana_port_context *apc,
kfree(scratch->rxqs);
scratch->rxqs = NULL;
out_err:
+ /* Give back any EQ this attempt added to the shared pool rather than
+ * holding its MSI-X vectors until some later teardown: the live set
+ * still needs only apc->num_queues of them. Safe here because this
+ * set's CQs have already been destroyed above.
+ */
+ mana_shrink_eqs(apc, apc->num_queues);
+
netdev_err(ndev, "mana_alloc_qset(num_queues=%u) failed: %d\n",
num_queues, err);
return err;
@@ -4655,13 +4703,15 @@ static void mana_qset_debugfs_publish(struct mana_port_context *apc)
/**
* mana_free_qset - tear down all queues in @qset
+ * @apc: live port context (owns the shared EQ pool)
* @scratch: scratch context from mana_qset_scratch_alloc()
* @qset: queue set to destroy (must no longer be installed on the live apc)
*
* Runs the existing destroyers against @scratch so the live port context
* is never made to point at queues that are being freed.
*/
-void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
+void mana_free_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, struct mana_qset *qset)
{
struct bpf_prog *retiring_prog;
unsigned int retiring_queues;
@@ -4776,12 +4826,19 @@ void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
memset(qset, 0, sizeof(*qset));
+ /* This set is gone, so any EQ above the live queue count is now
+ * unreferenced. Release those vectors instead of holding them at the
+ * high-water mark. Safe here and only here: the retiring set's CQs
+ * have just been destroyed.
+ */
+ mana_shrink_eqs(apc, apc->num_queues);
+
/* Queues built through a scratch context carry no debugfs nodes,
* because both sets are alive during the swap and would collide on
* the same names. The retiring set's nodes are gone now, so the
* published queues can finally take those names.
*/
- mana_qset_debugfs_publish(netdev_priv(scratch->ndev));
+ mana_qset_debugfs_publish(apc);
}
/* --- end of pre-allocate + swap reconfiguration path ---------------------- */
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 03fe657334c49a69ebe9c2677b2b8268321a162d..a4d62ea8aec8f4aaa521af2c4fa4af6d928e9b4e 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -744,11 +744,11 @@ static int mana_set_channels(struct net_device *ndev,
err = mana_publish_qset(apc, &newq, &oldq);
if (err) {
- mana_free_qset(scratch, &newq);
+ mana_free_qset(apc, scratch, &newq);
goto free_scratch;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(apc, scratch, &oldq);
free_scratch:
/* After the caller-side cleanup above, so the EQ pool outlives the
@@ -852,11 +852,11 @@ static int mana_set_ringparam(struct net_device *ndev,
if (err) {
NL_SET_ERR_MSG_FMT(extack, "failed to change ring params: %d",
err);
- mana_free_qset(scratch, &newq);
+ mana_free_qset(apc, scratch, &newq);
goto free_scratch;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(apc, scratch, &oldq);
free_scratch:
/* After the caller-side cleanup above, so the EQ pool outlives the
@@ -949,11 +949,11 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
err = mana_publish_qset(apc, &newq, &oldq);
if (err) {
- mana_free_qset(scratch, &newq);
+ mana_free_qset(apc, scratch, &newq);
goto free_scratch;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(apc, scratch, &oldq);
free_scratch:
/* After the caller-side cleanup above, so the EQ pool outlives the
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index d09bf2c7cec0be06c4190caab1f0723a5c4956d6..52f1c8f9c2b968ad14c90849fd3286b2c046d1d3 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -776,7 +776,8 @@ int mana_alloc_qset(struct mana_port_context *apc,
int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
struct mana_qset *out_old);
void mana_publish_close_if_needed(struct mana_port_context *apc);
-void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset);
+void mana_free_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, struct mana_qset *qset);
void mana_dim_change(struct mana_cq *cq, bool enable);
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set
@ 2026-08-13 5:04 Long Li
2026-08-13 5:04 ` [PATCH net-next v2 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
` (12 more replies)
0 siblings, 13 replies; 15+ messages in thread
From: Long Li @ 2026-08-13 5:04 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
MANA reconfigures a port by destroying its queues and building them again.
mana_detach() tears the whole queue set down, mana_attach() allocates a new
one, and every path that changes a queue property does this: the ethtool
channel, ring and private-flag setters, the MTU change and XDP attach.
If the second half fails there is nothing left to fall back to. The old
queues are already gone, so the port stays down and the failure cannot be
undone from userspace:
# ethtool -G ens1 rx 8192 tx 16384 # under memory pressure
netlink error: Cannot allocate memory
mana 7870:00:00.0 ens1: Failed to create 16 TX queues, -12
mana 7870:00:00.0 ens1: mana_attach failed: -12
# ip link show ens1
... state DOWN
On VM SKUs with no netvsc fallback interface this takes the VM off the
network entirely, and it is reachable from an ordinary ethtool ring resize
that happens to run when memory is tight.
v1 [1] tried to recover after the fact: schedule a reset and retry
mana_attach() with smaller values, falling back to the previous setting or
to the defaults. Review pointed out that silently replacing a user's
setting with a different one is the wrong behaviour, and that
pre-allocating the resources and replacing the queue set looked feasible
and should be investigated instead. This series does that, so there is no
failure to recover from and no user setting to override.
The model is to build the new queue set alongside the running one, publish
it, then retire the old one.
carrier off -> netif_tx_disable -> swap the queue pointers -> update the
netdev queue counts -> reprogram RSS/steering -> reattach XDP ->
netif_tx_start_all_queues -> restore carrier
Everything that can fail happens before anything is swapped. If allocation
fails the running queues have not been touched at all: the ethtool call
returns the error, the interface keeps forwarding traffic and the
configuration is unchanged. If the swap itself fails, the previous set is
put back and the port keeps running on it.
Patch layout:
1 the queue-set allocate/publish/free helpers.
2-6 convert the five callers: ethtool channels, rings and private
flags, the MTU change and XDP attach.
7 the remaining detach/attach users are the TX-timeout reset handler
and suspend; make mana_detach() always finish its teardown so the
reset handler cannot leave a port permanently dead.
8-11 keep behaviour the swap model would otherwise change: per-queue
counters move into the port context so a rebuild no longer zeroes
the interface statistics, the EQ pool is shared across a swap
instead of being duplicated, EQs left idle by a reduction are
released, and a user-configured RSS indirection table survives a
rebuild.
12-13 stop rebuilding queues that do not change. A reduction keeps the
surviving queues and an increase keeps the existing ones, so
32 -> 8 channels destroys 24 queue pairs instead of building 8 and
destroying 32, and 4 -> 8 builds 4 instead of 8.
Known trade-off, since it is deliberate: RX counters are now indexed by
queue number in the port context, so during the window between steering
moving to the new queues and the old ones being destroyed, both generations
can update one slot. MANA is 64-bit only, so u64_stats_sync carries no
seqcount and nothing can be corrupted; at worst a few increments are lost.
Serialising them would put a lock in the per-packet receive path, and
giving each set its own slots would make ndo_get_stats64() report a dip
during a swap, which is the regression this is meant to remove.
Testing, on Standard_D32ds_v6 (MANA) running Ubuntu 24.04:
- the failure above, made deterministic with fail_page_alloc: before the
series the port ends up down and unrecoverable; after it, ethtool
returns -ENOMEM, the ring sizes are unchanged, the link stays up and
traffic is uninterrupted.
- 33-case functional matrix over channel counts 1/2/4/8/32, ring sizes,
MTU, the private flag and XDP attach/detach across each, including
reconfiguration while the port is administratively down.
- XDP: all four verdicts exercised. PASS and TX across every
reconfiguration, DROP and REDIRECT verified by counter and by effect.
The program stays attached and RX keeps flowing across channel
shrink/grow, ring resize, MTU change and private-flag toggle, with
16.8 Gbit/s of received traffic running through the program while the
queue set is replaced underneath it.
- a queue-set allocation forced to fail while an XDP program is attached,
to exercise the unpublished-set teardown.
- no KASAN reports, BUGs or WARNs in any of the above.
- every patch builds with W=1 with no new warnings, checkpatch clean.
[1] https://lore.kernel.org/netdev/20260711041415.3008868-1-dipayanroy@linux.microsoft.com/
Dipayaan Roy (1):
net: mana: do not bail out of mana_detach on dealloc failure
Long Li (12):
net: mana: add queue-set allocation and teardown helpers
net: mana: swap queue sets in mana_set_channels
net: mana: swap queue sets in mana_set_ringparam
net: mana: swap queue sets in mana_set_priv_flags
net: mana: swap queue sets in mana_change_mtu
net: mana: swap queue sets in mana_xdp_set
net: mana: keep per-queue statistics in the port context
net: mana: share the EQ pool across a queue-set swap
net: mana: release EQs left idle by a channel-count reduction
net: mana: keep a user-configured RSS table across a queue rebuild
net: mana: keep the surviving queues when the channel count is reduced
net: mana: keep the existing queues when the channel count is raised
.../net/ethernet/microsoft/mana/mana_bpf.c | 110 +-
drivers/net/ethernet/microsoft/mana/mana_en.c | 1433 +++++++++++++++--
.../ethernet/microsoft/mana/mana_ethtool.c | 300 +++-
include/net/mana/gdma.h | 11 +-
include/net/mana/mana.h | 117 +-
5 files changed, 1746 insertions(+), 225 deletions(-)
base-commit: d67e5dbda22604d0fcde32fce58c65f88676e676
--
2.43.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH net-next v2 01/13] net: mana: add queue-set allocation and teardown helpers
2026-08-13 5:04 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
@ 2026-08-13 5:04 ` Long Li
2026-08-13 5:04 ` [PATCH net-next v2 02/13] net: mana: swap queue sets in mana_set_channels Long Li
` (11 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Long Li @ 2026-08-13 5:04 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
The ethtool reconfiguration paths (channel count, ring size, private
flags), mana_change_mtu() and mana_xdp_set() rebuild the queues with
mana_detach() then mana_attach(). That tears the vport down, so RDMA can
claim it while released, and a failed mana_attach() leaves the port down
with no way back but manual intervention.
Add the data model and helpers for pre-allocate and swap: a queue set
built, published and torn down independently of the vport, against a
scratch port context so the live one never points at queues still being
built or freed. Building in place is not an option: mana_start_xmit()
dereferences apc->tx_qp[] guarded only by apc->port_is_up.
The TX drain moves out of mana_dealloc_queues() so the new teardown path
gets it too, and its fallback reset becomes pci_try_reset_function()
rather than an open-coded pcie_flr(), which does not save and restore
config space. Trylock because this runs under RTNL while removal takes
the device lock first.
No functional change otherwise: nothing calls the new helpers yet.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 31 ++
drivers/net/ethernet/microsoft/mana/mana_en.c | 477 ++++++++++++++++--
.../ethernet/microsoft/mana/mana_ethtool.c | 9 +-
include/net/mana/mana.h | 63 +++
4 files changed, 538 insertions(+), 42 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index 53308e139cbe917b074dd381c83546fc74d7b79f..ca602e27044f92b87295cbc2de924adc71efa780 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
@@ -265,3 +265,34 @@ int mana_bpf(struct net_device *ndev, struct netdev_bpf *bpf)
return ret;
}
+
+/* Read the XDP program a queue set is running, without changing anything. */
+struct bpf_prog *mana_chn_xdp_peek(struct mana_port_context *apc)
+{
+ ASSERT_RTNL();
+
+ if (!apc->rxqs || !apc->rxqs[0])
+ return NULL;
+
+ return rtnl_dereference(apc->rxqs[0]->bpf_prog);
+}
+
+/* Drop the per-queue references a retiring set holds on @prog.
+ *
+ * Kept separate from mana_chn_setxdp() so the pointers can stay in place
+ * until the queues stop polling: clearing them up front would let packets
+ * already sitting in a retiring RQ take the pass path and reach the stack
+ * without the program ever seeing them.
+ */
+void mana_chn_xdp_release(struct bpf_prog *prog, unsigned int num_queues)
+{
+ unsigned int i;
+
+ ASSERT_RTNL();
+
+ if (!prog)
+ return;
+
+ for (i = 0; i < num_queues; i++)
+ bpf_prog_put(prog);
+}
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 3c96e6fc3d81dc16853cc458ef620b5150aa8988..60b1fc93d453b16bec37924e32ea54c64f179f1c 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -2015,7 +2015,8 @@ static void mana_poll_tx_cq(struct mana_cq *cq)
/* Ensure checking txq_stopped before apc->port_is_up. */
smp_rmb();
- if (txq_stopped && apc->port_is_up && avail_space >= MAX_TX_WQE_SIZE) {
+ if (txq_stopped && !READ_ONCE(txq->retiring) && apc->port_is_up &&
+ avail_space >= MAX_TX_WQE_SIZE) {
netif_tx_wake_queue(net_txq);
apc->eth_stats.wake_queue++;
}
@@ -2751,6 +2752,7 @@ static int mana_create_txq(struct mana_port_context *apc,
u64_stats_init(&txq->stats.syncp);
txq->ndev = net;
txq->net_txq = netdev_get_tx_queue(net, i);
+ txq->reset_gen = READ_ONCE(apc->ac->reset_gen);
txq->vp_offset = apc->tx_vp_offset;
txq->napi_initialized = false;
skb_queue_head_init(&txq->pending_skbs);
@@ -3006,11 +3008,14 @@ static int mana_push_wqe(struct mana_rxq *rxq)
static int mana_create_page_pool(struct mana_rxq *rxq, struct gdma_context *gc)
{
- struct mana_port_context *mpc = netdev_priv(rxq->ndev);
struct page_pool_params pprm = {};
int ret;
- pprm.pool_size = mpc->rx_queue_size / rxq->frag_count + 1;
+ /* Size the recycle ring from the queue being built, not from the live
+ * port context: during a swap the queue may be sized for a ring the
+ * running configuration does not use yet.
+ */
+ pprm.pool_size = rxq->num_rx_buf / rxq->frag_count + 1;
pprm.nid = gc->numa_node;
pprm.napi = &rxq->rx_cq.napi;
pprm.netdev = rxq->ndev;
@@ -3676,15 +3681,114 @@ int mana_attach(struct net_device *ndev)
return 0;
}
-static int mana_dealloc_queues(struct net_device *ndev)
+/* Drain a set about to be destroyed: nothing new can reach it, so wait for the
+ * hardware to finish what it owns, then release every mapped SKB.
+ *
+ * The 120s budget is shared across all queues. On timeout the device is reset,
+ * since its buffers are about to be freed while it may still DMA into them; if
+ * that fails too they are leaked.
+ *
+ * Returns true only if a reset happened, taking every queue on the function
+ * down with it.
+ */
+static bool mana_drain_txqs(struct mana_port_context *apc)
{
- struct mana_port_context *apc = netdev_priv(ndev);
unsigned long timeout = jiffies + 120 * HZ;
struct gdma_dev *gd = apc->ac->gdma_dev;
+ bool quiesced = true;
+ bool reset = false;
struct mana_txq *txq;
struct sk_buff *skb;
- int i, err;
u32 tsleep;
+ int i, err;
+
+ if (!apc->tx_qp)
+ return false;
+
+ for (i = 0; i < apc->num_queues; i++) {
+ if (!apc->tx_qp[i])
+ continue;
+
+ txq = &apc->tx_qp[i]->txq;
+
+ /* The function was reset after this queue was created, so the
+ * device has stopped touching its buffers and the completions
+ * waited for below can never arrive. Without this the port
+ * would burn the full timeout under RTNL, then reset the
+ * function again on the way out.
+ */
+ if (READ_ONCE(apc->ac->reset_gen) != txq->reset_gen)
+ continue;
+
+ tsleep = 1000;
+ while (atomic_read(&txq->pending_sends) > 0 &&
+ time_before(jiffies, timeout)) {
+ usleep_range(tsleep, tsleep + 1000);
+ tsleep <<= 1;
+ }
+ if (atomic_read(&txq->pending_sends)) {
+ /* The device still owns these buffers, so reset it
+ * before they are freed. pci_try_reset_function()
+ * rather than pcie_flr(): it saves and restores config
+ * space, which a bare FLR wipes behind the PCI core's
+ * back. Trylock because RTNL is held here while the
+ * remove path takes the device lock first.
+ */
+ err = pci_try_reset_function(to_pci_dev(gd->gdma_context->dev));
+ if (err) {
+ netdev_err(apc->ndev,
+ "function reset failed: %d, %d pkts pending in txq %u\n",
+ err, atomic_read(&txq->pending_sends),
+ txq->gdma_txq_id);
+ quiesced = false;
+ } else {
+ /* Every queue on the function is dead now,
+ * including the ones this loop has not reached
+ * and those of the other ports.
+ */
+ WRITE_ONCE(apc->ac->reset_gen,
+ apc->ac->reset_gen + 1);
+
+ /* Only a reset that actually happened takes the
+ * other ports down with it; reporting a failed
+ * one would rebuild them for nothing.
+ */
+ reset = true;
+ }
+ break;
+ }
+ }
+
+ /* Only a reset that actually happened makes freeing these safe; without
+ * one the device still owns them. Leak instead, bounded at one SQ ring
+ * of skbs per queue.
+ */
+ if (!quiesced) {
+ netdev_err(apc->ndev,
+ "device not quiesced, leaking pending TX buffers instead of unmapping memory it can still DMA from\n");
+ return reset;
+ }
+
+ for (i = 0; i < apc->num_queues; i++) {
+ if (!apc->tx_qp[i])
+ continue;
+
+ txq = &apc->tx_qp[i]->txq;
+ while ((skb = skb_dequeue(&txq->pending_skbs))) {
+ mana_unmap_skb(skb, apc);
+ dev_kfree_skb_any(skb);
+ }
+ atomic_set(&txq->pending_sends, 0);
+ }
+
+ return reset;
+}
+
+static int mana_dealloc_queues(struct net_device *ndev)
+{
+ struct mana_port_context *apc = netdev_priv(ndev);
+ struct gdma_dev *gd = apc->ac->gdma_dev;
+ int err;
if (apc->port_is_up)
return -EINVAL;
@@ -3702,41 +3806,27 @@ static int mana_dealloc_queues(struct net_device *ndev)
* new packets due to apc->port_is_up being false.
*
* Drain all the in-flight TX packets.
- * A timeout of 120 seconds for all the queues is used.
- * This will break the while loop when h/w is not responding.
- * This value of 120 has been decided here considering max
- * number of queues.
+ *
+ * If the drain had to reset the function to get there, every other
+ * port on the adapter lost its queues too, so schedule them for a
+ * rebuild. This port is being torn down here and needs no such
+ * treatment, and a down port stays down: with port_st_save false,
+ * detach and attach both skip the queue work.
*/
+ if (mana_drain_txqs(apc)) {
+ struct mana_context *ac = apc->ac;
+ unsigned int i;
- if (apc->tx_qp) {
- for (i = 0; i < apc->num_queues; i++) {
- txq = &apc->tx_qp[i]->txq;
- tsleep = 1000;
- while (atomic_read(&txq->pending_sends) > 0 &&
- time_before(jiffies, timeout)) {
- usleep_range(tsleep, tsleep + 1000);
- tsleep <<= 1;
- }
- if (atomic_read(&txq->pending_sends)) {
- err =
- pcie_flr(to_pci_dev(gd->gdma_context->dev));
- if (err) {
- netdev_err(ndev, "flr failed %d with %d pkts pending in txq %u\n",
- err,
- atomic_read(&txq->pending_sends),
- txq->gdma_txq_id);
- }
- break;
- }
- }
+ for (i = 0; i < ac->num_ports; i++) {
+ struct mana_port_context *sib;
- for (i = 0; i < apc->num_queues; i++) {
- txq = &apc->tx_qp[i]->txq;
- while ((skb = skb_dequeue(&txq->pending_skbs))) {
- mana_unmap_skb(skb, apc);
- dev_kfree_skb_any(skb);
- }
- atomic_set(&txq->pending_sends, 0);
+ if (!ac->ports[i] || ac->ports[i] == ndev)
+ continue;
+ sib = netdev_priv(ac->ports[i]);
+ netdev_err(ac->ports[i],
+ "queues reset by a sibling port, scheduling rebuild\n");
+ queue_work(ac->per_port_queue_reset_wq,
+ &sib->queue_reset_work);
}
}
@@ -3760,6 +3850,310 @@ static int mana_dealloc_queues(struct net_device *ndev)
return 0;
}
+/*
+ * ---------------------------------------------------------------------------
+ * Pre-allocate + swap reconfiguration path.
+ *
+ * The detach/attach reconfigure path tears the vport down and rebuilds it,
+ * which lets RDMA grab the vport mid-flight and, if attach fails, leaves the
+ * port permanently broken.
+ *
+ * The swap path builds a *new* set of EQs/TXQs/RXQs while the current set
+ * keeps serving traffic. If allocation fails the current qset is untouched
+ * and we return the error; the user's requested value is never silently
+ * replaced by a fallback. Publishing a new set onto the live port
+ * context is added separately. The vport is never torn down: vport_use_count
+ * stays at 1 throughout, so RDMA cannot hijack it.
+ *
+ * Allocation and teardown run against a *scratch* mana_port_context rather
+ * than the live one. This is essential, not cosmetic: an earlier revision
+ * temporarily NULLed apc->tx_qp so the allocators could
+ * build into the live context, which reliably panicked in mana_start_xmit()
+ * under traffic (it dereferences apc->tx_qp[] guarded only by port_is_up).
+ * The live apc is now mutated only inside mana_publish_qset(), with TX
+ * disabled.
+ *
+ * Note that both sets are live between publish and free, so this peaks at
+ * old+new queues, and therefore at old+new MSI-X vectors. A later patch
+ * gives the port a shared EQ pool so only the queues, not the interrupts,
+ * are doubled up.
+ *
+ * Per-queue debugfs is suppressed for a set while it is being built or torn
+ * down (see mana_qset_scratch_alloc()): the directory names are derived from
+ * the queue index, so the incoming set would collide with the outgoing one
+ * under vport%d. Restoring it needs per-set subdirectories or a
+ * debugfs_rename() once the swap has completed.
+ * ---------------------------------------------------------------------------
+ */
+
+/* Snapshot the queue-set fields of @ctx into @out. */
+static void mana_qset_snapshot(const struct mana_port_context *ctx,
+ struct mana_qset *out)
+{
+ out->eqs = ctx->eqs;
+ out->tx_qp = ctx->tx_qp;
+ out->rxqs = ctx->rxqs;
+ out->indir_table = ctx->indir_table;
+ out->indir_table_sz = ctx->indir_table_sz;
+ out->rxobj_table = ctx->rxobj_table;
+ out->default_rxobj = ctx->default_rxobj;
+ out->num_queues = ctx->num_queues;
+ out->rx_queue_size = ctx->rx_queue_size;
+ out->tx_queue_size = ctx->tx_queue_size;
+ out->priv_flags = ctx->priv_flags;
+ out->mana_eqs_debugfs = ctx->mana_eqs_debugfs;
+}
+
+/* Install @qset's fields onto @ctx. The vport (port_handle,
+ * vport_use_count) and the port-level debugfs dir are deliberately not
+ * touched: they outlive any individual queue set.
+ */
+static void mana_qset_install(struct mana_port_context *ctx,
+ const struct mana_qset *qset)
+{
+ ctx->eqs = qset->eqs;
+ ctx->tx_qp = qset->tx_qp;
+ ctx->rxqs = qset->rxqs;
+ ctx->indir_table = qset->indir_table;
+ ctx->indir_table_sz = qset->indir_table_sz;
+ ctx->rxobj_table = qset->rxobj_table;
+ ctx->default_rxobj = qset->default_rxobj;
+ ctx->num_queues = qset->num_queues;
+ ctx->rx_queue_size = qset->rx_queue_size;
+ ctx->tx_queue_size = qset->tx_queue_size;
+ ctx->priv_flags = qset->priv_flags;
+ ctx->mana_eqs_debugfs = qset->mana_eqs_debugfs;
+}
+
+/**
+ * mana_qset_scratch_alloc - build a scratch port context for queue work
+ * @apc: the live port context to shadow
+ *
+ * Returns a heap copy of @apc that shares its vport identity but owns no
+ * queues, so the existing allocators and destroyers can run against it
+ * without touching the live context.
+ */
+struct mana_port_context *mana_qset_scratch_alloc(struct mana_port_context *apc)
+{
+ struct mana_port_context *scratch;
+
+ scratch = kvzalloc(sizeof(*scratch), GFP_KERNEL);
+ if (!scratch)
+ return NULL;
+
+ *scratch = *apc;
+
+ /* Owns no queues yet. */
+ scratch->eqs = NULL;
+ scratch->tx_qp = NULL;
+ scratch->rxqs = NULL;
+ scratch->indir_table = NULL;
+ scratch->rxobj_table = NULL;
+ scratch->default_rxobj = INVALID_MANA_HANDLE;
+ scratch->mana_eqs_debugfs = NULL;
+
+ /* Never consume the live set's pre-allocated RX buffers;
+ * mana_get_rxbuf() falls back to normal allocation when these
+ * are NULL, which is what we want since the swap path no longer
+ * needs to de-risk post-teardown allocation.
+ */
+ scratch->rxbufs_pre = NULL;
+ scratch->das_pre = NULL;
+ scratch->rxbpre_total = 0;
+
+ /* Suppress debugfs for queues built through the scratch context:
+ * two sets are alive at once and would collide on the same names
+ * under vport%d. debugfs_start_creating() returns early on an
+ * IS_ERR() parent, and debugfs_remove() ignores IS_ERR_OR_NULL,
+ * so this makes every create/remove a clean no-op.
+ */
+ scratch->mana_port_debugfs = ERR_PTR(-ENODEV);
+
+ return scratch;
+}
+
+void mana_qset_scratch_free(struct mana_port_context *scratch)
+{
+ kvfree(scratch);
+}
+
+/* Rebuild the queues at the current count in @scratch, for callers changing a
+ * per-queue property; a count change goes through mana_split_qset() or
+ * mana_grow_qset(), so this never has to add an EQ. The installed set keeps
+ * serving traffic meanwhile. On error nothing is left allocated.
+ */
+int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
+ unsigned int rx_queue_size, unsigned int tx_queue_size,
+ u32 priv_flags, struct mana_qset *out)
+{
+ struct net_device *ndev = scratch->ndev;
+ int err;
+
+ ASSERT_RTNL();
+
+ scratch->num_queues = num_queues;
+ scratch->rx_queue_size = rx_queue_size;
+ scratch->tx_queue_size = tx_queue_size;
+ scratch->priv_flags = priv_flags;
+
+ err = mana_init_port_context(scratch);
+ if (err)
+ goto out_err;
+
+ err = mana_rss_table_alloc(scratch);
+ if (err)
+ goto cleanup_rxq_array;
+
+ err = mana_create_eq(scratch);
+ if (err)
+ goto cleanup_rss;
+
+ err = mana_create_txq(scratch, ndev);
+ if (err)
+ goto cleanup_eq;
+
+ err = mana_add_rx_queues(scratch, ndev);
+ if (err)
+ goto cleanup_rxq;
+
+ mana_rss_table_init(scratch);
+
+ mana_qset_snapshot(scratch, out);
+ return 0;
+
+cleanup_rxq:
+ /* mana_add_rx_queues() may have created queues before failing; they
+ * own RQ/CQ objects, NAPI state and page pools, so tear down whatever
+ * made it into scratch->rxqs[] before dropping the array.
+ */
+ mana_destroy_rxqs(scratch);
+ mana_destroy_txq(scratch);
+cleanup_eq:
+ mana_destroy_eq(scratch);
+cleanup_rss:
+ mana_cleanup_indir_table(scratch);
+cleanup_rxq_array:
+ kfree(scratch->rxqs);
+ scratch->rxqs = NULL;
+out_err:
+ netdev_err(ndev, "mana_alloc_qset(num_queues=%u) failed: %d\n",
+ num_queues, err);
+ return err;
+}
+
+/* Tear down @qset, no longer installed on @apc, against @scratch so the live
+ * context never points at queues being freed.
+ */
+void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
+{
+ struct bpf_prog *retiring_prog;
+ unsigned int retiring_queues;
+
+ ASSERT_RTNL();
+
+ if (!qset->rxqs && !qset->tx_qp && !qset->eqs)
+ return;
+
+ /* These queues are leaving. Stop their completions from touching the
+ * shared netdev queues: net_txq is shared with whatever replaced them
+ * at the same index, and a queue that is only draining always looks
+ * like it has room, so it would wake a live queue that stopped itself
+ * because its ring was full. The synchronize_net() below then retires
+ * any poll that has not seen the flag yet.
+ */
+ if (qset->tx_qp) {
+ unsigned int q;
+
+ for (q = 0; q < qset->num_queues; q++) {
+ if (qset->tx_qp[q])
+ WRITE_ONCE(qset->tx_qp[q]->txq.retiring, true);
+ }
+ }
+
+ /* The datapath gates on apc->port_is_up and then dereferences
+ * apc->tx_qp[] / apc->rxqs[] with no lock. mana_publish_qset() drains
+ * those readers before it installs the incoming set, which cannot
+ * cover one that sampled the retiring pointers between that install
+ * and the gate reopening. mana_xdp_xmit() is the case that matters:
+ * it runs from a redirecting device's NAPI, so the napi_synchronize()
+ * that mana_destroy_txq()/mana_destroy_rxq() do on this port's own
+ * NAPIs never waits for it. Give any such reader a grace period to
+ * finish before its queues are torn down under it. Every caller is a
+ * reconfiguration path holding RTNL, so this is expedited.
+ */
+ synchronize_net();
+
+ mana_qset_install(scratch, qset);
+
+ /* Note what this set owes the XDP program, but leave the queues
+ * pointing at it. They are still polling, and a packet already in a
+ * retiring RQ has to keep running the program rather than slip past
+ * it into the stack. The references are dropped once the queues are
+ * gone, below. XDP_TX from those polls is harmless here: it goes
+ * through mana_start_xmit() on the live port context, so it reaches
+ * the queue set that replaced this one, not the one being drained.
+ */
+ retiring_prog = mana_chn_xdp_peek(scratch);
+ retiring_queues = scratch->num_queues;
+
+ /* The retiring TX queues may still hold packets the device has not
+ * completed. Drain them before the SQs and the SKB queues go away,
+ * or those SKBs and their DMA mappings are leaked.
+ *
+ * This runs before any RX teardown, the order mana_dealloc_queues()
+ * uses. A device wedged badly enough to need the reset below is also
+ * one whose RQ teardown will not complete, and unmapping RX buffers
+ * first would leave it free to keep writing into them for as long as
+ * the drain takes.
+ */
+ if (mana_drain_txqs(scratch)) {
+ /* The drain had to reset the function to stop the device
+ * touching those buffers. A function reset takes down every
+ * port on the adapter, not just this one, so rebuild them all
+ * - the same recovery mana_tx_timeout() relies on. A port that
+ * is already down has nothing to rebuild and its handler
+ * leaves it down.
+ */
+ struct mana_port_context *apc = netdev_priv(scratch->ndev);
+ struct mana_context *ac = apc->ac;
+ unsigned int i;
+
+ netdev_err(scratch->ndev,
+ "device reset while retiring a queue set, scheduling port reset\n");
+
+ for (i = 0; i < ac->num_ports; i++) {
+ if (!ac->ports[i])
+ continue;
+ queue_work(ac->per_port_queue_reset_wq,
+ &((struct mana_port_context *)
+ netdev_priv(ac->ports[i]))->queue_reset_work);
+ }
+ }
+
+ /* Traffic was still being steered at these queues moments ago, so
+ * fence each retiring RQ before its buffers are unmapped, again the
+ * order mana_dealloc_queues() uses. mana_destroy_rxq() does destroy
+ * the hardware RQ before unmapping anything, but the fence is what
+ * makes the device confirm it is done with the buffers first.
+ */
+ mana_fence_rqs(scratch);
+
+ mana_destroy_rxqs(scratch);
+
+ /* The queues are gone, so nothing can run the program any more. */
+ mana_chn_xdp_release(retiring_prog, retiring_queues);
+
+ mana_destroy_txq(scratch);
+ mana_destroy_eq(scratch);
+ mana_cleanup_indir_table(scratch);
+ kfree(scratch->rxqs);
+ scratch->rxqs = NULL;
+
+ memset(qset, 0, sizeof(*qset));
+}
+
+/* --- end of pre-allocate + swap reconfiguration path ---------------------- */
+
int mana_detach(struct net_device *ndev, bool from_close)
{
struct mana_port_context *apc = netdev_priv(ndev);
@@ -4237,6 +4631,13 @@ void mana_remove(struct gdma_dev *gd, bool suspending)
unregister_netdevice(ndev);
mana_cleanup_indir_table(apc);
+ /* Clear the slot before the netdev goes away. A later port
+ * whose teardown has to reset the function walks ac->ports[]
+ * to schedule the rebuild, and would otherwise reach into the
+ * port freed here.
+ */
+ ac->ports[i] = NULL;
+
rtnl_unlock();
free_netdev(ndev);
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index ece7ff9cc409a806b6a6de70a85b44874bfa6dad..04b7a5c0fdabc9abc693065c32d4f60fc9ac6809 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -648,10 +648,11 @@ static int mana_set_coalesce(struct net_device *ndev,
return 0;
}
-/* mana_set_channels - change the number of queues on a port
- *
- * Returns -EBUSY if RDMA holds the vport with EQs sized to the
- * current num_queues.
+/* A count change leaves every surviving queue configured as it was, so
+ * neither direction rebuilds: a reduction retires the tail, an increase
+ * builds only the queues added. On failure the existing queues keep running
+ * and the requested value is never replaced by a fallback. The vport is never
+ * torn down, so RDMA cannot take it mid-reconfiguration.
*/
static int mana_set_channels(struct net_device *ndev,
struct ethtool_channels *channels)
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index 83b7eff4646ead7aef1382c6ce565a573a940af4..a7b7a00a57f176e9dc889f7b09b6ef56d5608a8e 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -143,6 +143,16 @@ struct mana_txq {
bool napi_initialized;
+ /* Value of mana_context.reset_gen when this queue was created. */
+ u32 reset_gen;
+
+ /* Set once this queue has been unpublished and is on its way out.
+ * Its completions must not touch flow control any more: net_txq is
+ * shared with the queue that replaced it at the same index, and a
+ * draining queue always looks like it has room.
+ */
+ bool retiring;
+
struct mana_stats_tx stats;
};
@@ -537,6 +547,13 @@ struct mana_context {
u8 bm_hostmode;
struct mana_ethtool_hc_stats hc_stats;
+
+ /* Bumped on every PCI function reset. A queue created before the
+ * current value can no longer be reached by the device, so its buffers
+ * need no drain. Written under RTNL, read locklessly.
+ */
+ u32 reset_gen;
+
struct workqueue_struct *per_port_queue_reset_wq;
/* Workqueue for querying hardware stats */
struct delayed_work gf_stats_work;
@@ -661,6 +678,39 @@ struct mana_port_context {
u32 steer_cqe_coalescing;
};
+/* struct mana_qset - a self-contained snapshot of the queue-related
+ * fields inside mana_port_context that can be swapped atomically.
+ *
+ * Prototype for the "pre-allocate + swap" reconfiguration path (as
+ * suggested by netdev maintainers): a new qset is allocated while the
+ * current one keeps serving traffic, then apc's queue fields are
+ * atomically switched to the new set and the old set is torn down.
+ * The vport (port_handle / vport_use_count) is *not* touched, so RDMA
+ * can never race in during reconfiguration.
+ */
+struct mana_qset {
+ struct mana_eq *eqs;
+ struct mana_tx_qp **tx_qp;
+ struct mana_rxq **rxqs;
+
+ u32 *indir_table;
+ u32 indir_table_sz;
+ mana_handle_t *rxobj_table;
+ mana_handle_t default_rxobj;
+
+ unsigned int num_queues;
+ unsigned int rx_queue_size;
+ unsigned int tx_queue_size;
+ u32 priv_flags;
+
+ /* Per-queue-set debugfs root ("EQs"). Owned by the qset: it is
+ * recreated by mana_create_eq() for each new set and torn down
+ * with that set, so it must travel with the qset rather than
+ * staying on apc.
+ */
+ struct dentry *mana_eqs_debugfs;
+};
+
netdev_tx_t mana_start_xmit(struct sk_buff *skb, struct net_device *ndev);
int mana_config_rss(struct mana_port_context *ac, enum TRI_STATE rx,
bool update_hash, bool update_tab);
@@ -670,6 +720,17 @@ int mana_alloc_queues(struct net_device *ndev);
int mana_attach(struct net_device *ndev);
int mana_detach(struct net_device *ndev, bool from_close);
+/* Pre-allocate + swap reconfiguration. Allocation and teardown run against a
+ * scratch context, so the live port context is mutated only inside
+ * mana_publish_qset() with TX disabled. Both sets share a port-owned EQ pool.
+ */
+struct mana_port_context *mana_qset_scratch_alloc(struct mana_port_context *apc);
+void mana_qset_scratch_free(struct mana_port_context *scratch);
+int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
+ unsigned int rx_queue_size, unsigned int tx_queue_size,
+ u32 priv_flags, struct mana_qset *out);
+void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset);
+
void mana_dim_change(struct mana_cq *cq, bool enable);
int mana_probe(struct gdma_dev *gd, bool resuming);
@@ -685,6 +746,8 @@ u32 mana_run_xdp(struct net_device *ndev, struct mana_rxq *rxq,
struct xdp_buff *xdp, void *buf_va, uint pkt_len);
struct bpf_prog *mana_xdp_get(struct mana_port_context *apc);
void mana_chn_setxdp(struct mana_port_context *apc, struct bpf_prog *prog);
+struct bpf_prog *mana_chn_xdp_peek(struct mana_port_context *apc);
+void mana_chn_xdp_release(struct bpf_prog *prog, unsigned int num_queues);
int mana_bpf(struct net_device *ndev, struct netdev_bpf *bpf);
int mana_query_gf_stats(struct mana_context *ac);
int mana_query_link_cfg(struct mana_port_context *apc);
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 02/13] net: mana: swap queue sets in mana_set_channels
2026-08-13 5:04 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
2026-08-13 5:04 ` [PATCH net-next v2 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
@ 2026-08-13 5:04 ` Long Li
2026-08-13 5:04 ` [PATCH net-next v2 03/13] net: mana: swap queue sets in mana_set_ringparam Long Li
` (10 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Long Li @ 2026-08-13 5:04 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
Convert the channel count path away from detach/attach: build the new
queue set while the current one keeps serving traffic, then swap it in
and retire the old one.
An allocation failure now returns the error with the running
configuration untouched, instead of leaving the port down, and the value
the user asked for is never silently replaced by a fallback. The vport is
never released, so RDMA cannot claim it mid-reconfiguration. If the swap
itself fails the previous set is put back and the port keeps running on
it.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 7 +
drivers/net/ethernet/microsoft/mana/mana_en.c | 345 ++++++++++++++++--
.../ethernet/microsoft/mana/mana_ethtool.c | 84 ++++-
include/net/mana/mana.h | 10 +
4 files changed, 400 insertions(+), 46 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index ca602e27044f92b87295cbc2de924adc71efa780..e16ce2a0715839594a5837288c1d4c1de412e7fb 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
@@ -59,6 +59,13 @@ int mana_xdp_xmit(struct net_device *ndev, int n, struct xdp_frame **frames,
if (unlikely(!apc->port_is_up))
return 0;
+ /* Pair with the smp_wmb() in mana_publish_qset(), as mana_start_xmit()
+ * does. This path is gated only by the flag above, so without the
+ * barrier it could pick q_idx from a stale real_num_tx_queues and
+ * index a freshly installed, smaller apc->tx_qp[].
+ */
+ smp_rmb();
+
q_idx = smp_processor_id() % ndev->real_num_tx_queues;
for (i = 0; i < n; i++) {
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 60b1fc93d453b16bec37924e32ea54c64f179f1c..c0f31b386536a34af338c4d8be50537e2fe55317 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -363,6 +363,18 @@ netdev_tx_t mana_start_xmit(struct sk_buff *skb, struct net_device *ndev)
if (unlikely(!apc->port_is_up))
goto tx_drop;
+ /* Pair with the smp_wmb() in mana_publish_qset(). A control dependency
+ * does not order loads, and a stale apc->num_queues would admit an
+ * index past the end of a freshly shrunk apc->tx_qp[].
+ */
+ smp_rmb();
+
+ /* XDP_TX from a retiring set carries its own RX queue index, which can
+ * be past the end of a smaller replacement apc->tx_qp[].
+ */
+ if (unlikely(txq_idx >= apc->num_queues))
+ goto tx_drop_count;
+
if (skb_cow_head(skb, MANA_HEADROOM))
goto tx_drop_count;
@@ -1042,6 +1054,11 @@ static void mana_cleanup_indir_table(struct mana_port_context *apc)
static int mana_init_port_context(struct mana_port_context *apc)
{
+ /* A port reconfigured while down already has an apc->rxqs, and
+ * mana_detach() takes its "already detached" early return without
+ * releasing it. Free it rather than overwrite the pointer.
+ */
+ kfree(apc->rxqs);
apc->rxqs = kzalloc_objs(struct mana_rxq *, apc->num_queues);
return !apc->rxqs ? -ENOMEM : 0;
@@ -2015,6 +2032,10 @@ static void mana_poll_tx_cq(struct mana_cq *cq)
/* Ensure checking txq_stopped before apc->port_is_up. */
smp_rmb();
+ /* Ordered by the same barrier: reaching here with txq_stopped set means
+ * the replacement queue has already run, which is strictly after this
+ * queue was marked retiring.
+ */
if (txq_stopped && !READ_ONCE(txq->retiring) && apc->port_is_up &&
avail_space >= MAX_TX_WQE_SIZE) {
netif_tx_wake_queue(net_txq);
@@ -3861,10 +3882,16 @@ static int mana_dealloc_queues(struct net_device *ndev)
* The swap path builds a *new* set of EQs/TXQs/RXQs while the current set
* keeps serving traffic. If allocation fails the current qset is untouched
* and we return the error; the user's requested value is never silently
- * replaced by a fallback. Publishing a new set onto the live port
- * context is added separately. The vport is never torn down: vport_use_count
+ * replaced by a fallback. Once the new qset is ready we publish it onto apc
+ * and destroy the old one. The vport is never torn down: vport_use_count
* stays at 1 throughout, so RDMA cannot hijack it.
*
+ * The cost of never dropping the working queues is that a rebuild needs room
+ * for both sets at once, so one at the vport's maximum queue count can be
+ * refused by the firmware where a teardown-first sequence would have fit.
+ * That surfaces as a failed ethtool operation with the port still running on
+ * its previous queues, which is the trade this path exists to make.
+ *
* Allocation and teardown run against a *scratch* mana_port_context rather
* than the live one. This is essential, not cosmetic: an earlier revision
* temporarily NULLed apc->tx_qp so the allocators could
@@ -4041,6 +4068,268 @@ int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
return err;
}
+/* Close a port mana_publish_qset() gave up on; does nothing otherwise. Under
+ * RTNL.
+ *
+ * The caller releases the unpublished set first: closing destroys the shared
+ * EQ pool its CQs attach to, and only the caller knows whether it owns its
+ * queues or shares them with the live set. RX is already off.
+ *
+ * Merely stopping the port would leave port_is_up false with queues still
+ * allocated, so mana_detach() skips teardown and the next open trips
+ * WARN_ON(apc->eqs).
+ */
+void mana_publish_close_if_needed(struct mana_port_context *apc)
+{
+ ASSERT_RTNL();
+
+ if (!apc->publish_dead_end)
+ return;
+
+ apc->publish_dead_end = false;
+
+ /* mana_dealloc_queues() requires the port already marked down, which
+ * mana_publish_qset() did before the swap it is unwinding.
+ */
+ if (mana_dealloc_queues(apc->ndev))
+ netdev_err(apc->ndev,
+ "failed to close the port after a failed rollback\n");
+}
+
+/* Start only the netdev queues that can take work. A carried-over queue may
+ * still have a full ring, and restarting it would just make mana_start_xmit()
+ * drop; leave it for mana_poll_tx_cq() to wake. Must run after port_is_up is
+ * set, or that wakeup is gated off.
+ */
+static void mana_start_txqs(struct mana_port_context *apc)
+{
+ struct net_device *ndev = apc->ndev;
+ unsigned int i;
+
+ if (!apc->tx_qp)
+ return;
+
+ for (i = 0; i < apc->num_queues; i++) {
+ if (!apc->tx_qp[i])
+ continue;
+
+ if (mana_can_tx(apc->tx_qp[i]->txq.gdma_sq))
+ netif_tx_wake_queue(netdev_get_tx_queue(ndev, i));
+ }
+}
+
+/* A retiring queue shares its struct netdev_queue with whatever replaced it
+ * at the same index, and only ever drains, so it always looks like it has
+ * room. Without this flag its completions would wake a netdev queue that the
+ * live queue stopped on a full ring.
+ *
+ * A queue both sets own must end up unmarked, so callers mark the leaving set
+ * first and unmark the incoming one second.
+ */
+static void mana_qset_set_retiring(struct mana_qset *qset, bool retiring)
+{
+ unsigned int q;
+
+ if (!qset->tx_qp)
+ return;
+
+ for (q = 0; q < qset->num_queues; q++) {
+ if (qset->tx_qp[q])
+ WRITE_ONCE(qset->tx_qp[q]->txq.retiring, retiring);
+ }
+}
+
+/* Give up on a swap. Steering may still point at the set the caller is about
+ * to free, and restoring it is exactly what failed, so stop delivery before
+ * those RQs and their buffers go away. This is the narrow steering request -
+ * no key, table or default-rxobj update - so it can land where the full
+ * mana_config_rss() restore did not.
+ *
+ * Closing the port is left to mana_publish_close_if_needed(), which must run
+ * after the caller has released that set.
+ */
+static void mana_publish_give_up(struct mana_port_context *apc)
+{
+ int err;
+
+ apc->rss_state = TRI_STATE_FALSE;
+
+ err = mana_disable_vport_rx(apc);
+ if (err && mana_en_need_log(apc, err))
+ netdev_err(apc->ndev, "failed to disable vPort RX: %d\n", err);
+
+ apc->publish_dead_end = true;
+}
+
+/* Swap @newq onto @apc, handing the previous set back in @out_old for the
+ * caller to free. On failure the old set is reinstalled and the caller frees
+ * only @newq. Must be called under RTNL.
+ *
+ * netif_tx_disable() is load-bearing: mana_start_xmit() dereferences
+ * apc->tx_qp[] guarded only by apc->port_is_up, and ndo_xdp_xmit() bypasses
+ * the txq-stopped checks, so port_is_up is cleared over the same window.
+ */
+int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
+ struct mana_qset *out_old)
+{
+ struct net_device *ndev = apc->ndev;
+ bool carrier_ok;
+ int err;
+
+ ASSERT_RTNL();
+
+ carrier_ok = netif_carrier_ok(ndev);
+ netif_carrier_off(ndev);
+
+ /* Clear port_is_up before stopping the queues, pairing with the
+ * smp_rmb() in mana_poll_tx_cq(): that reader samples
+ * netif_tx_queue_stopped() first, so a completion seeing a queue
+ * stopped here also sees port_is_up false and will not wake it
+ * mid-swap. It also fences mana_xdp_xmit(), which is gated only by
+ * port_is_up and would otherwise index a stale apc->tx_qp[].
+ */
+ WRITE_ONCE(apc->port_is_up, false);
+
+ /* Ensure port state updated before txq state */
+ smp_wmb();
+
+ netif_tx_disable(ndev);
+
+ mana_qset_snapshot(apc, out_old);
+
+ /* Mark the outgoing set before the grace period, not after: a
+ * completion that saw the flag clear must not still be in flight when
+ * the gate reopens, or it could wake a netdev queue that its
+ * replacement had stopped on a full ring.
+ */
+ mana_qset_set_retiring(out_old, true);
+
+ /* Wait out any transmit or ndo_xdp_xmit() that was already past the
+ * port_is_up test before the swap touches apc->tx_qp / the counts,
+ * and any completion that still saw the flag clear above.
+ */
+ synchronize_net();
+
+ /* Anything the incoming set carries over is staying, so clear the flag
+ * again - after the marking above, before the gate reopens.
+ */
+ mana_qset_set_retiring(newq, false);
+
+ mana_qset_install(apc, newq);
+ apc->rss_state = apc->num_queues > 1 ? TRI_STATE_TRUE : TRI_STATE_FALSE;
+
+ err = netif_set_real_num_tx_queues(ndev, apc->num_queues);
+ if (err)
+ goto rollback;
+
+ err = netif_set_real_num_rx_queues(ndev, apc->num_queues);
+ if (err)
+ goto rollback;
+
+ /* Carry the XDP program over before steering can reach the new RXQs:
+ * they were created with bpf_prog == NULL, so a packet arriving first
+ * would bypass an attached program. This also takes the per-queue
+ * references that mana_free_qset() drops for the old set.
+ */
+ mana_chn_setxdp(apc, mana_xdp_get(apc));
+
+ err = mana_config_rss(apc, TRI_STATE_TRUE, true, true);
+ if (err)
+ goto rollback;
+
+ /* Pair with the queue-state stores above: a datapath reader that sees
+ * the gate open must also see the queue set it is about to index.
+ */
+ smp_wmb();
+
+ WRITE_ONCE(apc->port_is_up, true);
+ mana_start_txqs(apc);
+ if (carrier_ok)
+ netif_carrier_on(ndev);
+
+ return 0;
+
+rollback:
+ netdev_err(ndev, "mana_publish_qset failed: %d, restoring previous queue set\n",
+ err);
+
+ /* The roles are swapped now: @newq is the set going away and @out_old
+ * is live again. Same ordering rule, leaving set first.
+ */
+ mana_qset_set_retiring(newq, true);
+ mana_qset_set_retiring(out_old, false);
+
+ mana_qset_install(apc, out_old);
+ apc->rss_state = apc->num_queues > 1 ? TRI_STATE_TRUE : TRI_STATE_FALSE;
+
+ if (netif_set_real_num_tx_queues(ndev, apc->num_queues) ||
+ netif_set_real_num_rx_queues(ndev, apc->num_queues)) {
+ /* The netdev queue counts no longer describe the restored
+ * apc->tx_qp[], so resuming TX could index past it. Leave the
+ * port stopped and the carrier down instead; that is visible
+ * to the admin and recoverable with a down/up.
+ *
+ * Steering can still point at @newq, which the caller frees
+ * next, so shut RX down at the vport first.
+ */
+ netdev_err(ndev, "failed to restore queue counts, closing the port\n");
+ mana_publish_give_up(apc);
+ return err;
+ }
+
+ if (mana_config_rss(apc, TRI_STATE_TRUE, true, true)) {
+ /* Steering may still point at the queue set the caller is
+ * about to free, and it cannot be repointed. Disable vport RX
+ * so the device stops delivering into those queues before they
+ * are destroyed, and stay down rather than run with steering
+ * that does not match apc->rxqs[].
+ */
+ netdev_err(ndev, "failed to restore RSS steering, closing the port\n");
+ mana_publish_give_up(apc);
+ return err;
+ }
+
+ /* Same pairing as the success path: the restored queue set has to be
+ * visible before the gate reopens on it.
+ */
+ smp_wmb();
+
+ WRITE_ONCE(apc->port_is_up, true);
+ mana_start_txqs(apc);
+ if (carrier_ok)
+ netif_carrier_on(ndev);
+
+ /* out_old is live again on apc; caller must only free newq. */
+ return err;
+}
+
+/* Give live queues the debugfs nodes suppressed while they were built in a
+ * scratch context, whose names would collide under vport%d. Once the retiring
+ * set is gone the survivors take them.
+ *
+ * Idempotent: a carried-over queue keeps its node; suppressed creation leaves
+ * an error pointer, not NULL, so both read as "no node". Under RTNL.
+ */
+static void mana_qset_debugfs_publish(struct mana_port_context *apc)
+{
+ unsigned int i;
+
+ ASSERT_RTNL();
+
+ if (IS_ERR_OR_NULL(apc->mana_port_debugfs))
+ return;
+
+ for (i = 0; i < apc->num_queues; i++) {
+ if (apc->tx_qp && apc->tx_qp[i] &&
+ IS_ERR_OR_NULL(apc->tx_qp[i]->mana_tx_debugfs))
+ mana_create_txq_debugfs(apc, i);
+
+ if (apc->rxqs && apc->rxqs[i] &&
+ IS_ERR_OR_NULL(apc->rxqs[i]->mana_rx_debugfs))
+ mana_create_rxq_debugfs(apc, i);
+ }
+}
+
/* Tear down @qset, no longer installed on @apc, against @scratch so the live
* context never points at queues being freed.
*/
@@ -4070,41 +4359,36 @@ void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
}
}
- /* The datapath gates on apc->port_is_up and then dereferences
- * apc->tx_qp[] / apc->rxqs[] with no lock. mana_publish_qset() drains
- * those readers before it installs the incoming set, which cannot
- * cover one that sampled the retiring pointers between that install
- * and the gate reopening. mana_xdp_xmit() is the case that matters:
- * it runs from a redirecting device's NAPI, so the napi_synchronize()
- * that mana_destroy_txq()/mana_destroy_rxq() do on this port's own
- * NAPIs never waits for it. Give any such reader a grace period to
- * finish before its queues are torn down under it. Every caller is a
- * reconfiguration path holding RTNL, so this is expedited.
+ /* A reader that sampled the retiring pointers after mana_publish_qset()
+ * installed the new set is not covered by the drain it did earlier.
+ * mana_xdp_xmit() is the case that matters: it runs from another
+ * device's NAPI, which this port's napi_synchronize() never waits for.
*/
synchronize_net();
mana_qset_install(scratch, qset);
- /* Note what this set owes the XDP program, but leave the queues
- * pointing at it. They are still polling, and a packet already in a
- * retiring RQ has to keep running the program rather than slip past
- * it into the stack. The references are dropped once the queues are
- * gone, below. XDP_TX from those polls is harmless here: it goes
- * through mana_start_xmit() on the live port context, so it reaches
- * the queue set that replaced this one, not the one being drained.
+ /* Teardown follows mana_dealloc_queues()' order, minus the vport RX
+ * disable: steering already points at the incoming set, and disabling
+ * vport RX would stop the set that is now live. Where publish could
+ * not repoint steering it disabled vport RX itself, so nothing is
+ * delivered here either way.
+ */
+
+ /* Note what this set owes the XDP program but leave the queues
+ * pointing at it: they are still polling, and a packet already in a
+ * retiring RQ must keep running the program rather than slip into the
+ * stack. The references are dropped once the queues are gone, below.
*/
retiring_prog = mana_chn_xdp_peek(scratch);
retiring_queues = scratch->num_queues;
- /* The retiring TX queues may still hold packets the device has not
- * completed. Drain them before the SQs and the SKB queues go away,
- * or those SKBs and their DMA mappings are leaked.
+ /* Drain packets the device has not completed before the SQs and SKB
+ * queues go away, or those SKBs and their DMA mappings are leaked.
*
- * This runs before any RX teardown, the order mana_dealloc_queues()
- * uses. A device wedged badly enough to need the reset below is also
- * one whose RQ teardown will not complete, and unmapping RX buffers
- * first would leave it free to keep writing into them for as long as
- * the drain takes.
+ * This runs before any RX teardown, as mana_dealloc_queues() does:
+ * unmapping RX buffers first would leave a wedged device free to keep
+ * writing into them for as long as the drain takes.
*/
if (mana_drain_txqs(scratch)) {
/* The drain had to reset the function to stop the device
@@ -4150,6 +4434,13 @@ void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
scratch->rxqs = NULL;
memset(qset, 0, sizeof(*qset));
+
+ /* Queues built through a scratch context carry no debugfs nodes,
+ * because both sets are alive during the swap and would collide on
+ * the same names. The retiring set's nodes are gone now, so the
+ * published queues can finally take those names.
+ */
+ mana_qset_debugfs_publish(netdev_priv(scratch->ndev));
}
/* --- end of pre-allocate + swap reconfiguration path ---------------------- */
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 04b7a5c0fdabc9abc693065c32d4f60fc9ac6809..b12291555eaeb4fafc305ffe7d9e550ec01952c8 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -659,42 +659,88 @@ static int mana_set_channels(struct net_device *ndev,
{
struct mana_port_context *apc = netdev_priv(ndev);
unsigned int new_count = channels->combined_count;
- unsigned int old_count = apc->num_queues;
+ struct mana_port_context *scratch;
+ struct mana_qset newq, oldq;
int err;
- /* Set channel_changing to block RDMA from grabbing the vport
- * during the detach/attach window. mana_cfg_vport() checks
- * this flag under vport_mutex and returns -EBUSY if set.
+ if (new_count < 1 || new_count > apc->max_queues) {
+ netdev_err(ndev, "Invalid combined_count %u (max %u)\n",
+ new_count, apc->max_queues);
+ return -EINVAL;
+ }
+
+ if (new_count == apc->num_queues)
+ return 0;
+
+ /* Down: no queues to swap, so record the count and resize the arrays
+ * indexed by it. apc->rxqs has to grow here because mana_open() goes
+ * straight to mana_alloc_queues() without rebuilding the port context.
+ *
+ * RDMA can own the vport while the port is down and derives an EQ from
+ * apc->eqs[] modulo apc->num_queues, so refuse while it is in use.
*/
mutex_lock(&apc->vport_mutex);
- if (!apc->port_is_up && apc->vport_use_count) {
+ if (!apc->port_is_up) {
+ struct mana_rxq **rxqs;
+
+ if (apc->vport_use_count) {
+ mutex_unlock(&apc->vport_mutex);
+ return -EBUSY;
+ }
+
+ rxqs = kzalloc_objs(struct mana_rxq *, new_count);
+ if (!rxqs) {
+ mutex_unlock(&apc->vport_mutex);
+ return -ENOMEM;
+ }
+
+ kfree(apc->rxqs);
+ apc->rxqs = rxqs;
+ apc->num_queues = new_count;
+ mutex_unlock(&apc->vport_mutex);
+ return 0;
+ }
+
+ /* Block RDMA from acquiring the vport for the duration.
+ *
+ * No vport_use_count test here, unlike the branch above: bringing the
+ * port up takes the vport itself, so the count is always non-zero. That
+ * reference is also what makes the swap safe - RAW QPs, the only users
+ * of apc->eqs[] modulo apc->num_queues, cannot exist while the ethernet
+ * port owns it.
+ */
+ if (apc->channel_changing) {
mutex_unlock(&apc->vport_mutex);
return -EBUSY;
}
apc->channel_changing = true;
mutex_unlock(&apc->vport_mutex);
- err = mana_pre_alloc_rxbufs(apc, ndev->mtu, new_count);
- if (err) {
- netdev_err(ndev, "Insufficient memory for new allocations");
+ scratch = mana_qset_scratch_alloc(apc);
+ if (!scratch) {
+ err = -ENOMEM;
goto clear_flag;
}
- err = mana_detach(ndev, false);
- if (err) {
- netdev_err(ndev, "mana_detach failed: %d\n", err);
- goto out;
- }
+ err = mana_alloc_qset(scratch, new_count, apc->rx_queue_size,
+ apc->tx_queue_size, apc->priv_flags, &newq);
+ if (err)
+ goto free_scratch; /* current qset untouched, nothing to undo */
- apc->num_queues = new_count;
- err = mana_attach(ndev);
+ err = mana_publish_qset(apc, &newq, &oldq);
if (err) {
- apc->num_queues = old_count;
- netdev_err(ndev, "mana_attach failed: %d\n", err);
+ mana_free_qset(scratch, &newq);
+ goto free_scratch;
}
-out:
- mana_pre_dealloc_rxbufs(apc);
+ mana_free_qset(scratch, &oldq);
+
+free_scratch:
+ /* After the caller-side cleanup above, so the EQ pool outlives the
+ * CQs that reference it.
+ */
+ mana_publish_close_if_needed(apc);
+ mana_qset_scratch_free(scratch);
clear_flag:
mutex_lock(&apc->vport_mutex);
apc->channel_changing = false;
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index a7b7a00a57f176e9dc889f7b09b6ef56d5608a8e..66a653bad22eba511d8cf8cf71b572c604044ecb 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -633,6 +633,13 @@ struct mana_port_context {
*/
bool channel_changing;
+ /* mana_publish_qset() could neither publish the new set nor restore the
+ * old one. Vport RX is already off; the port still has to be closed,
+ * which mana_publish_close_if_needed() does once the caller has
+ * released the set that failed.
+ */
+ bool publish_dead_end;
+
/* Net shaper handle*/
struct net_shaper_handle handle;
@@ -729,6 +736,9 @@ void mana_qset_scratch_free(struct mana_port_context *scratch);
int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
unsigned int rx_queue_size, unsigned int tx_queue_size,
u32 priv_flags, struct mana_qset *out);
+int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
+ struct mana_qset *out_old);
+void mana_publish_close_if_needed(struct mana_port_context *apc);
void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset);
void mana_dim_change(struct mana_cq *cq, bool enable);
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 03/13] net: mana: swap queue sets in mana_set_ringparam
2026-08-13 5:04 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
2026-08-13 5:04 ` [PATCH net-next v2 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
2026-08-13 5:04 ` [PATCH net-next v2 02/13] net: mana: swap queue sets in mana_set_channels Long Li
@ 2026-08-13 5:04 ` Long Li
2026-08-13 5:04 ` [PATCH net-next v2 04/13] net: mana: swap queue sets in mana_set_priv_flags Long Li
` (9 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Long Li @ 2026-08-13 5:04 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
Convert the ring size path to pre-allocate and swap, for the same reasons
as the channel count path: an allocation failure returns the error with
the running configuration untouched, and the vport is never released.
This drops the fallback on failure. Previously a failed mana_attach() was
retried with the previous values, or the defaults, or the minimums, so a
user who asked for a specific size could end up with a different one with
no indication beyond dmesg. There is nothing to recover from now, so the
error is returned.
Also return early when the requested sizes round to the values already in
use.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../ethernet/microsoft/mana/mana_ethtool.c | 74 +++++++++++++------
1 file changed, 51 insertions(+), 23 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index b12291555eaeb4fafc305ffe7d9e550ec01952c8..40e9886c87a81c86080e52955a0a685cf0472772 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -761,19 +761,18 @@ static void mana_get_ringparam(struct net_device *ndev,
ring->tx_max_pending = MAX_TX_BUFFERS_PER_QUEUE;
}
+
static int mana_set_ringparam(struct net_device *ndev,
struct ethtool_ringparam *ring,
struct kernel_ethtool_ringparam *kernel_ring,
struct netlink_ext_ack *extack)
{
struct mana_port_context *apc = netdev_priv(ndev);
+ struct mana_port_context *scratch;
+ struct mana_qset newq, oldq;
u32 new_tx, new_rx;
- u32 old_tx, old_rx;
int err;
- old_tx = apc->tx_queue_size;
- old_rx = apc->rx_queue_size;
-
if (ring->tx_pending < MIN_TX_BUFFERS_PER_QUEUE) {
NL_SET_ERR_MSG_FMT(extack, "tx:%d less than the min:%d", ring->tx_pending,
MIN_TX_BUFFERS_PER_QUEUE);
@@ -791,32 +790,61 @@ static int mana_set_ringparam(struct net_device *ndev,
netdev_info(ndev, "Using nearest power of 2 values for Txq:%d Rxq:%d\n",
new_tx, new_rx);
- /* pre-allocating new buffers to prevent failures in mana_attach() later */
- apc->rx_queue_size = new_rx;
- err = mana_pre_alloc_rxbufs(apc, ndev->mtu, apc->num_queues);
- apc->rx_queue_size = old_rx;
- if (err) {
- netdev_err(ndev, "Insufficient memory for new allocations\n");
- return err;
+ if (new_rx == apc->rx_queue_size && new_tx == apc->tx_queue_size)
+ return 0;
+
+ /* Port is down: no queues to rebuild, just record the new sizes. */
+ if (!apc->port_is_up) {
+ apc->rx_queue_size = new_rx;
+ apc->tx_queue_size = new_tx;
+ return 0;
}
- err = mana_detach(ndev, false);
- if (err) {
- netdev_err(ndev, "mana_detach failed: %d\n", err);
- goto out;
+ /* Block RDMA from acquiring the vport for the duration. The vport
+ * itself is never released, so vport_use_count stays > 0.
+ */
+ mutex_lock(&apc->vport_mutex);
+ if (apc->channel_changing) {
+ mutex_unlock(&apc->vport_mutex);
+ return -EBUSY;
}
+ apc->channel_changing = true;
+ mutex_unlock(&apc->vport_mutex);
- apc->tx_queue_size = new_tx;
- apc->rx_queue_size = new_rx;
+ scratch = mana_qset_scratch_alloc(apc);
+ if (!scratch) {
+ err = -ENOMEM;
+ goto clear_flag;
+ }
- err = mana_attach(ndev);
+ err = mana_alloc_qset(scratch, apc->num_queues, new_rx, new_tx,
+ apc->priv_flags, &newq);
if (err) {
- netdev_err(ndev, "mana_attach failed: %d\n", err);
- apc->tx_queue_size = old_tx;
- apc->rx_queue_size = old_rx;
+ NL_SET_ERR_MSG_FMT(extack, "failed to change ring params: %d",
+ err);
+ goto free_scratch; /* current qset untouched */
}
-out:
- mana_pre_dealloc_rxbufs(apc);
+
+ err = mana_publish_qset(apc, &newq, &oldq);
+ if (err) {
+ NL_SET_ERR_MSG_FMT(extack, "failed to change ring params: %d",
+ err);
+ mana_free_qset(scratch, &newq);
+ goto free_scratch;
+ }
+
+ mana_free_qset(scratch, &oldq);
+
+free_scratch:
+ /* After the caller-side cleanup above, so the EQ pool outlives the
+ * CQs that reference it.
+ */
+ mana_publish_close_if_needed(apc);
+ mana_qset_scratch_free(scratch);
+clear_flag:
+ mutex_lock(&apc->vport_mutex);
+ apc->channel_changing = false;
+ mutex_unlock(&apc->vport_mutex);
return err;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 04/13] net: mana: swap queue sets in mana_set_priv_flags
2026-08-13 5:04 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (2 preceding siblings ...)
2026-08-13 5:04 ` [PATCH net-next v2 03/13] net: mana: swap queue sets in mana_set_ringparam Long Li
@ 2026-08-13 5:04 ` Long Li
2026-08-13 5:04 ` [PATCH net-next v2 05/13] net: mana: swap queue sets in mana_change_mtu Long Li
` (8 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Long Li @ 2026-08-13 5:04 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF changes the RX buffer layout, so
toggling it rebuilds the queues. Convert that path to pre-allocate and
swap: a failure returns the error with both the queues and the flag word
untouched.
The flag becomes part of the queue-set configuration rather than being
written before the rebuild and rolled back on failure, so apc->priv_flags
never describes queues that do not exist. Scheduling queue_reset_work()
on failure goes with it, leaving the TX timeout handler as its only user.
The existing shortcuts keep their behaviour - a down port, or full-page
RX already forced by a jumbo MTU or an attached XDP program, still just
records the value - but now share one condition.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../ethernet/microsoft/mana/mana_ethtool.c | 79 ++++++++++---------
1 file changed, 41 insertions(+), 38 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 40e9886c87a81c86080e52955a0a685cf0472772..dc3a0a22959ef19e2ea9921e81c7295aef628ccf 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -870,11 +870,15 @@ static u32 mana_get_priv_flags(struct net_device *ndev)
return apc->priv_flags;
}
+/* MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF changes the RX buffer layout, so the
+ * queues have to be rebuilt.
+ */
static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
{
struct mana_port_context *apc = netdev_priv(ndev);
u32 changed = apc->priv_flags ^ priv_flags;
- u32 old_priv_flags = apc->priv_flags;
+ struct mana_port_context *scratch;
+ struct mana_qset newq, oldq;
int err = 0;
if (!changed)
@@ -884,54 +888,53 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
if (priv_flags & ~GENMASK(MANA_PRIV_FLAG_MAX - 1, 0))
return -EINVAL;
- apc->priv_flags = priv_flags;
-
- if (changed & BIT(MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF)) {
- if (!apc->port_is_up)
- return 0;
-
- /* If XDP is attached or MTU is jumbo, single-buffer-per-page
- * is already forced regardless of this flag. Skip the
- * expensive detach/attach cycle since nothing changes.
- */
- if (ndev->mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 ||
- mana_xdp_get(apc))
- return 0;
+ /* Only the RX buffer layout flag requires a queue rebuild. Anything
+ * else, a down port, or a configuration where single-buffer-per-page
+ * is already forced, just records the new value.
+ */
+ if (!(changed & BIT(MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF)) ||
+ !apc->port_is_up ||
+ ndev->mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 ||
+ mana_xdp_get(apc)) {
+ apc->priv_flags = priv_flags;
+ return 0;
+ }
- /* Block RDMA from grabbing the vport during detach/attach */
- mutex_lock(&apc->vport_mutex);
- apc->channel_changing = true;
+ /* Block RDMA from acquiring the vport for the duration. */
+ mutex_lock(&apc->vport_mutex);
+ if (apc->channel_changing) {
mutex_unlock(&apc->vport_mutex);
+ return -EBUSY;
+ }
+ apc->channel_changing = true;
+ mutex_unlock(&apc->vport_mutex);
- err = mana_pre_alloc_rxbufs(apc, ndev->mtu, apc->num_queues);
- if (err) {
- netdev_err(ndev,
- "Insufficient memory for new allocations\n");
- apc->priv_flags = old_priv_flags;
- goto clear_flag;
- }
+ scratch = mana_qset_scratch_alloc(apc);
+ if (!scratch) {
+ err = -ENOMEM;
+ goto clear_flag;
+ }
- err = mana_detach(ndev, false);
- if (err) {
- netdev_err(ndev, "mana_detach failed: %d\n", err);
- apc->priv_flags = old_priv_flags;
- goto out;
- }
+ err = mana_alloc_qset(scratch, apc->num_queues, apc->rx_queue_size,
+ apc->tx_queue_size, priv_flags, &newq);
+ if (err)
+ goto free_scratch; /* current qset and priv_flags untouched */
- err = mana_attach(ndev);
- if (err) {
- netdev_err(ndev, "mana_attach failed: %d\n", err);
- apc->priv_flags = old_priv_flags;
- }
+ err = mana_publish_qset(apc, &newq, &oldq);
+ if (err) {
+ mana_free_qset(scratch, &newq);
+ goto free_scratch;
}
-out:
- mana_pre_dealloc_rxbufs(apc);
+ mana_free_qset(scratch, &oldq);
+
+free_scratch:
+ mana_publish_close_if_needed(apc);
+ mana_qset_scratch_free(scratch);
clear_flag:
mutex_lock(&apc->vport_mutex);
apc->channel_changing = false;
mutex_unlock(&apc->vport_mutex);
-
return err;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 05/13] net: mana: swap queue sets in mana_change_mtu
2026-08-13 5:04 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (3 preceding siblings ...)
2026-08-13 5:04 ` [PATCH net-next v2 04/13] net: mana: swap queue sets in mana_set_priv_flags Long Li
@ 2026-08-13 5:04 ` Long Li
2026-08-13 5:04 ` [PATCH net-next v2 06/13] net: mana: swap queue sets in mana_xdp_set Long Li
` (7 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Long Li @ 2026-08-13 5:04 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
The RX buffer layout depends on the MTU, so changing it rebuilds the
queues. Convert mana_change_mtu() to pre-allocate and swap.
The MTU becomes part of the queue-set configuration, so a new set can be
built for the new MTU while the running one still serves traffic at the
old one, and ndev->mtu is updated only once the new set is live.
Previously it was written before mana_attach() and rolled back on
failure, so a failed change was briefly visible to the stack.
Signed-off-by: Long Li <longli@microsoft.com>
---
drivers/net/ethernet/microsoft/mana/mana_en.c | 69 ++++++++++++++-----
.../ethernet/microsoft/mana/mana_ethtool.c | 8 ++-
include/net/mana/mana.h | 13 +++-
3 files changed, 67 insertions(+), 23 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index c0f31b386536a34af338c4d8be50537e2fe55317..c858a58577dfb1774b2b9394c01e9000edbe8bf9 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -884,35 +884,49 @@ int mana_pre_alloc_rxbufs(struct mana_port_context *mpc, int new_mtu, int num_qu
return -ENOMEM;
}
+/* ndev->mtu is updated only once the new set is live (mana_publish_qset), so
+ * a failed allocation leaves the queues and the advertised MTU untouched.
+ */
static int mana_change_mtu(struct net_device *ndev, int new_mtu)
{
struct mana_port_context *mpc = netdev_priv(ndev);
- unsigned int old_mtu = ndev->mtu;
+ struct mana_port_context *scratch;
+ struct mana_qset newq, oldq;
int err;
- /* Pre-allocate buffers to prevent failure in mana_attach later */
- err = mana_pre_alloc_rxbufs(mpc, new_mtu, mpc->num_queues);
- if (err) {
- netdev_err(ndev, "Insufficient memory for new MTU\n");
- return err;
+ /* Port is down: no queues to rebuild, just record the new MTU.
+ * mana_open() will size the RX buffers accordingly.
+ */
+ if (!mpc->port_is_up) {
+ mpc->configured_mtu = new_mtu;
+ WRITE_ONCE(ndev->mtu, new_mtu);
+ return 0;
}
- err = mana_detach(ndev, false);
- if (err) {
- netdev_err(ndev, "mana_detach failed: %d\n", err);
- goto out;
- }
+ scratch = mana_qset_scratch_alloc(mpc);
+ if (!scratch)
+ return -ENOMEM;
- WRITE_ONCE(ndev->mtu, new_mtu);
+ err = mana_alloc_qset(scratch, mpc->num_queues, mpc->rx_queue_size,
+ mpc->tx_queue_size, mpc->priv_flags, new_mtu,
+ &newq);
+ if (err)
+ goto free_scratch; /* current qset and ndev->mtu untouched */
- err = mana_attach(ndev);
+ err = mana_publish_qset(mpc, &newq, &oldq);
if (err) {
- netdev_err(ndev, "mana_attach failed: %d\n", err);
- WRITE_ONCE(ndev->mtu, old_mtu);
+ mana_free_qset(scratch, &newq);
+ goto free_scratch;
}
-out:
- mana_pre_dealloc_rxbufs(mpc);
+ mana_free_qset(scratch, &oldq);
+
+free_scratch:
+ /* After the caller-side cleanup above, so the EQ pool outlives the
+ * CQs that reference it.
+ */
+ mana_publish_close_if_needed(mpc);
+ mana_qset_scratch_free(scratch);
return err;
}
@@ -3089,7 +3103,8 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc,
rxq->rxq_idx = rxq_idx;
rxq->rxobj = INVALID_MANA_HANDLE;
- mana_get_rxbuf_cfg(apc, ndev->mtu, &rxq->datasize, &rxq->alloc_size,
+ mana_get_rxbuf_cfg(apc, apc->configured_mtu, &rxq->datasize,
+ &rxq->alloc_size,
&rxq->headroom, &rxq->frag_count);
/* Create page pool for RX queue */
err = mana_create_page_pool(rxq, gc);
@@ -3928,6 +3943,7 @@ static void mana_qset_snapshot(const struct mana_port_context *ctx,
out->rx_queue_size = ctx->rx_queue_size;
out->tx_queue_size = ctx->tx_queue_size;
out->priv_flags = ctx->priv_flags;
+ out->mtu = ctx->configured_mtu;
out->mana_eqs_debugfs = ctx->mana_eqs_debugfs;
}
@@ -3949,6 +3965,7 @@ static void mana_qset_install(struct mana_port_context *ctx,
ctx->rx_queue_size = qset->rx_queue_size;
ctx->tx_queue_size = qset->tx_queue_size;
ctx->priv_flags = qset->priv_flags;
+ ctx->configured_mtu = qset->mtu;
ctx->mana_eqs_debugfs = qset->mana_eqs_debugfs;
}
@@ -4011,7 +4028,7 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
*/
int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
unsigned int rx_queue_size, unsigned int tx_queue_size,
- u32 priv_flags, struct mana_qset *out)
+ u32 priv_flags, int mtu, struct mana_qset *out)
{
struct net_device *ndev = scratch->ndev;
int err;
@@ -4023,6 +4040,12 @@ int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
scratch->tx_queue_size = tx_queue_size;
scratch->priv_flags = priv_flags;
+ /* mana_get_rxbuf_cfg() reads this when sizing RX buffers, so the
+ * new set is built for the requested MTU without disturbing the
+ * running set.
+ */
+ scratch->configured_mtu = mtu;
+
err = mana_init_port_context(scratch);
if (err)
goto out_err;
@@ -4237,6 +4260,11 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
if (err)
goto rollback;
+ /* The new set is serving traffic, so advertise its MTU. A no-op unless
+ * the caller is changing it.
+ */
+ WRITE_ONCE(ndev->mtu, apc->configured_mtu);
+
/* Pair with the queue-state stores above: a datapath reader that sees
* the gate open must also see the queue set it is about to index.
*/
@@ -4289,6 +4317,8 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
return err;
}
+ WRITE_ONCE(ndev->mtu, apc->configured_mtu);
+
/* Same pairing as the success path: the restored queue set has to be
* visible before the gate reopens on it.
*/
@@ -4508,6 +4538,7 @@ static int mana_probe_port(struct mana_context *ac, int port_idx,
apc->port_handle = INVALID_MANA_HANDLE;
apc->pf_filter_handle = INVALID_MANA_HANDLE;
apc->port_idx = port_idx;
+ apc->configured_mtu = ndev->mtu;
apc->link_cfg_error = 1;
apc->cqe_coalescing_enable = 0;
apc->cqe8_coalescing_enable = 0;
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index dc3a0a22959ef19e2ea9921e81c7295aef628ccf..f4818305bec83f8a60ce02baf280b0d4b9fcb51e 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -723,7 +723,8 @@ static int mana_set_channels(struct net_device *ndev,
}
err = mana_alloc_qset(scratch, new_count, apc->rx_queue_size,
- apc->tx_queue_size, apc->priv_flags, &newq);
+ apc->tx_queue_size, apc->priv_flags,
+ apc->configured_mtu, &newq);
if (err)
goto free_scratch; /* current qset untouched, nothing to undo */
@@ -818,7 +819,7 @@ static int mana_set_ringparam(struct net_device *ndev,
}
err = mana_alloc_qset(scratch, apc->num_queues, new_rx, new_tx,
- apc->priv_flags, &newq);
+ apc->priv_flags, apc->configured_mtu, &newq);
if (err) {
NL_SET_ERR_MSG_FMT(extack, "failed to change ring params: %d",
err);
@@ -916,7 +917,8 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
}
err = mana_alloc_qset(scratch, apc->num_queues, apc->rx_queue_size,
- apc->tx_queue_size, priv_flags, &newq);
+ apc->tx_queue_size, priv_flags,
+ apc->configured_mtu, &newq);
if (err)
goto free_scratch; /* current qset and priv_flags untouched */
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index 66a653bad22eba511d8cf8cf71b572c604044ecb..b9d79240dbc7cc0fb106336d68b6f0cf3a34f2bf 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -620,6 +620,11 @@ struct mana_port_context {
unsigned int rx_queue_size;
unsigned int tx_queue_size;
+ /* MTU the RX queues were built for. Equal to ndev->mtu except during a
+ * swap, when the new set is built before ndev->mtu is updated.
+ */
+ int configured_mtu;
+
mana_handle_t port_handle;
mana_handle_t pf_filter_handle;
@@ -710,6 +715,12 @@ struct mana_qset {
unsigned int tx_queue_size;
u32 priv_flags;
+ /* MTU the RX buffers of this set were sized for. It feeds
+ * mana_get_rxbuf_cfg(), so it is part of the queue-set
+ * configuration and must be swapped atomically with the queues.
+ */
+ int mtu;
+
/* Per-queue-set debugfs root ("EQs"). Owned by the qset: it is
* recreated by mana_create_eq() for each new set and torn down
* with that set, so it must travel with the qset rather than
@@ -735,7 +746,7 @@ struct mana_port_context *mana_qset_scratch_alloc(struct mana_port_context *apc)
void mana_qset_scratch_free(struct mana_port_context *scratch);
int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
unsigned int rx_queue_size, unsigned int tx_queue_size,
- u32 priv_flags, struct mana_qset *out);
+ u32 priv_flags, int mtu, struct mana_qset *out);
int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
struct mana_qset *out_old);
void mana_publish_close_if_needed(struct mana_port_context *apc);
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 06/13] net: mana: swap queue sets in mana_xdp_set
2026-08-13 5:04 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (4 preceding siblings ...)
2026-08-13 5:04 ` [PATCH net-next v2 05/13] net: mana: swap queue sets in mana_change_mtu Long Li
@ 2026-08-13 5:04 ` Long Li
2026-08-13 5:04 ` [PATCH net-next v2 07/13] net: mana: do not bail out of mana_detach on dealloc failure Long Li
` (6 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Long Li @ 2026-08-13 5:04 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
Attaching or detaching an XDP program changes the RX buffer layout -
full pages with headroom rather than page fragments - so it rebuilds the
queues. Convert mana_xdp_set() to pre-allocate and swap, completing the
removal of detach/attach from the reconfiguration paths.
The program becomes part of the queue-set configuration, so it is
swapped atomically with the queues it was built for and restored by the
rollback path. Program references follow the same swap, so attaching no
longer leaves the queues briefly running without one.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 68 ++++++++++---------
drivers/net/ethernet/microsoft/mana/mana_en.c | 14 ++--
.../ethernet/microsoft/mana/mana_ethtool.c | 7 +-
include/net/mana/mana.h | 16 +++--
4 files changed, 57 insertions(+), 48 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index e16ce2a0715839594a5837288c1d4c1de412e7fb..84b1484faaec8bf411ce402538862347e68954f0 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
@@ -175,10 +175,17 @@ void mana_chn_setxdp(struct mana_port_context *apc, struct bpf_prog *prog)
bpf_prog_put(old_prog);
}
+/* Attaching or detaching XDP changes the RX buffer layout (full pages vs
+ * fragments), so the RX queues are rebuilt. The swap helpers handle
+ * refcounting: mana_publish_qset() attaches the program to the new queues,
+ * mana_free_qset() drops the old set's references.
+ */
static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
struct netlink_ext_ack *extack)
{
struct mana_port_context *apc = netdev_priv(ndev);
+ struct mana_port_context *scratch;
+ struct mana_qset newq, oldq;
struct bpf_prog *old_prog;
struct gdma_context *gc;
int err;
@@ -198,46 +205,46 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
return -EOPNOTSUPP;
}
- /* One refcnt of the prog is hold by the caller already, so
- * don't increase refcnt for this one.
- */
- apc->bpf_prog = prog;
-
if (apc->port_is_up) {
- /* Re-create rxq's after xdp prog was loaded or unloaded.
- * Ex: re create rxq's to switch from full pages to smaller
- * size page fragments when xdp prog is unloaded and
- * vice-versa.
- */
-
- /* Pre-allocate buffers to prevent failure in mana_attach */
- err = mana_pre_alloc_rxbufs(apc, ndev->mtu, apc->num_queues);
- if (err) {
+ scratch = mana_qset_scratch_alloc(apc);
+ if (!scratch) {
NL_SET_ERR_MSG_MOD(extack,
- "XDP: Insufficient memory for tx/rx re-config");
- return err;
+ "XDP: Insufficient memory for re-config");
+ return -ENOMEM;
}
- err = mana_detach(ndev, false);
+ err = mana_alloc_qset(scratch, apc->num_queues,
+ apc->rx_queue_size, apc->tx_queue_size,
+ apc->priv_flags, apc->configured_mtu,
+ prog, &newq);
if (err) {
- netdev_err(ndev,
- "mana_detach failed at xdp set: %d\n", err);
NL_SET_ERR_MSG_MOD(extack,
- "XDP: Re-config failed at detach");
- goto err_dealloc_rxbuffs;
+ "XDP: Re-config failed at alloc");
+ mana_qset_scratch_free(scratch);
+ return err;
}
- err = mana_attach(ndev);
+ err = mana_publish_qset(apc, &newq, &oldq);
if (err) {
- netdev_err(ndev,
- "mana_attach failed at xdp set: %d\n", err);
NL_SET_ERR_MSG_MOD(extack,
- "XDP: Re-config failed at attach");
- goto err_dealloc_rxbuffs;
+ "XDP: Re-config failed at publish");
+ mana_free_qset(scratch, &newq);
+ /* After the cleanup above: closing destroys the EQ pool
+ * those queues' CQs were attached to.
+ */
+ mana_publish_close_if_needed(apc);
+ mana_qset_scratch_free(scratch);
+ return err;
}
- mana_chn_setxdp(apc, prog);
- mana_pre_dealloc_rxbufs(apc);
+ mana_free_qset(scratch, &oldq);
+ mana_qset_scratch_free(scratch);
+ } else {
+ /* No queues to rebuild; mana_open() will size the RX buffers
+ * for this program. One refcnt is held by the caller already,
+ * so don't take another.
+ */
+ apc->bpf_prog = prog;
}
if (old_prog)
@@ -250,11 +257,6 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
ndev->max_mtu = gc->adapter_mtu - ETH_HLEN;
return 0;
-
-err_dealloc_rxbuffs:
- apc->bpf_prog = old_prog;
- mana_pre_dealloc_rxbufs(apc);
- return err;
}
int mana_bpf(struct net_device *ndev, struct netdev_bpf *bpf)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index c858a58577dfb1774b2b9394c01e9000edbe8bf9..e54ad4db918ce3d662092a94ac13c41a4b958d6a 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -909,7 +909,7 @@ static int mana_change_mtu(struct net_device *ndev, int new_mtu)
err = mana_alloc_qset(scratch, mpc->num_queues, mpc->rx_queue_size,
mpc->tx_queue_size, mpc->priv_flags, new_mtu,
- &newq);
+ mpc->bpf_prog, &newq);
if (err)
goto free_scratch; /* current qset and ndev->mtu untouched */
@@ -3944,6 +3944,7 @@ static void mana_qset_snapshot(const struct mana_port_context *ctx,
out->tx_queue_size = ctx->tx_queue_size;
out->priv_flags = ctx->priv_flags;
out->mtu = ctx->configured_mtu;
+ out->bpf_prog = ctx->bpf_prog;
out->mana_eqs_debugfs = ctx->mana_eqs_debugfs;
}
@@ -3966,6 +3967,7 @@ static void mana_qset_install(struct mana_port_context *ctx,
ctx->tx_queue_size = qset->tx_queue_size;
ctx->priv_flags = qset->priv_flags;
ctx->configured_mtu = qset->mtu;
+ ctx->bpf_prog = qset->bpf_prog;
ctx->mana_eqs_debugfs = qset->mana_eqs_debugfs;
}
@@ -4028,7 +4030,8 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
*/
int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
unsigned int rx_queue_size, unsigned int tx_queue_size,
- u32 priv_flags, int mtu, struct mana_qset *out)
+ u32 priv_flags, int mtu, struct bpf_prog *bpf_prog,
+ struct mana_qset *out)
{
struct net_device *ndev = scratch->ndev;
int err;
@@ -4040,11 +4043,12 @@ int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
scratch->tx_queue_size = tx_queue_size;
scratch->priv_flags = priv_flags;
- /* mana_get_rxbuf_cfg() reads this when sizing RX buffers, so the
- * new set is built for the requested MTU without disturbing the
- * running set.
+ /* mana_get_rxbuf_cfg() reads both of these when sizing RX buffers,
+ * so the new set is built for the requested MTU / XDP program
+ * without disturbing the running set.
*/
scratch->configured_mtu = mtu;
+ scratch->bpf_prog = bpf_prog;
err = mana_init_port_context(scratch);
if (err)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index f4818305bec83f8a60ce02baf280b0d4b9fcb51e..0daacee28001ed45a66ef00449b2c2acbf2859d4 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -724,7 +724,7 @@ static int mana_set_channels(struct net_device *ndev,
err = mana_alloc_qset(scratch, new_count, apc->rx_queue_size,
apc->tx_queue_size, apc->priv_flags,
- apc->configured_mtu, &newq);
+ apc->configured_mtu, apc->bpf_prog, &newq);
if (err)
goto free_scratch; /* current qset untouched, nothing to undo */
@@ -819,7 +819,8 @@ static int mana_set_ringparam(struct net_device *ndev,
}
err = mana_alloc_qset(scratch, apc->num_queues, new_rx, new_tx,
- apc->priv_flags, apc->configured_mtu, &newq);
+ apc->priv_flags, apc->configured_mtu,
+ apc->bpf_prog, &newq);
if (err) {
NL_SET_ERR_MSG_FMT(extack, "failed to change ring params: %d",
err);
@@ -918,7 +919,7 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
err = mana_alloc_qset(scratch, apc->num_queues, apc->rx_queue_size,
apc->tx_queue_size, priv_flags,
- apc->configured_mtu, &newq);
+ apc->configured_mtu, apc->bpf_prog, &newq);
if (err)
goto free_scratch; /* current qset and priv_flags untouched */
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index b9d79240dbc7cc0fb106336d68b6f0cf3a34f2bf..4fcd5e307a4805b81c5c82482fc65a4ba68eea89 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -589,7 +589,7 @@ struct mana_port_context {
/* Indirection Table for RX & TX. The values are queue indexes */
u32 *indir_table;
- u32 indir_table_sz;
+ u32 indir_table_sz;
/* Indirection table containing RxObject Handles */
mana_handle_t *rxobj_table;
@@ -715,11 +715,12 @@ struct mana_qset {
unsigned int tx_queue_size;
u32 priv_flags;
- /* MTU the RX buffers of this set were sized for. It feeds
- * mana_get_rxbuf_cfg(), so it is part of the queue-set
+ /* MTU and XDP program the RX buffers of this set were sized for.
+ * Both feed mana_get_rxbuf_cfg(), so they are part of the queue-set
* configuration and must be swapped atomically with the queues.
*/
int mtu;
+ struct bpf_prog *bpf_prog;
/* Per-queue-set debugfs root ("EQs"). Owned by the qset: it is
* recreated by mana_create_eq() for each new set and torn down
@@ -738,15 +739,16 @@ int mana_alloc_queues(struct net_device *ndev);
int mana_attach(struct net_device *ndev);
int mana_detach(struct net_device *ndev, bool from_close);
-/* Pre-allocate + swap reconfiguration. Allocation and teardown run against a
- * scratch context, so the live port context is mutated only inside
- * mana_publish_qset() with TX disabled. Both sets share a port-owned EQ pool.
+/* Pre-allocate + swap reconfiguration path (prototype). Allocation and
+ * teardown run against a scratch context so the live port context is only
+ * mutated inside mana_publish_qset(), with TX disabled.
*/
struct mana_port_context *mana_qset_scratch_alloc(struct mana_port_context *apc);
void mana_qset_scratch_free(struct mana_port_context *scratch);
int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
unsigned int rx_queue_size, unsigned int tx_queue_size,
- u32 priv_flags, int mtu, struct mana_qset *out);
+ u32 priv_flags, int mtu, struct bpf_prog *bpf_prog,
+ struct mana_qset *out);
int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
struct mana_qset *out_old);
void mana_publish_close_if_needed(struct mana_port_context *apc);
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 07/13] net: mana: do not bail out of mana_detach on dealloc failure
2026-08-13 5:04 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (5 preceding siblings ...)
2026-08-13 5:04 ` [PATCH net-next v2 06/13] net: mana: swap queue sets in mana_xdp_set Long Li
@ 2026-08-13 5:04 ` Long Li
2026-08-13 5:04 ` [PATCH net-next v2 08/13] net: mana: keep per-queue statistics in the port context Long Li
` (5 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Long Li @ 2026-08-13 5:04 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel, Dipayaan Roy
From: Dipayaan Roy <dipayanroy@linux.microsoft.com>
mana_detach() sets port_is_up = false before calling
mana_dealloc_queues(). If that call were to fail and return early,
netif_device_detach() and mana_cleanup_port_context() are skipped,
leaving the port in an inconsistent state where port_is_up is false but
netif_device_present() still returns true.
With the ethtool, MTU and XDP paths converted to pre-allocate and swap,
the callers that reach this code with from_close == false are the TX
timeout reset handler and the suspend path. For the reset handler the
inconsistent state is fatal: its mana_detach() would overwrite
port_st_save with false, so the following mana_attach() skips queue
allocation and leaves the port permanently dead. For suspend it leaves
the port context allocated across the transition.
Remove the early return so that mana_detach() always completes its full
teardown. mana_dealloc_queues() already performs best-effort cleanup
regardless of internal errors, and in practice cannot fail here since
port_is_up is already false, so continuing to netif_device_detach() and
mana_cleanup_port_context() is safe and keeps the state consistent.
Signed-off-by: Dipayaan Roy <dipayanroy@linux.microsoft.com>
Signed-off-by: Long Li <longli@microsoft.com>
---
drivers/net/ethernet/microsoft/mana/mana_en.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index e54ad4db918ce3d662092a94ac13c41a4b958d6a..2b3250483b43954c74d8cbf6a9e6e77880eac8ca 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -4502,10 +4502,8 @@ int mana_detach(struct net_device *ndev, bool from_close)
if (apc->port_st_save) {
err = mana_dealloc_queues(ndev);
- if (err) {
+ if (err)
netdev_err(ndev, "%s failed to deallocate queues: %d\n", __func__, err);
- return err;
- }
}
if (!from_close) {
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 08/13] net: mana: keep per-queue statistics in the port context
2026-08-13 5:04 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (6 preceding siblings ...)
2026-08-13 5:04 ` [PATCH net-next v2 07/13] net: mana: do not bail out of mana_detach on dealloc failure Long Li
@ 2026-08-13 5:04 ` Long Li
2026-08-13 5:04 ` [PATCH net-next v2 09/13] net: mana: share the EQ pool across a queue-set swap Long Li
` (4 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Long Li @ 2026-08-13 5:04 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
struct mana_rxq and struct mana_txq embed their statistics by value, so
every reconfiguration - ethtool channel count, ring size and private
flags, MTU changes, XDP attach - destroys and recreates them, resetting
the interface counters. rx_bytes can be observed going backwards:
rx_bytes before: 4475831638
rx_bytes after: 526629152
Move them into port-context arrays sized to max_queues and allocated for
the lifetime of the port, so a queue set can be freed without losing what
it accumulated. ndo_get_stats64() walks max_queues, so counters from
queues a later reconfiguration removed are still reported and the totals
stay monotonic. The ethtool per-queue statistics keep iterating the
current count, which is what sizes their string table.
Counters reset on detach/attach the same way before this, and are kept
across it now too; it matters more as reconfiguration is hitless and so
typically done on a live link.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 4 +-
drivers/net/ethernet/microsoft/mana/mana_en.c | 93 +++++++++++++++----
.../ethernet/microsoft/mana/mana_ethtool.c | 4 +-
include/net/mana/mana.h | 28 ++++--
4 files changed, 100 insertions(+), 29 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index 84b1484faaec8bf411ce402538862347e68954f0..ed9ec6b8af480a303bfa9c0dd9894f5049d8b6d6 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
@@ -75,7 +75,7 @@ int mana_xdp_xmit(struct net_device *ndev, int n, struct xdp_frame **frames,
count++;
}
- tx_stats = &apc->tx_qp[q_idx]->txq.stats;
+ tx_stats = apc->tx_qp[q_idx]->txq.stats;
u64_stats_update_begin(&tx_stats->syncp);
tx_stats->xdp_xmit += count;
@@ -102,7 +102,7 @@ u32 mana_run_xdp(struct net_device *ndev, struct mana_rxq *rxq,
act = bpf_prog_run_xdp(prog, xdp);
- rx_stats = &rxq->stats;
+ rx_stats = rxq->stats;
switch (act) {
case XDP_PASS:
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 2b3250483b43954c74d8cbf6a9e6e77880eac8ca..27484e5cf71a2216e7dc89c21ee0e9d95786cf30 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -381,7 +381,7 @@ netdev_tx_t mana_start_xmit(struct sk_buff *skb, struct net_device *ndev)
txq = &apc->tx_qp[txq_idx]->txq;
gdma_sq = txq->gdma_sq;
cq = &apc->tx_qp[txq_idx]->tx_cq;
- tx_stats = &txq->stats;
+ tx_stats = txq->stats;
BUILD_BUG_ON(MAX_TX_WQE_SGL_ENTRIES != MANA_MAX_TX_WQE_SGL_ENTRIES);
if (MAX_SKB_FRAGS + 2 > MAX_TX_WQE_SGL_ENTRIES &&
@@ -560,7 +560,7 @@ netdev_tx_t mana_start_xmit(struct sk_buff *skb, struct net_device *ndev)
/* Populated the packet and bytes counters based on post GSO packet
* calculations
*/
- tx_stats = &txq->stats;
+ tx_stats = txq->stats;
u64_stats_update_begin(&tx_stats->syncp);
tx_stats->packets += num_gso_seg;
tx_stats->bytes += len + ((num_gso_seg - 1) * gso_hs);
@@ -606,9 +606,9 @@ static void mana_get_stats64(struct net_device *ndev,
struct rtnl_link_stats64 *st)
{
struct mana_port_context *apc = netdev_priv(ndev);
- unsigned int num_queues = apc->num_queues;
struct mana_stats_rx *rx_stats;
struct mana_stats_tx *tx_stats;
+ unsigned int num_queues;
unsigned int start;
u64 packets, bytes;
int q;
@@ -616,6 +616,12 @@ static void mana_get_stats64(struct net_device *ndev,
if (!apc->port_is_up)
return;
+ /* Walk every slot, not just the queues currently open: counters
+ * accumulated on queues that a later reconfiguration removed must
+ * still be reported, or the interface totals would go backwards.
+ */
+ num_queues = apc->max_queues;
+
netdev_stats_to_stats64(st, &ndev->stats);
if (apc->ac->hwc_timeout_occurred)
@@ -624,7 +630,7 @@ static void mana_get_stats64(struct net_device *ndev,
st->rx_missed_errors = apc->ac->hc_stats.hc_rx_discards_no_wqe;
for (q = 0; q < num_queues; q++) {
- rx_stats = &apc->rxqs[q]->stats;
+ rx_stats = &apc->rxq_stats[q];
do {
start = u64_stats_fetch_begin(&rx_stats->syncp);
@@ -637,7 +643,7 @@ static void mana_get_stats64(struct net_device *ndev,
}
for (q = 0; q < num_queues; q++) {
- tx_stats = &apc->tx_qp[q]->txq.stats;
+ tx_stats = &apc->txq_stats[q];
do {
start = u64_stats_fetch_begin(&tx_stats->syncp);
@@ -1059,6 +1065,48 @@ static void mana_cleanup_port_context(struct mana_port_context *apc)
apc->rxqs = NULL;
}
+/* Counters belong to the port, not the queues, so a queue-set replacement
+ * does not reset them. Sized to max_queues, allocated once.
+ *
+ * A swap adds no writer to a TX slot. RX slots do overlap briefly, since a
+ * retiring rxq keeps its NAPI until mana_free_qset() destroys it. MANA is
+ * 64-bit only, so u64_stats_sync has no seqcount and at worst a few
+ * increments are lost; the alternatives are a lock in the receive path or
+ * per-set slots that make ndo_get_stats64() dip during a swap.
+ */
+static int mana_alloc_queue_stats(struct mana_port_context *apc)
+{
+ unsigned int i;
+
+ apc->rxq_stats = kcalloc(apc->max_queues, sizeof(*apc->rxq_stats),
+ GFP_KERNEL);
+ if (!apc->rxq_stats)
+ return -ENOMEM;
+
+ apc->txq_stats = kcalloc(apc->max_queues, sizeof(*apc->txq_stats),
+ GFP_KERNEL);
+ if (!apc->txq_stats) {
+ kfree(apc->rxq_stats);
+ apc->rxq_stats = NULL;
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < apc->max_queues; i++) {
+ u64_stats_init(&apc->rxq_stats[i].syncp);
+ u64_stats_init(&apc->txq_stats[i].syncp);
+ }
+
+ return 0;
+}
+
+static void mana_free_queue_stats(struct mana_port_context *apc)
+{
+ kfree(apc->rxq_stats);
+ apc->rxq_stats = NULL;
+ kfree(apc->txq_stats);
+ apc->txq_stats = NULL;
+}
+
static void mana_cleanup_indir_table(struct mana_port_context *apc)
{
apc->indir_table_sz = 0;
@@ -2114,7 +2162,7 @@ static void mana_rx_skb(void *buf_va, bool from_pool,
struct mana_rxcomp_oob *cqe, struct mana_rxq *rxq,
u32 pkt_len, u32 pkt_hash)
{
- struct mana_stats_rx *rx_stats = &rxq->stats;
+ struct mana_stats_rx *rx_stats = rxq->stats;
struct net_device *ndev = rxq->ndev;
u16 rxq_idx = rxq->rxq_idx;
struct napi_struct *napi;
@@ -2428,13 +2476,13 @@ static void mana_process_rx_cqe(struct mana_rxq *rxq, struct mana_cq *cq,
* Coalesced CQEs have at least 2 packets, so index is pkt_i - 2.
*/
if (pkt_i > 1) {
- u64_stats_update_begin(&rxq->stats.syncp);
- rxq->stats.coalesced_cqe[pkt_i - 2]++;
- u64_stats_update_end(&rxq->stats.syncp);
+ u64_stats_update_begin(&rxq->stats->syncp);
+ rxq->stats->coalesced_cqe[pkt_i - 2]++;
+ u64_stats_update_end(&rxq->stats->syncp);
} else if (!pkt_i && !pktlen) {
- u64_stats_update_begin(&rxq->stats.syncp);
- rxq->stats.pkt_len0_err++;
- u64_stats_update_end(&rxq->stats.syncp);
+ u64_stats_update_begin(&rxq->stats->syncp);
+ rxq->stats->pkt_len0_err++;
+ u64_stats_update_end(&rxq->stats->syncp);
netdev_err_once(ndev,
"RX pkt len=0, rq=%u, cq=%u, rxobj=0x%llx\n",
rxq->gdma_id, cq->gdma_id, rxq->rxobj);
@@ -2566,8 +2614,8 @@ static void mana_update_rx_dim(struct mana_cq *cq)
if (!smp_load_acquire(&apc->rx_dim_enabled))
return;
- dim_update_sample(READ_ONCE(cq->dim_event_ctr), rxq->stats.packets,
- rxq->stats.bytes, &dim_sample);
+ dim_update_sample(READ_ONCE(cq->dim_event_ctr), rxq->stats->packets,
+ rxq->stats->bytes, &dim_sample);
net_dim(&cq->dim, &dim_sample);
}
@@ -2784,7 +2832,7 @@ static int mana_create_txq(struct mana_port_context *apc,
/* Create SQ */
txq = &apc->tx_qp[i]->txq;
- u64_stats_init(&txq->stats.syncp);
+ txq->stats = &apc->txq_stats[i];
txq->ndev = net;
txq->net_txq = netdev_get_tx_queue(net, i);
txq->reset_gen = READ_ONCE(apc->ac->reset_gen);
@@ -3099,6 +3147,8 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc,
return ERR_PTR(-ENOMEM);
rxq->ndev = ndev;
+ /* Wire up the port-owned statistics before the queue can be polled. */
+ rxq->stats = &apc->rxq_stats[rxq_idx];
rxq->num_rx_buf = apc->rx_queue_size;
rxq->rxq_idx = rxq_idx;
rxq->rxobj = INVALID_MANA_HANDLE;
@@ -3249,7 +3299,6 @@ static int mana_add_rx_queues(struct mana_port_context *apc,
goto out;
}
- u64_stats_init(&rxq->stats.syncp);
apc->rxqs[i] = rxq;
@@ -4555,6 +4604,10 @@ static int mana_probe_port(struct mana_context *ac, int port_idx,
apc->tx_dim_enabled = MANA_ADAPTIVE_TX_DEF;
}
+ err = mana_alloc_queue_stats(apc);
+ if (err)
+ goto free_net;
+
mutex_init(&apc->vport_mutex);
apc->vport_use_count = 0;
@@ -4577,7 +4630,7 @@ static int mana_probe_port(struct mana_context *ac, int port_idx,
err = mana_init_port(ndev);
if (err)
- goto free_net;
+ goto free_stats;
err = mana_rss_table_alloc(apc);
if (err)
@@ -4614,6 +4667,11 @@ static int mana_probe_port(struct mana_context *ac, int port_idx,
mana_cleanup_indir_table(apc);
reset_apc:
mana_cleanup_port_context(apc);
+free_stats:
+ /* The counter arrays are separate allocations, so free_netdev() does
+ * not release them with the port context.
+ */
+ mana_free_queue_stats(apc);
free_net:
*ndev_storage = NULL;
netdev_err(ndev, "Failed to probe vPort %d: %d\n", port_idx, err);
@@ -4954,6 +5012,7 @@ void mana_remove(struct gdma_dev *gd, bool suspending)
unregister_netdevice(ndev);
mana_cleanup_indir_table(apc);
+ mana_free_queue_stats(apc);
/* Clear the slot before the netdev goes away. A later port
* whose teardown has to reset the function walks ac->ports[]
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 0daacee28001ed45a66ef00449b2c2acbf2859d4..26f5ea1f5091a7fab729ecf3468bdc13886fcccb 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -271,7 +271,7 @@ static void mana_get_ethtool_stats(struct net_device *ndev,
data[i++] = *(u64 *)(phy_stats + mana_phy_stats[q].offset);
for (q = 0; q < num_queues; q++) {
- rx_stats = &apc->rxqs[q]->stats;
+ rx_stats = &apc->rxq_stats[q];
do {
start = u64_stats_fetch_begin(&rx_stats->syncp);
@@ -296,7 +296,7 @@ static void mana_get_ethtool_stats(struct net_device *ndev,
}
for (q = 0; q < num_queues; q++) {
- tx_stats = &apc->tx_qp[q]->txq.stats;
+ tx_stats = &apc->txq_stats[q];
do {
start = u64_stats_fetch_begin(&tx_stats->syncp);
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index 4fcd5e307a4805b81c5c82482fc65a4ba68eea89..77cec5b44049202e21f919d4336fdf8ebc248a18 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -102,7 +102,10 @@ struct mana_stats_rx {
u64 pkt_len0_err;
u64 coalesced_cqe[MANA_CQE_COAL_PKTS_8 - 1];
struct u64_stats_sync syncp;
-};
+ /* Per-port array indexed by queue, so keep entries on separate cache
+ * lines: queues polled on different CPUs would bounce a shared one.
+ */
+} ____cacheline_aligned_in_smp;
struct mana_stats_tx {
u64 packets;
@@ -117,7 +120,8 @@ struct mana_stats_tx {
u64 csum_partial;
u64 mana_map_err;
struct u64_stats_sync syncp;
-};
+ /* Per-queue array entry, same cache line reasoning as the RX side. */
+} ____cacheline_aligned_in_smp;
struct mana_txq {
struct gdma_queue *gdma_sq;
@@ -146,14 +150,14 @@ struct mana_txq {
/* Value of mana_context.reset_gen when this queue was created. */
u32 reset_gen;
- /* Set once this queue has been unpublished and is on its way out.
- * Its completions must not touch flow control any more: net_txq is
- * shared with the queue that replaced it at the same index, and a
- * draining queue always looks like it has room.
+ /* Unpublished and draining. Its completions must leave flow control
+ * alone: net_txq is shared with its replacement, and a draining queue
+ * always looks like it has room.
*/
bool retiring;
- struct mana_stats_tx stats;
+ /* Points into apc->txq_stats[], which outlives the queue. */
+ struct mana_stats_tx *stats;
};
/* skb data and frags dma mappings */
@@ -415,7 +419,8 @@ struct mana_rxq {
u32 buf_index;
- struct mana_stats_rx stats;
+ /* Points into apc->rxq_stats[], which outlives the queue. */
+ struct mana_stats_rx *stats;
struct bpf_prog __rcu *bpf_prog;
struct xdp_rxq_info xdp_rxq;
@@ -617,6 +622,13 @@ struct mana_port_context {
unsigned int max_queues;
unsigned int num_queues;
+ /* Per-queue counters, max_queues entries each. Allocated at probe and
+ * freed at remove, never on queue teardown, so a reconfiguration does
+ * not reset them.
+ */
+ struct mana_stats_rx *rxq_stats;
+ struct mana_stats_tx *txq_stats;
+
unsigned int rx_queue_size;
unsigned int tx_queue_size;
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 09/13] net: mana: share the EQ pool across a queue-set swap
2026-08-13 5:04 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (7 preceding siblings ...)
2026-08-13 5:04 ` [PATCH net-next v2 08/13] net: mana: keep per-queue statistics in the port context Long Li
@ 2026-08-13 5:04 ` Long Li
2026-08-13 5:04 ` [PATCH net-next v2 10/13] net: mana: release EQs left idle by a channel-count reduction Long Li
` (3 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Long Li @ 2026-08-13 5:04 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
Raising the channel count fails with -ENOSPC once the queue-set swap is
in use:
# ethtool -L ens1 combined 32
netlink error: No space left on device
mana 7870:00:00.0: No free MSI vectors available
Building a second set of EQs while the running set still held its own
meant peak demand of old + new MSI-X vectors. With 32 usable vectors and
a driver that comes up at 16 queues, 16 -> 17 already needs 33, so the
advertised maximum is unreachable.
Make the EQ pool belong to the port rather than to a queue set, so both
sets share it and peak usage is max(old, new) rather than the sum. The
pool only ever grows, up to the maximum channel count ethtool reports,
and is released on detach as before.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 2 +-
drivers/net/ethernet/microsoft/mana/mana_en.c | 173 +++++++++++-------
.../ethernet/microsoft/mana/mana_ethtool.c | 6 +-
include/net/mana/mana.h | 34 ++--
4 files changed, 120 insertions(+), 95 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index ed9ec6b8af480a303bfa9c0dd9894f5049d8b6d6..05936453fbbfa59c563fdea50e0b40096c2b46ac 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
@@ -213,7 +213,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
return -ENOMEM;
}
- err = mana_alloc_qset(scratch, apc->num_queues,
+ err = mana_alloc_qset(apc, scratch, apc->num_queues,
apc->rx_queue_size, apc->tx_queue_size,
apc->priv_flags, apc->configured_mtu,
prog, &newq);
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 27484e5cf71a2216e7dc89c21ee0e9d95786cf30..be7f9f6626e42c33fc0e749e6897ecc8117222cf 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -913,7 +913,7 @@ static int mana_change_mtu(struct net_device *ndev, int new_mtu)
if (!scratch)
return -ENOMEM;
- err = mana_alloc_qset(scratch, mpc->num_queues, mpc->rx_queue_size,
+ err = mana_alloc_qset(mpc, scratch, mpc->num_queues, mpc->rx_queue_size,
mpc->tx_queue_size, mpc->priv_flags, new_mtu,
mpc->bpf_prog, &newq);
if (err)
@@ -1809,7 +1809,7 @@ void mana_destroy_eq(struct mana_port_context *apc)
debugfs_remove_recursive(apc->mana_eqs_debugfs);
apc->mana_eqs_debugfs = NULL;
- for (i = 0; i < apc->num_queues; i++) {
+ for (i = 0; i < apc->num_eqs; i++) {
eq = apc->eqs[i].eq;
if (!eq)
continue;
@@ -1821,6 +1821,7 @@ void mana_destroy_eq(struct mana_port_context *apc)
kfree(apc->eqs);
apc->eqs = NULL;
+ apc->num_eqs = 0;
}
EXPORT_SYMBOL_NS(mana_destroy_eq, "NET_MANA");
@@ -1849,9 +1850,14 @@ int mana_create_eq(struct mana_port_context *apc)
if (WARN_ON(apc->eqs))
return -EEXIST;
- apc->eqs = kzalloc_objs(struct mana_eq, apc->num_queues);
+ /* Size the array to the largest queue count this port can ever use,
+ * so growing it later never has to reallocate (the CQs of a live
+ * queue set hold pointers taken from these slots).
+ */
+ apc->eqs = kzalloc_objs(struct mana_eq, apc->max_queues);
if (!apc->eqs)
return -ENOMEM;
+ apc->num_eqs = 0;
spec.type = GDMA_EQ;
spec.monitor_avl_buf = false;
@@ -1881,6 +1887,7 @@ int mana_create_eq(struct mana_port_context *apc)
}
apc->eqs[i].eq->eq.irq = gic->irq;
mana_create_eq_debugfs(apc, i);
+ apc->num_eqs = i + 1;
}
return 0;
@@ -1890,6 +1897,70 @@ int mana_create_eq(struct mana_port_context *apc)
}
EXPORT_SYMBOL_NS(mana_create_eq, "NET_MANA");
+/**
+ * mana_grow_eqs - make sure the port has at least @need EQs
+ * @apc: port context
+ * @need: number of EQs the new queue set requires
+ *
+ * EQs are bound to MSI-X vectors, so the pool is port-owned and shared across
+ * a swap: peak usage is max(old, new), not the sum. Grow-only, up to
+ * apc->max_queues.
+ */
+static int mana_grow_eqs(struct mana_port_context *apc, unsigned int need)
+{
+ struct gdma_dev *gd = apc->ac->gdma_dev;
+ struct gdma_context *gc = gd->gdma_context;
+ struct gdma_queue_spec spec = {};
+ struct gdma_irq_context *gic;
+ unsigned int i;
+ int err;
+ int msi;
+
+ if (WARN_ON(!apc->eqs))
+ return -EINVAL;
+
+ if (need > apc->max_queues)
+ return -EINVAL;
+
+ if (need <= apc->num_eqs)
+ return 0;
+
+ spec.type = GDMA_EQ;
+ spec.monitor_avl_buf = false;
+ spec.queue_size = EQ_SIZE;
+ spec.eq.callback = NULL;
+ spec.eq.context = apc->eqs;
+ spec.eq.log2_throttle_limit = LOG2_EQ_THROTTLE;
+
+ for (i = apc->num_eqs; i < need; i++) {
+ msi = (i + 1) % gc->num_msix_usable;
+
+ gic = mana_gd_get_gic(gc, !gc->msi_sharing, &msi);
+ if (IS_ERR(gic)) {
+ err = PTR_ERR(gic);
+ goto out;
+ }
+ spec.eq.msix_index = msi;
+
+ err = mana_gd_create_mana_eq(gd, &spec, &apc->eqs[i].eq);
+ if (err) {
+ dev_err(gc->dev, "Failed to grow EQ %u : %d\n", i, err);
+ mana_gd_put_gic(gc, !gc->msi_sharing, msi);
+ goto out;
+ }
+ apc->eqs[i].eq->eq.irq = gic->irq;
+ mana_create_eq_debugfs(apc, i);
+ apc->num_eqs = i + 1;
+ }
+
+ return 0;
+out:
+ /* Keep whatever was created: the running queue set still needs its
+ * own EQs, and the extras are reused by the next attempt.
+ */
+ return err;
+}
+
static int mana_fence_rq(struct mana_port_context *apc, struct mana_rxq *rxq)
{
struct mana_fence_rq_resp resp = {};
@@ -3936,52 +4007,23 @@ static int mana_dealloc_queues(struct net_device *ndev)
}
/*
- * ---------------------------------------------------------------------------
- * Pre-allocate + swap reconfiguration path.
- *
- * The detach/attach reconfigure path tears the vport down and rebuilds it,
- * which lets RDMA grab the vport mid-flight and, if attach fails, leaves the
- * port permanently broken.
- *
- * The swap path builds a *new* set of EQs/TXQs/RXQs while the current set
- * keeps serving traffic. If allocation fails the current qset is untouched
- * and we return the error; the user's requested value is never silently
- * replaced by a fallback. Once the new qset is ready we publish it onto apc
- * and destroy the old one. The vport is never torn down: vport_use_count
- * stays at 1 throughout, so RDMA cannot hijack it.
+ * Pre-allocate and swap reconfiguration.
*
- * The cost of never dropping the working queues is that a rebuild needs room
- * for both sets at once, so one at the vport's maximum queue count can be
- * refused by the firmware where a teardown-first sequence would have fit.
- * That surfaces as a failed ethtool operation with the port still running on
- * its previous queues, which is the trade this path exists to make.
+ * Build a new queue set while the current one serves traffic, publish it, then
+ * destroy the old one. A failed allocation leaves the running config untouched,
+ * and the vport is never torn down, so RDMA cannot take it mid-swap. The cost
+ * is room for both sets at once, so a rebuild at the vport's maximum queue
+ * count can be refused; EQs are shared from a port-owned pool, not doubled.
*
- * Allocation and teardown run against a *scratch* mana_port_context rather
- * than the live one. This is essential, not cosmetic: an earlier revision
- * temporarily NULLed apc->tx_qp so the allocators could
- * build into the live context, which reliably panicked in mana_start_xmit()
- * under traffic (it dereferences apc->tx_qp[] guarded only by port_is_up).
- * The live apc is now mutated only inside mana_publish_qset(), with TX
- * disabled.
- *
- * Note that both sets are live between publish and free, so this peaks at
- * old+new queues, and therefore at old+new MSI-X vectors. A later patch
- * gives the port a shared EQ pool so only the queues, not the interrupts,
- * are doubled up.
- *
- * Per-queue debugfs is suppressed for a set while it is being built or torn
- * down (see mana_qset_scratch_alloc()): the directory names are derived from
- * the queue index, so the incoming set would collide with the outgoing one
- * under vport%d. Restoring it needs per-set subdirectories or a
- * debugfs_rename() once the swap has completed.
- * ---------------------------------------------------------------------------
+ * Everything builds in a scratch mana_port_context, since mana_start_xmit()
+ * dereferences apc->tx_qp[] guarded only by port_is_up. Per-queue debugfs is
+ * suppressed meanwhile, as the names would collide.
*/
/* Snapshot the queue-set fields of @ctx into @out. */
static void mana_qset_snapshot(const struct mana_port_context *ctx,
struct mana_qset *out)
{
- out->eqs = ctx->eqs;
out->tx_qp = ctx->tx_qp;
out->rxqs = ctx->rxqs;
out->indir_table = ctx->indir_table;
@@ -3994,7 +4036,6 @@ static void mana_qset_snapshot(const struct mana_port_context *ctx,
out->priv_flags = ctx->priv_flags;
out->mtu = ctx->configured_mtu;
out->bpf_prog = ctx->bpf_prog;
- out->mana_eqs_debugfs = ctx->mana_eqs_debugfs;
}
/* Install @qset's fields onto @ctx. The vport (port_handle,
@@ -4004,7 +4045,6 @@ static void mana_qset_snapshot(const struct mana_port_context *ctx,
static void mana_qset_install(struct mana_port_context *ctx,
const struct mana_qset *qset)
{
- ctx->eqs = qset->eqs;
ctx->tx_qp = qset->tx_qp;
ctx->rxqs = qset->rxqs;
ctx->indir_table = qset->indir_table;
@@ -4017,7 +4057,6 @@ static void mana_qset_install(struct mana_port_context *ctx,
ctx->priv_flags = qset->priv_flags;
ctx->configured_mtu = qset->mtu;
ctx->bpf_prog = qset->bpf_prog;
- ctx->mana_eqs_debugfs = qset->mana_eqs_debugfs;
}
/**
@@ -4038,29 +4077,24 @@ struct mana_port_context *mana_qset_scratch_alloc(struct mana_port_context *apc)
*scratch = *apc;
- /* Owns no queues yet. */
- scratch->eqs = NULL;
+ /* EQs stay shared with the live port: they are a vector-backed
+ * resource and must not be duplicated for the new set.
+ */
scratch->tx_qp = NULL;
scratch->rxqs = NULL;
scratch->indir_table = NULL;
scratch->rxobj_table = NULL;
scratch->default_rxobj = INVALID_MANA_HANDLE;
- scratch->mana_eqs_debugfs = NULL;
- /* Never consume the live set's pre-allocated RX buffers;
- * mana_get_rxbuf() falls back to normal allocation when these
- * are NULL, which is what we want since the swap path no longer
- * needs to de-risk post-teardown allocation.
+ /* Never consume the live set's pre-allocated RX buffers; the swap path
+ * has no post-teardown allocation to de-risk.
*/
scratch->rxbufs_pre = NULL;
scratch->das_pre = NULL;
scratch->rxbpre_total = 0;
- /* Suppress debugfs for queues built through the scratch context:
- * two sets are alive at once and would collide on the same names
- * under vport%d. debugfs_start_creating() returns early on an
- * IS_ERR() parent, and debugfs_remove() ignores IS_ERR_OR_NULL,
- * so this makes every create/remove a clean no-op.
+ /* Two sets are alive at once and would collide on the same names under
+ * vport%d. An IS_ERR() parent makes every create and remove a no-op.
*/
scratch->mana_port_debugfs = ERR_PTR(-ENODEV);
@@ -4077,7 +4111,8 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
* mana_grow_qset(), so this never has to add an EQ. The installed set keeps
* serving traffic meanwhile. On error nothing is left allocated.
*/
-int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
+int mana_alloc_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, unsigned int num_queues,
unsigned int rx_queue_size, unsigned int tx_queue_size,
u32 priv_flags, int mtu, struct bpf_prog *bpf_prog,
struct mana_qset *out)
@@ -4107,13 +4142,20 @@ int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
if (err)
goto cleanup_rxq_array;
- err = mana_create_eq(scratch);
+ /* Grow the port's shared EQ pool if this set needs more. The pool
+ * belongs to @apc, not to either queue set, so both sets can be
+ * live at once without double-booking MSI-X vectors.
+ */
+ err = mana_grow_eqs(apc, num_queues);
if (err)
goto cleanup_rss;
+ scratch->eqs = apc->eqs;
+ scratch->num_eqs = apc->num_eqs;
+
err = mana_create_txq(scratch, ndev);
if (err)
- goto cleanup_eq;
+ goto cleanup_rss;
err = mana_add_rx_queues(scratch, ndev);
if (err)
@@ -4131,8 +4173,6 @@ int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
*/
mana_destroy_rxqs(scratch);
mana_destroy_txq(scratch);
-cleanup_eq:
- mana_destroy_eq(scratch);
cleanup_rss:
mana_cleanup_indir_table(scratch);
cleanup_rxq_array:
@@ -4423,16 +4463,10 @@ void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
ASSERT_RTNL();
- if (!qset->rxqs && !qset->tx_qp && !qset->eqs)
+ if (!qset->rxqs && !qset->tx_qp)
return;
- /* These queues are leaving. Stop their completions from touching the
- * shared netdev queues: net_txq is shared with whatever replaced them
- * at the same index, and a queue that is only draining always looks
- * like it has room, so it would wake a live queue that stopped itself
- * because its ring was full. The synchronize_net() below then retires
- * any poll that has not seen the flag yet.
- */
+ /* Keep their completions off the netdev queues they now share. */
if (qset->tx_qp) {
unsigned int q;
@@ -4511,7 +4545,6 @@ void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
mana_chn_xdp_release(retiring_prog, retiring_queues);
mana_destroy_txq(scratch);
- mana_destroy_eq(scratch);
mana_cleanup_indir_table(scratch);
kfree(scratch->rxqs);
scratch->rxqs = NULL;
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 26f5ea1f5091a7fab729ecf3468bdc13886fcccb..08e6fb7785cd3be72f8737083e6424783c5e0d21 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -722,7 +722,7 @@ static int mana_set_channels(struct net_device *ndev,
goto clear_flag;
}
- err = mana_alloc_qset(scratch, new_count, apc->rx_queue_size,
+ err = mana_alloc_qset(apc, scratch, new_count, apc->rx_queue_size,
apc->tx_queue_size, apc->priv_flags,
apc->configured_mtu, apc->bpf_prog, &newq);
if (err)
@@ -818,7 +818,7 @@ static int mana_set_ringparam(struct net_device *ndev,
goto clear_flag;
}
- err = mana_alloc_qset(scratch, apc->num_queues, new_rx, new_tx,
+ err = mana_alloc_qset(apc, scratch, apc->num_queues, new_rx, new_tx,
apc->priv_flags, apc->configured_mtu,
apc->bpf_prog, &newq);
if (err) {
@@ -917,7 +917,7 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
goto clear_flag;
}
- err = mana_alloc_qset(scratch, apc->num_queues, apc->rx_queue_size,
+ err = mana_alloc_qset(apc, scratch, apc->num_queues, apc->rx_queue_size,
apc->tx_queue_size, priv_flags,
apc->configured_mtu, apc->bpf_prog, &newq);
if (err)
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index 77cec5b44049202e21f919d4336fdf8ebc248a18..dfb6ba0012fda629192e4fe9cf8aba57fd5bb451 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -581,7 +581,12 @@ struct mana_port_context {
u8 mac_addr[ETH_ALEN];
+ /* EQ pool, owned by the port rather than a queue set: EQs are bound to
+ * MSI-X vectors, which a swap must not double-book. Sized to
+ * max_queues; num_eqs is how many exist.
+ */
struct mana_eq *eqs;
+ unsigned int num_eqs;
struct dentry *mana_eqs_debugfs;
enum TRI_STATE rss_state;
@@ -702,18 +707,11 @@ struct mana_port_context {
u32 steer_cqe_coalescing;
};
-/* struct mana_qset - a self-contained snapshot of the queue-related
- * fields inside mana_port_context that can be swapped atomically.
- *
- * Prototype for the "pre-allocate + swap" reconfiguration path (as
- * suggested by netdev maintainers): a new qset is allocated while the
- * current one keeps serving traffic, then apc's queue fields are
- * atomically switched to the new set and the old set is torn down.
- * The vport (port_handle / vport_use_count) is *not* touched, so RDMA
- * can never race in during reconfiguration.
+/* The queue-related fields of mana_port_context that can be swapped as a
+ * unit. The vport (port_handle, vport_use_count) is not part of it and is
+ * never touched by a swap.
*/
struct mana_qset {
- struct mana_eq *eqs;
struct mana_tx_qp **tx_qp;
struct mana_rxq **rxqs;
@@ -733,13 +731,6 @@ struct mana_qset {
*/
int mtu;
struct bpf_prog *bpf_prog;
-
- /* Per-queue-set debugfs root ("EQs"). Owned by the qset: it is
- * recreated by mana_create_eq() for each new set and torn down
- * with that set, so it must travel with the qset rather than
- * staying on apc.
- */
- struct dentry *mana_eqs_debugfs;
};
netdev_tx_t mana_start_xmit(struct sk_buff *skb, struct net_device *ndev);
@@ -751,13 +742,14 @@ int mana_alloc_queues(struct net_device *ndev);
int mana_attach(struct net_device *ndev);
int mana_detach(struct net_device *ndev, bool from_close);
-/* Pre-allocate + swap reconfiguration path (prototype). Allocation and
- * teardown run against a scratch context so the live port context is only
- * mutated inside mana_publish_qset(), with TX disabled.
+/* Pre-allocate + swap reconfiguration. Allocation and teardown run against a
+ * scratch context, so the live port context is mutated only inside
+ * mana_publish_qset() with TX disabled. Both sets share a port-owned EQ pool.
*/
struct mana_port_context *mana_qset_scratch_alloc(struct mana_port_context *apc);
void mana_qset_scratch_free(struct mana_port_context *scratch);
-int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
+int mana_alloc_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, unsigned int num_queues,
unsigned int rx_queue_size, unsigned int tx_queue_size,
u32 priv_flags, int mtu, struct bpf_prog *bpf_prog,
struct mana_qset *out);
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 10/13] net: mana: release EQs left idle by a channel-count reduction
2026-08-13 5:04 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (8 preceding siblings ...)
2026-08-13 5:04 ` [PATCH net-next v2 09/13] net: mana: share the EQ pool across a queue-set swap Long Li
@ 2026-08-13 5:04 ` Long Li
2026-08-13 5:04 ` [PATCH net-next v2 11/13] net: mana: keep a user-configured RSS table across a queue rebuild Long Li
` (2 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Long Li @ 2026-08-13 5:04 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
The shared EQ pool only grows, so it sits at the high-water mark of every
channel count the port has ever used. After "ethtool -L ens1 combined 32"
then "combined 4" the port keeps 32 EQs and 32 MSI-X vectors while using
four:
# ethtool -L ens1 combined 4
# grep -c mana /proc/interrupts
33
The pre-swap path recreated every EQ per reconfiguration, so this is new.
Release the EQs above the live queue count once a retiring set has been
torn down. That is the only safe point: a CQ holds the gdma_queue pointer
of its parent EQ, so an EQ may only be destroyed once the set referencing
it is gone.
While here, fix mana_create_eq_debugfs(), which stored the new dentry in
a stack copy rather than in apc->eqs[i].
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 4 +-
drivers/net/ethernet/microsoft/mana/mana_en.c | 73 +++++++++++++++----
.../ethernet/microsoft/mana/mana_ethtool.c | 15 ++--
include/net/mana/mana.h | 3 +-
4 files changed, 70 insertions(+), 25 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index 05936453fbbfa59c563fdea50e0b40096c2b46ac..4b29406595b37877e1e5d14cf93d68aa3c4ace02 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
@@ -228,7 +228,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
if (err) {
NL_SET_ERR_MSG_MOD(extack,
"XDP: Re-config failed at publish");
- mana_free_qset(scratch, &newq);
+ mana_free_qset(apc, scratch, &newq);
/* After the cleanup above: closing destroys the EQ pool
* those queues' CQs were attached to.
*/
@@ -237,7 +237,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
return err;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(apc, scratch, &oldq);
mana_qset_scratch_free(scratch);
} else {
/* No queues to rebuild; mana_open() will size the RX buffers
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index be7f9f6626e42c33fc0e749e6897ecc8117222cf..7c43c2f9043ba591b58e4ee2211cf37da9fead36 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -921,16 +921,13 @@ static int mana_change_mtu(struct net_device *ndev, int new_mtu)
err = mana_publish_qset(mpc, &newq, &oldq);
if (err) {
- mana_free_qset(scratch, &newq);
+ mana_free_qset(mpc, scratch, &newq);
goto free_scratch;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(mpc, scratch, &oldq);
free_scratch:
- /* After the caller-side cleanup above, so the EQ pool outlives the
- * CQs that reference it.
- */
mana_publish_close_if_needed(mpc);
mana_qset_scratch_free(scratch);
return err;
@@ -1817,6 +1814,9 @@ void mana_destroy_eq(struct mana_port_context *apc)
msi = eq->eq.msix_index;
mana_gd_destroy_queue(gc, eq);
mana_gd_put_gic(gc, !gc->msi_sharing, msi);
+ apc->eqs[i].eq = NULL;
+ /* Freed with the parent by debugfs_remove_recursive() above. */
+ apc->eqs[i].mana_eq_debugfs = NULL;
}
kfree(apc->eqs);
@@ -1827,15 +1827,16 @@ EXPORT_SYMBOL_NS(mana_destroy_eq, "NET_MANA");
static void mana_create_eq_debugfs(struct mana_port_context *apc, int i)
{
- struct mana_eq eq = apc->eqs[i];
+ struct mana_eq *eq = &apc->eqs[i];
char eqnum[32];
sprintf(eqnum, "eq%d", i);
- eq.mana_eq_debugfs = debugfs_create_dir(eqnum, apc->mana_eqs_debugfs);
- debugfs_create_u32("head", 0400, eq.mana_eq_debugfs, &eq.eq->head);
- debugfs_create_u32("tail", 0400, eq.mana_eq_debugfs, &eq.eq->tail);
- debugfs_create_u32("irq", 0400, eq.mana_eq_debugfs, &eq.eq->eq.irq);
- debugfs_create_file("eq_dump", 0400, eq.mana_eq_debugfs, eq.eq, &mana_dbg_q_fops);
+ eq->mana_eq_debugfs = debugfs_create_dir(eqnum, apc->mana_eqs_debugfs);
+ debugfs_create_u32("head", 0400, eq->mana_eq_debugfs, &eq->eq->head);
+ debugfs_create_u32("tail", 0400, eq->mana_eq_debugfs, &eq->eq->tail);
+ debugfs_create_u32("irq", 0400, eq->mana_eq_debugfs, &eq->eq->eq.irq);
+ debugfs_create_file("eq_dump", 0400, eq->mana_eq_debugfs, eq->eq,
+ &mana_dbg_q_fops);
}
int mana_create_eq(struct mana_port_context *apc)
@@ -1961,6 +1962,37 @@ static int mana_grow_eqs(struct mana_port_context *apc, unsigned int need)
return err;
}
+/* Release EQs above @keep, returning the MSI-X vectors freed. Only safe once
+ * no set references them, i.e. after mana_free_qset(), or a live CQ would
+ * point at a destroyed EQ.
+ */
+static void mana_shrink_eqs(struct mana_port_context *apc, unsigned int keep)
+{
+ struct gdma_context *gc = apc->ac->gdma_dev->gdma_context;
+ struct gdma_queue *eq;
+ unsigned int msi;
+ unsigned int i;
+
+ if (!apc->eqs || keep >= apc->num_eqs)
+ return;
+
+ for (i = keep; i < apc->num_eqs; i++) {
+ eq = apc->eqs[i].eq;
+ if (!eq)
+ continue;
+
+ debugfs_remove_recursive(apc->eqs[i].mana_eq_debugfs);
+ apc->eqs[i].mana_eq_debugfs = NULL;
+
+ msi = eq->eq.msix_index;
+ mana_gd_destroy_queue(gc, eq);
+ mana_gd_put_gic(gc, !gc->msi_sharing, msi);
+ apc->eqs[i].eq = NULL;
+ }
+
+ apc->num_eqs = keep;
+}
+
static int mana_fence_rq(struct mana_port_context *apc, struct mana_rxq *rxq)
{
struct mana_fence_rq_resp resp = {};
@@ -4179,6 +4211,13 @@ int mana_alloc_qset(struct mana_port_context *apc,
kfree(scratch->rxqs);
scratch->rxqs = NULL;
out_err:
+ /* Give back any EQ this attempt added to the shared pool rather than
+ * holding its MSI-X vectors until some later teardown: the live set
+ * still needs only apc->num_queues of them. Safe here because this
+ * set's CQs have already been destroyed above.
+ */
+ mana_shrink_eqs(apc, apc->num_queues);
+
netdev_err(ndev, "mana_alloc_qset(num_queues=%u) failed: %d\n",
num_queues, err);
return err;
@@ -4456,7 +4495,8 @@ static void mana_qset_debugfs_publish(struct mana_port_context *apc)
/* Tear down @qset, no longer installed on @apc, against @scratch so the live
* context never points at queues being freed.
*/
-void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
+void mana_free_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, struct mana_qset *qset)
{
struct bpf_prog *retiring_prog;
unsigned int retiring_queues;
@@ -4551,12 +4591,19 @@ void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
memset(qset, 0, sizeof(*qset));
+ /* This set is gone, so any EQ above the live queue count is now
+ * unreferenced. Release those vectors instead of holding them at the
+ * high-water mark. Safe here and only here: the retiring set's CQs
+ * have just been destroyed.
+ */
+ mana_shrink_eqs(apc, apc->num_queues);
+
/* Queues built through a scratch context carry no debugfs nodes,
* because both sets are alive during the swap and would collide on
* the same names. The retiring set's nodes are gone now, so the
* published queues can finally take those names.
*/
- mana_qset_debugfs_publish(netdev_priv(scratch->ndev));
+ mana_qset_debugfs_publish(apc);
}
/* --- end of pre-allocate + swap reconfiguration path ---------------------- */
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 08e6fb7785cd3be72f8737083e6424783c5e0d21..024119dd4e353e33d11ccc883b4fe09a99434a26 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -730,16 +730,13 @@ static int mana_set_channels(struct net_device *ndev,
err = mana_publish_qset(apc, &newq, &oldq);
if (err) {
- mana_free_qset(scratch, &newq);
+ mana_free_qset(apc, scratch, &newq);
goto free_scratch;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(apc, scratch, &oldq);
free_scratch:
- /* After the caller-side cleanup above, so the EQ pool outlives the
- * CQs that reference it.
- */
mana_publish_close_if_needed(apc);
mana_qset_scratch_free(scratch);
clear_flag:
@@ -831,11 +828,11 @@ static int mana_set_ringparam(struct net_device *ndev,
if (err) {
NL_SET_ERR_MSG_FMT(extack, "failed to change ring params: %d",
err);
- mana_free_qset(scratch, &newq);
+ mana_free_qset(apc, scratch, &newq);
goto free_scratch;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(apc, scratch, &oldq);
free_scratch:
/* After the caller-side cleanup above, so the EQ pool outlives the
@@ -925,11 +922,11 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
err = mana_publish_qset(apc, &newq, &oldq);
if (err) {
- mana_free_qset(scratch, &newq);
+ mana_free_qset(apc, scratch, &newq);
goto free_scratch;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(apc, scratch, &oldq);
free_scratch:
mana_publish_close_if_needed(apc);
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index dfb6ba0012fda629192e4fe9cf8aba57fd5bb451..619c66f3c6192dd2988a1bba73e991df2a773923 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -756,7 +756,8 @@ int mana_alloc_qset(struct mana_port_context *apc,
int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
struct mana_qset *out_old);
void mana_publish_close_if_needed(struct mana_port_context *apc);
-void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset);
+void mana_free_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, struct mana_qset *qset);
void mana_dim_change(struct mana_cq *cq, bool enable);
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 11/13] net: mana: keep a user-configured RSS table across a queue rebuild
2026-08-13 5:04 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (9 preceding siblings ...)
2026-08-13 5:04 ` [PATCH net-next v2 10/13] net: mana: release EQs left idle by a channel-count reduction Long Li
@ 2026-08-13 5:04 ` Long Li
2026-08-13 5:04 ` [PATCH net-next v2 12/13] net: mana: keep the surviving queues when the channel count is reduced Long Li
2026-08-13 5:04 ` [PATCH net-next v2 13/13] net: mana: keep the existing queues when the channel count is raised Long Li
12 siblings, 0 replies; 15+ messages in thread
From: Long Li @ 2026-08-13 5:04 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
A queue rebuild regenerates the RSS indirection table from the driver
default, so a table the user installed with "ethtool -X" is silently
replaced by any reconfiguration that rebuilds the queues.
Carry it over instead when the queue count is unchanged: the entries are
queue indices, so they stay meaningful. A driver-generated table is
still regenerated, and a table that cannot be honoured for the new queue
count is reported to the core as lost - but only once the new set is
actually live, so a failed reconfiguration does not clear
IFF_RXFH_CONFIGURED on a port that kept its old queues.
Signed-off-by: Long Li <longli@microsoft.com>
---
drivers/net/ethernet/microsoft/mana/mana_en.c | 78 +++++++++++++++++--
include/net/mana/mana.h | 7 ++
2 files changed, 80 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 7c43c2f9043ba591b58e4ee2211cf37da9fead36..6e77b59cfcf907f7e584625273972bf185501d20 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -3496,6 +3496,38 @@ static void mana_rss_table_init(struct mana_port_context *apc)
ethtool_rxfh_indir_default(i, apc->num_queues);
}
+/* Whether @apc's indirection table can be carried to a set of @num_queues,
+ * rather than rebuilt from the driver default.
+ *
+ * Only a user table ("ethtool -X") is kept; a driver one is rebuilt to spread
+ * over the new count. ethtool_check_max_channel() already refuses a reduction
+ * that leaves a user table pointing past the last queue, so the bounds check
+ * below only guards rebuild paths that bypass ethtool.
+ *
+ * @lost reports a table that cannot be carried instead of calling
+ * ethtool_rxfh_indir_lost() here, since the swap may still fail and leave the
+ * port on queues where the table still applies. False when none was set.
+ */
+static bool mana_rss_table_keep(struct mana_port_context *apc,
+ unsigned int num_queues, bool *lost)
+{
+ u32 i;
+
+ *lost = false;
+
+ if (!netif_is_rxfh_configured(apc->ndev))
+ return false;
+
+ for (i = 0; i < apc->indir_table_sz; i++) {
+ if (apc->indir_table[i] >= num_queues) {
+ *lost = true;
+ return false;
+ }
+ }
+
+ return true;
+}
+
int mana_disable_vport_rx(struct mana_port_context *apc)
{
return mana_cfg_vport_steering(apc, TRI_STATE_FALSE, false, false,
@@ -3766,6 +3798,7 @@ int mana_alloc_queues(struct net_device *ndev)
{
struct mana_port_context *apc = netdev_priv(ndev);
struct gdma_dev *gd = apc->ac->gdma_dev;
+ bool indir_lost;
int err;
err = mana_create_vport(apc, ndev);
@@ -3811,7 +3844,18 @@ int mana_alloc_queues(struct net_device *ndev)
goto destroy_rxq;
}
- mana_rss_table_init(apc);
+ /* Keep a user-configured RSS table across a rebuild; the entries are
+ * queue indices, so they stay meaningful as long as the queue count
+ * is unchanged. Only a driver-generated table is regenerated here.
+ *
+ * Nothing to roll back to here, so report the loss as soon as it is
+ * decided and keep the table and the core's view of it in step.
+ */
+ if (!mana_rss_table_keep(apc, apc->num_queues, &indir_lost)) {
+ if (indir_lost)
+ ethtool_rxfh_indir_lost(ndev);
+ mana_rss_table_init(apc);
+ }
err = mana_config_rss(apc, TRI_STATE_TRUE, true, true);
if (err) {
@@ -4068,11 +4112,15 @@ static void mana_qset_snapshot(const struct mana_port_context *ctx,
out->priv_flags = ctx->priv_flags;
out->mtu = ctx->configured_mtu;
out->bpf_prog = ctx->bpf_prog;
+
+ /* A set taken from a live context has nothing pending; the builders
+ * set this after snapshotting if they had to drop the user's table.
+ */
+ out->rxfh_indir_lost = false;
}
-/* Install @qset's fields onto @ctx. The vport (port_handle,
- * vport_use_count) and the port-level debugfs dir are deliberately not
- * touched: they outlive any individual queue set.
+/* The vport (port_handle, vport_use_count) and the port-level debugfs dir are
+ * not touched: they outlive any individual queue set.
*/
static void mana_qset_install(struct mana_port_context *ctx,
const struct mana_qset *qset)
@@ -4150,6 +4198,7 @@ int mana_alloc_qset(struct mana_port_context *apc,
struct mana_qset *out)
{
struct net_device *ndev = scratch->ndev;
+ bool indir_lost;
int err;
ASSERT_RTNL();
@@ -4193,9 +4242,19 @@ int mana_alloc_qset(struct mana_port_context *apc,
if (err)
goto cleanup_rxq;
- mana_rss_table_init(scratch);
+ /* Carry a user-configured RSS table over to the new set. The entries
+ * are queue indices, so mana_config_rss() in mana_publish_qset() maps
+ * them onto the new set's RX objects. A driver-generated table is
+ * rebuilt instead, so it covers every queue of the new set.
+ */
+ if (mana_rss_table_keep(apc, num_queues, &indir_lost))
+ memcpy(scratch->indir_table, apc->indir_table,
+ apc->indir_table_sz * sizeof(*apc->indir_table));
+ else
+ mana_rss_table_init(scratch);
mana_qset_snapshot(scratch, out);
+ out->rxfh_indir_lost = indir_lost;
return 0;
cleanup_rxq:
@@ -4407,6 +4466,15 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
if (carrier_ok)
netif_carrier_on(ndev);
+ /* The set that could not carry the user's indirection table is the one
+ * serving traffic now, so the table really is gone. Reporting it here
+ * rather than while the set was being built keeps a failed swap from
+ * clearing IFF_RXFH_CONFIGURED on a port that kept its old queues, and
+ * with them a table that is still valid and still programmed.
+ */
+ if (newq->rxfh_indir_lost)
+ ethtool_rxfh_indir_lost(ndev);
+
return 0;
rollback:
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index 619c66f3c6192dd2988a1bba73e991df2a773923..0d7375adeb5366bbbb99a7faa931a8830f71ef10 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -731,6 +731,13 @@ struct mana_qset {
*/
int mtu;
struct bpf_prog *bpf_prog;
+
+ /* The user's RSS indirection table could not be carried onto this set,
+ * so a default one was generated for it. mana_publish_qset() tells the
+ * core once this set is live; until then the port is still running on
+ * a set where the user's table applies.
+ */
+ bool rxfh_indir_lost;
};
netdev_tx_t mana_start_xmit(struct sk_buff *skb, struct net_device *ndev);
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 12/13] net: mana: keep the surviving queues when the channel count is reduced
2026-08-13 5:04 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (10 preceding siblings ...)
2026-08-13 5:04 ` [PATCH net-next v2 11/13] net: mana: keep a user-configured RSS table across a queue rebuild Long Li
@ 2026-08-13 5:04 ` Long Li
2026-08-13 5:04 ` [PATCH net-next v2 13/13] net: mana: keep the existing queues when the channel count is raised Long Li
12 siblings, 0 replies; 15+ messages in thread
From: Long Li @ 2026-08-13 5:04 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
A channel-count reduction rebuilds every queue it keeps, even though none
of their properties change. Shrinking 16 channels to 8 creates 8 TX and 8
RX queues and then destroys all 16 of each:
set_channels 16 -> 8 created SQ=8 RQ=8 | destroyed SQ=16 RQ=16
A queue is derived from the ring sizes, the MTU/priv-flag/XDP buffer
layout and its EQ, none of which depend on the count, and queue i is
bound to EQ i at any count. Carry the survivors over and retire only the
tail: a reduction allocates two pointer arrays and a steering table
instead of a full queue set, the peak stays at one set rather than two,
and the queues that stay keep their page pools, posted RX buffers and
NAPI state.
A ring resize, MTU change, priv-flag toggle or XDP attach changes a
property of every queue, so those still rebuild.
Signed-off-by: Long Li <longli@microsoft.com>
---
drivers/net/ethernet/microsoft/mana/mana_en.c | 127 ++++++++++++++++++
.../ethernet/microsoft/mana/mana_ethtool.c | 33 +++++
include/net/mana/mana.h | 4 +
3 files changed, 164 insertions(+)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 6e77b59cfcf907f7e584625273972bf185501d20..c2b8ac67963fb6134a21b682a1b9ee373b1d0b7c 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -4186,6 +4186,133 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
kvfree(scratch);
}
+/* Carve the live set into a kept prefix [0, @new_count) in @out_new and a
+ * tail to retire in @out_tail. @apc is untouched, on failure too.
+ *
+ * Queue i is built from the ring sizes and the buffer layout and keeps EQ i
+ * at any count, so a reduction carries the survivors over and destroys only
+ * the tail. Allocates two pointer arrays and a steering table, nothing else:
+ * the queues that stay keep their page pools, posted buffers and NAPI.
+ */
+int mana_split_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, unsigned int new_count,
+ struct mana_qset *out_new, struct mana_qset *out_tail)
+{
+ unsigned int old_count = apc->num_queues;
+ struct mana_tx_qp **new_tx, **tail_tx;
+ struct mana_rxq **new_rx, **tail_rx;
+ unsigned int tail_count;
+ bool indir_lost;
+ unsigned int i;
+ int err;
+
+ ASSERT_RTNL();
+
+ if (WARN_ON(new_count == 0 || new_count >= old_count))
+ return -EINVAL;
+ if (WARN_ON(!apc->tx_qp || !apc->rxqs))
+ return -EINVAL;
+
+ tail_count = old_count - new_count;
+
+ /* Build the smaller set's steering table separately: mana_config_rss()
+ * would otherwise index the shorter rxqs[] with entries still referring
+ * to retired queues.
+ */
+ scratch->num_queues = new_count;
+ err = mana_rss_table_alloc(scratch);
+ if (err)
+ return err;
+
+ if (mana_rss_table_keep(apc, new_count, &indir_lost))
+ memcpy(scratch->indir_table, apc->indir_table,
+ apc->indir_table_sz * sizeof(*apc->indir_table));
+ else
+ mana_rss_table_init(scratch);
+
+ new_tx = kzalloc_objs(struct mana_tx_qp *, new_count);
+ new_rx = kzalloc_objs(struct mana_rxq *, new_count);
+ tail_tx = kzalloc_objs(struct mana_tx_qp *, tail_count);
+ tail_rx = kzalloc_objs(struct mana_rxq *, tail_count);
+ if (!new_tx || !new_rx || !tail_tx || !tail_rx) {
+ err = -ENOMEM;
+ goto free_arrays;
+ }
+
+ for (i = 0; i < new_count; i++) {
+ new_tx[i] = apc->tx_qp[i];
+ new_rx[i] = apc->rxqs[i];
+ }
+ for (i = 0; i < tail_count; i++) {
+ tail_tx[i] = apc->tx_qp[new_count + i];
+ tail_rx[i] = apc->rxqs[new_count + i];
+ }
+
+ /* The kept prefix, with the new steering table. */
+ out_new->tx_qp = new_tx;
+ out_new->rxqs = new_rx;
+ out_new->indir_table = scratch->indir_table;
+ out_new->indir_table_sz = scratch->indir_table_sz;
+ out_new->rxobj_table = scratch->rxobj_table;
+ out_new->default_rxobj = apc->rxqs[0]->rxobj;
+ out_new->num_queues = new_count;
+ out_new->rx_queue_size = apc->rx_queue_size;
+ out_new->tx_queue_size = apc->tx_queue_size;
+ out_new->priv_flags = apc->priv_flags;
+ out_new->mtu = apc->configured_mtu;
+ out_new->bpf_prog = apc->bpf_prog;
+ out_new->rxfh_indir_lost = indir_lost;
+
+ /* Ownership of the table moved to @out_new. */
+ scratch->indir_table = NULL;
+ scratch->rxobj_table = NULL;
+
+ /* The tail. It owns no steering table; bpf_prog is carried so that
+ * retiring it drops exactly the tail's per-queue program references
+ * and leaves the kept ones alone.
+ */
+ memset(out_tail, 0, sizeof(*out_tail));
+ out_tail->tx_qp = tail_tx;
+ out_tail->rxqs = tail_rx;
+ out_tail->default_rxobj = INVALID_MANA_HANDLE;
+ out_tail->num_queues = tail_count;
+ out_tail->rx_queue_size = apc->rx_queue_size;
+ out_tail->tx_queue_size = apc->tx_queue_size;
+ out_tail->priv_flags = apc->priv_flags;
+ out_tail->mtu = apc->configured_mtu;
+ out_tail->bpf_prog = apc->bpf_prog;
+
+ return 0;
+
+free_arrays:
+ kfree(new_tx);
+ kfree(new_rx);
+ kfree(tail_tx);
+ kfree(tail_rx);
+ mana_cleanup_indir_table(scratch);
+ return err;
+}
+
+/**
+ * mana_discard_split - drop the containers built by mana_split_qset()
+ * @newq: set that was never published
+ * @tailq: matching tail
+ *
+ * Frees the pointer arrays and the steering table only: the queues they refer
+ * to are still owned by the live port context.
+ */
+void mana_discard_split(struct mana_qset *newq, struct mana_qset *tailq)
+{
+ kfree(newq->tx_qp);
+ kfree(newq->rxqs);
+ kfree(newq->indir_table);
+ kfree(newq->rxobj_table);
+ kfree(tailq->tx_qp);
+ kfree(tailq->rxqs);
+ memset(newq, 0, sizeof(*newq));
+ memset(tailq, 0, sizeof(*tailq));
+}
+
/* Rebuild the queues at the current count in @scratch, for callers changing a
* per-queue property; a count change goes through mana_split_qset() or
* mana_grow_qset(), so this never has to add an EQ. The installed set keeps
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 024119dd4e353e33d11ccc883b4fe09a99434a26..415422aa68672accd03c161732fa1e96dd5562ba 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -722,6 +722,39 @@ static int mana_set_channels(struct net_device *ndev,
goto clear_flag;
}
+ /* A reduction keeps its queues configured identically, so carry them
+ * over and retire only the tail: no DMA ring, no hardware WQ object,
+ * and no old+new peak.
+ */
+ if (new_count < apc->num_queues) {
+ struct mana_qset tailq;
+
+ err = mana_split_qset(apc, scratch, new_count, &newq, &tailq);
+ if (err)
+ goto free_scratch; /* current qset untouched */
+
+ err = mana_publish_qset(apc, &newq, &oldq);
+ if (err) {
+ /* The old set is live again; drop only the containers
+ * built above, never the queues they point at.
+ */
+ mana_discard_split(&newq, &tailq);
+ goto free_scratch;
+ }
+
+ /* @oldq holds the original arrays and steering table. Every
+ * queue they referenced is now owned by either the published
+ * set or the tail, so only the containers are freed here.
+ */
+ kfree(oldq.tx_qp);
+ kfree(oldq.rxqs);
+ kfree(oldq.indir_table);
+ kfree(oldq.rxobj_table);
+
+ mana_free_qset(apc, scratch, &tailq);
+ goto free_scratch;
+ }
+
err = mana_alloc_qset(apc, scratch, new_count, apc->rx_queue_size,
apc->tx_queue_size, apc->priv_flags,
apc->configured_mtu, apc->bpf_prog, &newq);
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index 0d7375adeb5366bbbb99a7faa931a8830f71ef10..cd41a135815710162b1e5fd3edafa119ae12d1dd 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -760,6 +760,10 @@ int mana_alloc_qset(struct mana_port_context *apc,
unsigned int rx_queue_size, unsigned int tx_queue_size,
u32 priv_flags, int mtu, struct bpf_prog *bpf_prog,
struct mana_qset *out);
+int mana_split_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, unsigned int new_count,
+ struct mana_qset *out_new, struct mana_qset *out_tail);
+void mana_discard_split(struct mana_qset *newq, struct mana_qset *tailq);
int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
struct mana_qset *out_old);
void mana_publish_close_if_needed(struct mana_port_context *apc);
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH net-next v2 13/13] net: mana: keep the existing queues when the channel count is raised
2026-08-13 5:04 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (11 preceding siblings ...)
2026-08-13 5:04 ` [PATCH net-next v2 12/13] net: mana: keep the surviving queues when the channel count is reduced Long Li
@ 2026-08-13 5:04 ` Long Li
12 siblings, 0 replies; 15+ messages in thread
From: Long Li @ 2026-08-13 5:04 UTC (permalink / raw)
To: Long Li, Konstantin Taranov, Jakub Kicinski, David S . Miller,
Paolo Abeni, Eric Dumazet, Andrew Lunn, Jason Gunthorpe,
Leon Romanovsky, Haiyang Zhang, K . Y . Srinivasan, Wei Liu,
Dexuan Cui, shradhagupta, Simon Horman, ernis, stephen
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
The reduction path already carries its surviving queues over. An increase
still builds a complete second set and throws the running one away, even
though it keeps every queue it already had:
set_channels 4 -> 8 created SQ=8 RQ=8 | destroyed SQ=4 RQ=4
The same reasoning applies in both directions. Carry the running queues
over and build only the new tail, so growing 4 channels to 8 creates 4
SQ/RQ pairs instead of 8 and never holds 12 of each against the vport
maximum.
This completes the conversion, so advertise it to the firmware. Every
resize path - channel count, ring size, MTU, the full-page RX private
flag and XDP attach - now builds the new set before retiring the old and
keeps the old one running if that fails.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 2 +-
drivers/net/ethernet/microsoft/mana/mana_en.c | 246 +++++++++++++++---
.../ethernet/microsoft/mana/mana_ethtool.c | 41 ++-
include/net/mana/gdma.h | 11 +-
include/net/mana/mana.h | 7 +-
5 files changed, 258 insertions(+), 49 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index 4b29406595b37877e1e5d14cf93d68aa3c4ace02..f47755fa866004612bd4bd06b417172c7acf9eb7 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
@@ -213,7 +213,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
return -ENOMEM;
}
- err = mana_alloc_qset(apc, scratch, apc->num_queues,
+ err = mana_alloc_qset(apc, scratch,
apc->rx_queue_size, apc->tx_queue_size,
apc->priv_flags, apc->configured_mtu,
prog, &newq);
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index c2b8ac67963fb6134a21b682a1b9ee373b1d0b7c..2d8fcdedc8b66b07cce666f031104a385efd27d6 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -913,7 +913,7 @@ static int mana_change_mtu(struct net_device *ndev, int new_mtu)
if (!scratch)
return -ENOMEM;
- err = mana_alloc_qset(mpc, scratch, mpc->num_queues, mpc->rx_queue_size,
+ err = mana_alloc_qset(mpc, scratch, mpc->rx_queue_size,
mpc->tx_queue_size, mpc->priv_flags, new_mtu,
mpc->bpf_prog, &newq);
if (err)
@@ -2825,7 +2825,11 @@ static void mana_deinit_txq(struct mana_port_context *apc, struct mana_txq *txq)
mana_gd_destroy_queue(gd->gdma_context, txq->gdma_sq);
}
-static void mana_destroy_txq(struct mana_port_context *apc)
+/* The array itself is left in place: the grow path tears down only a range,
+ * and the queues below @first are still live and still referenced by it.
+ */
+static void mana_destroy_txq_from(struct mana_port_context *apc,
+ unsigned int first)
{
struct napi_struct *napi;
int i;
@@ -2833,7 +2837,7 @@ static void mana_destroy_txq(struct mana_port_context *apc)
if (!apc->tx_qp)
return;
- for (i = 0; i < apc->num_queues; i++) {
+ for (i = first; i < apc->num_queues; i++) {
if (!apc->tx_qp[i])
continue;
@@ -2858,6 +2862,14 @@ static void mana_destroy_txq(struct mana_port_context *apc)
kvfree(apc->tx_qp[i]);
}
+}
+
+static void mana_destroy_txq(struct mana_port_context *apc)
+{
+ if (!apc->tx_qp)
+ return;
+
+ mana_destroy_txq_from(apc, 0);
kfree(apc->tx_qp);
apc->tx_qp = NULL;
@@ -2888,8 +2900,12 @@ static void mana_create_txq_debugfs(struct mana_port_context *apc, int idx)
tx_qp->tx_cq.gdma_cq, &mana_dbg_q_fops);
}
+/* @first is non-zero only for the grow path, which supplies an already
+ * allocated apc->tx_qp[] holding the carried-over queues. On error only the
+ * queues this call created are torn down.
+ */
static int mana_create_txq(struct mana_port_context *apc,
- struct net_device *net)
+ struct net_device *net, unsigned int first)
{
struct mana_context *ac = apc->ac;
struct gdma_dev *gd = ac->gdma_dev;
@@ -2904,9 +2920,14 @@ static int mana_create_txq(struct mana_port_context *apc,
int err;
int i;
- apc->tx_qp = kzalloc_objs(struct mana_tx_qp *, apc->num_queues);
- if (!apc->tx_qp)
- return -ENOMEM;
+ if (first) {
+ if (WARN_ON(!apc->tx_qp))
+ return -EINVAL;
+ } else {
+ apc->tx_qp = kzalloc_objs(struct mana_tx_qp *, apc->num_queues);
+ if (!apc->tx_qp)
+ return -ENOMEM;
+ }
/* The minimum size of the WQE is 32 bytes, hence
* apc->tx_queue_size represents the maximum number of WQEs
@@ -2923,7 +2944,7 @@ static int mana_create_txq(struct mana_port_context *apc,
gc = gd->gdma_context;
- for (i = 0; i < apc->num_queues; i++) {
+ for (i = first; i < apc->num_queues; i++) {
apc->tx_qp[i] = kvzalloc_obj(*apc->tx_qp[i]);
if (!apc->tx_qp[i]) {
err = -ENOMEM;
@@ -3031,7 +3052,10 @@ static int mana_create_txq(struct mana_port_context *apc,
out:
netdev_err(net, "Failed to create %d TX queues, %d\n",
apc->num_queues, err);
- mana_destroy_txq(apc);
+ if (first)
+ mana_destroy_txq_from(apc, first);
+ else
+ mana_destroy_txq(apc);
return err;
}
@@ -3387,14 +3411,18 @@ static void mana_create_rxq_debugfs(struct mana_port_context *apc, int idx)
&mana_dbg_q_fops);
}
+/* @first is non-zero only for the grow path; the slots below it already hold
+ * carried-over queues. Queues created before a failure are left in
+ * apc->rxqs[] for the caller to tear down.
+ */
static int mana_add_rx_queues(struct mana_port_context *apc,
- struct net_device *ndev)
+ struct net_device *ndev, unsigned int first)
{
struct mana_rxq *rxq;
int err = 0;
int i;
- for (i = 0; i < apc->num_queues; i++) {
+ for (i = first; i < apc->num_queues; i++) {
rxq = mana_create_rxq(apc, i, &apc->eqs[i], ndev);
if (IS_ERR(rxq)) {
err = PTR_ERR(rxq);
@@ -3413,14 +3441,16 @@ static int mana_add_rx_queues(struct mana_port_context *apc,
return err;
}
-static void mana_destroy_rxqs(struct mana_port_context *apc)
+/* The array is left in place; see mana_destroy_txq_from(). */
+static void mana_destroy_rxqs_from(struct mana_port_context *apc,
+ unsigned int first)
{
struct mana_rxq *rxq;
u32 rxq_idx;
if (apc->rxqs) {
- for (rxq_idx = 0; rxq_idx < apc->num_queues; rxq_idx++) {
+ for (rxq_idx = first; rxq_idx < apc->num_queues; rxq_idx++) {
rxq = apc->rxqs[rxq_idx];
if (!rxq)
continue;
@@ -3431,6 +3461,11 @@ static void mana_destroy_rxqs(struct mana_port_context *apc)
}
}
+static void mana_destroy_rxqs(struct mana_port_context *apc)
+{
+ mana_destroy_rxqs_from(apc, 0);
+}
+
static void mana_destroy_vport(struct mana_port_context *apc)
{
struct gdma_dev *gd = apc->ac->gdma_dev;
@@ -3815,7 +3850,7 @@ int mana_alloc_queues(struct net_device *ndev)
goto destroy_vport;
}
- err = mana_create_txq(apc, ndev);
+ err = mana_create_txq(apc, ndev, 0);
if (err) {
netdev_err(ndev, "Failed to create TXQ on vPort %u: %d\n",
apc->port_idx, err);
@@ -3830,7 +3865,7 @@ int mana_alloc_queues(struct net_device *ndev)
goto destroy_txq;
}
- err = mana_add_rx_queues(apc, ndev);
+ err = mana_add_rx_queues(apc, ndev, 0);
if (err)
goto destroy_rxq;
@@ -4313,13 +4348,161 @@ void mana_discard_split(struct mana_qset *newq, struct mana_qset *tailq)
memset(tailq, 0, sizeof(*tailq));
}
+/* The mirror image of mana_split_qset(): carry the existing queues over into
+ * @out_new and build only the [old, @new_count) tail. Growing 4 channels to 8
+ * creates 4 SQ/RQ pairs, not 8, and never holds 12 against the vport maximum.
+ *
+ * @out_fresh names just the queues created here, so a failed publish retires
+ * exactly those. On failure @apc is untouched.
+ */
+int mana_grow_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, unsigned int new_count,
+ struct mana_qset *out_new, struct mana_qset *out_fresh)
+{
+ unsigned int old_count = apc->num_queues;
+ struct mana_tx_qp **new_tx, **fresh_tx;
+ struct mana_rxq **new_rx, **fresh_rx;
+ struct net_device *ndev = apc->ndev;
+ unsigned int fresh_count;
+ bool indir_lost;
+ unsigned int i;
+ int err;
+
+ ASSERT_RTNL();
+
+ if (WARN_ON(new_count <= old_count))
+ return -EINVAL;
+ if (WARN_ON(!apc->tx_qp || !apc->rxqs))
+ return -EINVAL;
+
+ fresh_count = new_count - old_count;
+
+ new_tx = kzalloc_objs(struct mana_tx_qp *, new_count);
+ new_rx = kzalloc_objs(struct mana_rxq *, new_count);
+ fresh_tx = kzalloc_objs(struct mana_tx_qp *, fresh_count);
+ fresh_rx = kzalloc_objs(struct mana_rxq *, fresh_count);
+ if (!new_tx || !new_rx || !fresh_tx || !fresh_rx) {
+ err = -ENOMEM;
+ goto free_arrays;
+ }
+
+ for (i = 0; i < old_count; i++) {
+ new_tx[i] = apc->tx_qp[i];
+ new_rx[i] = apc->rxqs[i];
+ }
+
+ /* @scratch now describes the merged set; the builders fill only the
+ * [old_count, new_count) slots.
+ */
+ scratch->num_queues = new_count;
+ scratch->tx_qp = new_tx;
+ scratch->rxqs = new_rx;
+
+ err = mana_rss_table_alloc(scratch);
+ if (err)
+ goto free_arrays;
+
+ /* Same shared, port-owned EQ pool as a full rebuild; this only adds
+ * the vectors the extra queues need.
+ */
+ err = mana_grow_eqs(apc, new_count);
+ if (err)
+ goto cleanup_rss;
+
+ scratch->eqs = apc->eqs;
+ scratch->num_eqs = apc->num_eqs;
+
+ err = mana_create_txq(scratch, ndev, old_count);
+ if (err)
+ goto cleanup_rss; /* create_txq already undid its own work */
+
+ err = mana_add_rx_queues(scratch, ndev, old_count);
+ if (err)
+ goto cleanup_rxq;
+
+ if (mana_rss_table_keep(apc, new_count, &indir_lost))
+ memcpy(scratch->indir_table, apc->indir_table,
+ apc->indir_table_sz * sizeof(*apc->indir_table));
+ else
+ mana_rss_table_init(scratch);
+
+ mana_qset_snapshot(scratch, out_new);
+ out_new->rxfh_indir_lost = indir_lost;
+
+ for (i = 0; i < fresh_count; i++) {
+ fresh_tx[i] = new_tx[old_count + i];
+ fresh_rx[i] = new_rx[old_count + i];
+ }
+
+ memset(out_fresh, 0, sizeof(*out_fresh));
+ out_fresh->tx_qp = fresh_tx;
+ out_fresh->rxqs = fresh_rx;
+ out_fresh->default_rxobj = INVALID_MANA_HANDLE;
+ out_fresh->num_queues = fresh_count;
+ out_fresh->rx_queue_size = apc->rx_queue_size;
+ out_fresh->tx_queue_size = apc->tx_queue_size;
+ out_fresh->priv_flags = apc->priv_flags;
+ out_fresh->mtu = apc->configured_mtu;
+ out_fresh->bpf_prog = apc->bpf_prog;
+
+ /* mana_publish_qset() cannot do this: mana_chn_setxdp() decides from
+ * rxqs[0], a carried-over queue that already holds the program, and
+ * returns early. Address only the new queues through @out_fresh so
+ * exactly fresh_count references are taken.
+ */
+ mana_qset_install(scratch, out_fresh);
+ mana_chn_setxdp(scratch, mana_xdp_get(apc));
+
+ return 0;
+
+cleanup_rxq:
+ mana_destroy_rxqs_from(scratch, old_count);
+ mana_destroy_txq_from(scratch, old_count);
+cleanup_rss:
+ mana_cleanup_indir_table(scratch);
+free_arrays:
+ /* Only the containers: every queue they name is still live on @apc. */
+ scratch->tx_qp = NULL;
+ scratch->rxqs = NULL;
+ kfree(new_tx);
+ kfree(new_rx);
+ kfree(fresh_tx);
+ kfree(fresh_rx);
+
+ /* Give back any EQ this attempt added rather than holding its MSI-X
+ * vectors: the live set still needs only apc->num_queues of them, and
+ * every CQ this call created has been destroyed above.
+ */
+ mana_shrink_eqs(apc, apc->num_queues);
+
+ netdev_err(ndev, "mana_grow_qset(num_queues=%u) failed: %d\n",
+ new_count, err);
+ return err;
+}
+
+/**
+ * mana_discard_grow - drop the merged containers built by mana_grow_qset()
+ * @newq: set that was never published
+ *
+ * Frees the pointer arrays and steering table only: carried-over queues
+ * belong to the live context, fresh ones are retired through @out_fresh.
+ */
+void mana_discard_grow(struct mana_qset *newq)
+{
+ kfree(newq->tx_qp);
+ kfree(newq->rxqs);
+ kfree(newq->indir_table);
+ kfree(newq->rxobj_table);
+ memset(newq, 0, sizeof(*newq));
+}
+
/* Rebuild the queues at the current count in @scratch, for callers changing a
* per-queue property; a count change goes through mana_split_qset() or
* mana_grow_qset(), so this never has to add an EQ. The installed set keeps
* serving traffic meanwhile. On error nothing is left allocated.
*/
int mana_alloc_qset(struct mana_port_context *apc,
- struct mana_port_context *scratch, unsigned int num_queues,
+ struct mana_port_context *scratch,
unsigned int rx_queue_size, unsigned int tx_queue_size,
u32 priv_flags, int mtu, struct bpf_prog *bpf_prog,
struct mana_qset *out)
@@ -4330,7 +4513,7 @@ int mana_alloc_qset(struct mana_port_context *apc,
ASSERT_RTNL();
- scratch->num_queues = num_queues;
+ scratch->num_queues = apc->num_queues;
scratch->rx_queue_size = rx_queue_size;
scratch->tx_queue_size = tx_queue_size;
scratch->priv_flags = priv_flags;
@@ -4350,22 +4533,19 @@ int mana_alloc_qset(struct mana_port_context *apc,
if (err)
goto cleanup_rxq_array;
- /* Grow the port's shared EQ pool if this set needs more. The pool
- * belongs to @apc, not to either queue set, so both sets can be
- * live at once without double-booking MSI-X vectors.
+ /* The queue count is unchanged, so the port's shared EQ pool already
+ * has an EQ for every queue this set will build. Both sets reference
+ * the same pool while they are live, so a swap never needs old + new
+ * MSI-X vectors.
*/
- err = mana_grow_eqs(apc, num_queues);
- if (err)
- goto cleanup_rss;
-
scratch->eqs = apc->eqs;
scratch->num_eqs = apc->num_eqs;
- err = mana_create_txq(scratch, ndev);
+ err = mana_create_txq(scratch, ndev, 0);
if (err)
goto cleanup_rss;
- err = mana_add_rx_queues(scratch, ndev);
+ err = mana_add_rx_queues(scratch, ndev, 0);
if (err)
goto cleanup_rxq;
@@ -4374,7 +4554,7 @@ int mana_alloc_qset(struct mana_port_context *apc,
* them onto the new set's RX objects. A driver-generated table is
* rebuilt instead, so it covers every queue of the new set.
*/
- if (mana_rss_table_keep(apc, num_queues, &indir_lost))
+ if (mana_rss_table_keep(apc, scratch->num_queues, &indir_lost))
memcpy(scratch->indir_table, apc->indir_table,
apc->indir_table_sz * sizeof(*apc->indir_table));
else
@@ -4397,15 +4577,11 @@ int mana_alloc_qset(struct mana_port_context *apc,
kfree(scratch->rxqs);
scratch->rxqs = NULL;
out_err:
- /* Give back any EQ this attempt added to the shared pool rather than
- * holding its MSI-X vectors until some later teardown: the live set
- * still needs only apc->num_queues of them. Safe here because this
- * set's CQs have already been destroyed above.
+ /* No EQ to give back: this path never adds one, it reuses the pool
+ * the live set is already using.
*/
- mana_shrink_eqs(apc, apc->num_queues);
-
netdev_err(ndev, "mana_alloc_qset(num_queues=%u) failed: %d\n",
- num_queues, err);
+ apc->num_queues, err);
return err;
}
@@ -4667,7 +4843,7 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
* Idempotent: a carried-over queue keeps its node; suppressed creation leaves
* an error pointer, not NULL, so both read as "no node". Under RTNL.
*/
-static void mana_qset_debugfs_publish(struct mana_port_context *apc)
+void mana_qset_debugfs_publish(struct mana_port_context *apc)
{
unsigned int i;
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 415422aa68672accd03c161732fa1e96dd5562ba..a1c24d41903c100fdd5155e80b068ed9eaca26f4 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -660,7 +660,7 @@ static int mana_set_channels(struct net_device *ndev,
struct mana_port_context *apc = netdev_priv(ndev);
unsigned int new_count = channels->combined_count;
struct mana_port_context *scratch;
- struct mana_qset newq, oldq;
+ struct mana_qset newq, oldq, freshq;
int err;
if (new_count < 1 || new_count > apc->max_queues) {
@@ -755,19 +755,41 @@ static int mana_set_channels(struct net_device *ndev,
goto free_scratch;
}
- err = mana_alloc_qset(apc, scratch, new_count, apc->rx_queue_size,
- apc->tx_queue_size, apc->priv_flags,
- apc->configured_mtu, apc->bpf_prog, &newq);
+ /* An increase does not change the queues that already exist either, so
+ * carry them over as well and build only the queues being added. The
+ * peak stays at the new count instead of old + new.
+ */
+ err = mana_grow_qset(apc, scratch, new_count, &newq, &freshq);
if (err)
goto free_scratch; /* current qset untouched, nothing to undo */
err = mana_publish_qset(apc, &newq, &oldq);
if (err) {
- mana_free_qset(apc, scratch, &newq);
+ /* The old set is live again. Retire the queues that were just
+ * built - @freshq names exactly those - and then drop the
+ * merged containers without touching the carried-over queues.
+ */
+ mana_free_qset(apc, scratch, &freshq);
+ mana_discard_grow(&newq);
goto free_scratch;
}
- mana_free_qset(apc, scratch, &oldq);
+ /* Nothing is retired by a grow: every queue @oldq referenced is now
+ * part of the published set, and so is every queue in @freshq. Only
+ * the containers of both are released here.
+ */
+ kfree(oldq.tx_qp);
+ kfree(oldq.rxqs);
+ kfree(oldq.indir_table);
+ kfree(oldq.rxobj_table);
+ kfree(freshq.tx_qp);
+ kfree(freshq.rxqs);
+
+ /* A grow retires nothing, so mana_free_qset() never runs to hand out
+ * the debugfs names. The queues that were just added are the only
+ * ones missing a node, and no retiring set is holding their names.
+ */
+ mana_qset_debugfs_publish(apc);
free_scratch:
mana_publish_close_if_needed(apc);
@@ -848,7 +870,7 @@ static int mana_set_ringparam(struct net_device *ndev,
goto clear_flag;
}
- err = mana_alloc_qset(apc, scratch, apc->num_queues, new_rx, new_tx,
+ err = mana_alloc_qset(apc, scratch, new_rx, new_tx,
apc->priv_flags, apc->configured_mtu,
apc->bpf_prog, &newq);
if (err) {
@@ -868,9 +890,6 @@ static int mana_set_ringparam(struct net_device *ndev,
mana_free_qset(apc, scratch, &oldq);
free_scratch:
- /* After the caller-side cleanup above, so the EQ pool outlives the
- * CQs that reference it.
- */
mana_publish_close_if_needed(apc);
mana_qset_scratch_free(scratch);
clear_flag:
@@ -947,7 +966,7 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
goto clear_flag;
}
- err = mana_alloc_qset(apc, scratch, apc->num_queues, apc->rx_queue_size,
+ err = mana_alloc_qset(apc, scratch, apc->rx_queue_size,
apc->tx_queue_size, priv_flags,
apc->configured_mtu, apc->bpf_prog, &newq);
if (err)
diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h
index 70a7f1fee5d3b0a6460cbd76159d01a369838d68..c54500700f6f2f4b432102364e64fd021e7c2c88 100644
--- a/include/net/mana/gdma.h
+++ b/include/net/mana/gdma.h
@@ -672,6 +672,14 @@ enum {
/* Driver supports dynamic interrupt moderation - DIM */
#define GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION BIT(28)
+/* Driver recovers by itself when a queue resize fails: a failed resize leaves
+ * the queues that were already serving traffic in place, so the host does not
+ * have to bring the port back. This covers the resize itself failing. It does
+ * not promise recovery when restoring the previous queue set fails too, which
+ * leaves the port administratively down for the admin to bring back up.
+ */
+#define GDMA_DRV_CAP_FLAG_1_SELF_RECOVERY_ON_QUEUE_RESIZE_FAILURE BIT(31)
+
#define GDMA_DRV_CAP_FLAGS1 \
(GDMA_DRV_CAP_FLAG_1_EQ_SHARING_MULTI_VPORT | \
GDMA_DRV_CAP_FLAG_1_NAPI_WKDONE_FIX | \
@@ -688,7 +696,8 @@ enum {
GDMA_DRV_CAP_FLAG_1_HANDLE_STALL_SQ_RECOVERY | \
GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECOVERY | \
GDMA_DRV_CAP_FLAG_1_EQ_MSI_UNSHARE_MULTI_VPORT | \
- GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION)
+ GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION | \
+ GDMA_DRV_CAP_FLAG_1_SELF_RECOVERY_ON_QUEUE_RESIZE_FAILURE)
#define GDMA_DRV_CAP_FLAGS2 0
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index cd41a135815710162b1e5fd3edafa119ae12d1dd..8857e7739d2c2505255403defb09749c85804922 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -756,7 +756,7 @@ int mana_detach(struct net_device *ndev, bool from_close);
struct mana_port_context *mana_qset_scratch_alloc(struct mana_port_context *apc);
void mana_qset_scratch_free(struct mana_port_context *scratch);
int mana_alloc_qset(struct mana_port_context *apc,
- struct mana_port_context *scratch, unsigned int num_queues,
+ struct mana_port_context *scratch,
unsigned int rx_queue_size, unsigned int tx_queue_size,
u32 priv_flags, int mtu, struct bpf_prog *bpf_prog,
struct mana_qset *out);
@@ -764,11 +764,16 @@ int mana_split_qset(struct mana_port_context *apc,
struct mana_port_context *scratch, unsigned int new_count,
struct mana_qset *out_new, struct mana_qset *out_tail);
void mana_discard_split(struct mana_qset *newq, struct mana_qset *tailq);
+int mana_grow_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, unsigned int new_count,
+ struct mana_qset *out_new, struct mana_qset *out_fresh);
+void mana_discard_grow(struct mana_qset *newq);
int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
struct mana_qset *out_old);
void mana_publish_close_if_needed(struct mana_port_context *apc);
void mana_free_qset(struct mana_port_context *apc,
struct mana_port_context *scratch, struct mana_qset *qset);
+void mana_qset_debugfs_publish(struct mana_port_context *apc);
void mana_dim_change(struct mana_cq *cq, bool enable);
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-08-13 5:05 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-13 5:04 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
2026-08-13 5:04 ` [PATCH net-next v2 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
2026-08-13 5:04 ` [PATCH net-next v2 02/13] net: mana: swap queue sets in mana_set_channels Long Li
2026-08-13 5:04 ` [PATCH net-next v2 03/13] net: mana: swap queue sets in mana_set_ringparam Long Li
2026-08-13 5:04 ` [PATCH net-next v2 04/13] net: mana: swap queue sets in mana_set_priv_flags Long Li
2026-08-13 5:04 ` [PATCH net-next v2 05/13] net: mana: swap queue sets in mana_change_mtu Long Li
2026-08-13 5:04 ` [PATCH net-next v2 06/13] net: mana: swap queue sets in mana_xdp_set Long Li
2026-08-13 5:04 ` [PATCH net-next v2 07/13] net: mana: do not bail out of mana_detach on dealloc failure Long Li
2026-08-13 5:04 ` [PATCH net-next v2 08/13] net: mana: keep per-queue statistics in the port context Long Li
2026-08-13 5:04 ` [PATCH net-next v2 09/13] net: mana: share the EQ pool across a queue-set swap Long Li
2026-08-13 5:04 ` [PATCH net-next v2 10/13] net: mana: release EQs left idle by a channel-count reduction Long Li
2026-08-13 5:04 ` [PATCH net-next v2 11/13] net: mana: keep a user-configured RSS table across a queue rebuild Long Li
2026-08-13 5:04 ` [PATCH net-next v2 12/13] net: mana: keep the surviving queues when the channel count is reduced Long Li
2026-08-13 5:04 ` [PATCH net-next v2 13/13] net: mana: keep the existing queues when the channel count is raised Long Li
-- strict thread matches above, loose matches on Subject: below --
2026-08-11 6:35 [PATCH net-next v2 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
2026-08-11 6:35 ` [PATCH net-next v2 10/13] net: mana: release EQs left idle by a channel-count reduction Long Li
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox