* [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set
@ 2026-08-11 6:34 Long Li
2026-08-11 6:34 ` [PATCH net-next v2 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
` (9 more replies)
0 siblings, 10 replies; 11+ messages in thread
From: Long Li @ 2026-08-11 6:34 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.
[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 | 117 +-
drivers/net/ethernet/microsoft/mana/mana_en.c | 1698 ++++++++++++++++-
.../ethernet/microsoft/mana/mana_ethtool.c | 333 +++-
include/net/mana/gdma.h | 11 +-
include/net/mana/mana.h | 130 +-
5 files changed, 2066 insertions(+), 223 deletions(-)
base-commit: d67e5dbda22604d0fcde32fce58c65f88676e676
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH net-next v2 01/13] net: mana: add queue-set allocation and teardown helpers
2026-08-11 6:34 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
@ 2026-08-11 6:34 ` Long Li
2026-08-11 6:34 ` [PATCH net-next v2 02/13] net: mana: swap queue sets in mana_set_channels Long Li
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Long Li @ 2026-08-11 6:34 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) and mana_change_mtu()/mana_xdp_set() all rebuild the queues by
calling mana_detach() followed by mana_attach(). That tears the vport
down and rebuilds it, so the RDMA driver can claim the vport while it
is released, and if mana_attach() fails the port is left down with no
way back except manual intervention.
Introduce the data model for replacing that with pre-allocate and swap.
struct mana_qset holds the queue-related fields of
mana_port_context that can be rebuilt independently of the vport: the
EQ/TXQ/RXQ arrays, the indirection and rxobj tables, and the
configuration those queues were built for.
mana_alloc_qset() builds a complete queue set and mana_free_qset()
destroys one. Both run against a scratch mana_port_context obtained
from mana_qset_scratch_alloc() - a heap copy that shares the port's
vport identity but owns no queues - so the live port context is never
made to point at queues that are still being built or freed.
Running the allocators against a scratch context rather than
temporarily clearing the live one is essential, not cosmetic.
mana_start_xmit() dereferences apc->tx_qp[] guarded only by
apc->port_is_up, and mana_alloc_qset() takes 50-300ms including
firmware calls. An earlier revision of this work cleared apc->tx_qp so
the existing allocators could build into the live context, and reliably
panicked under traffic:
RIP: 0010:mana_start_xmit+0x138/0x1040 ; apc->tx_qp[txq_idx]
RAX: 0000000000000000 CR2: 0000000000000000
Kernel panic - not syncing: Fatal exception in interrupt
The scratch context also gets rxbufs_pre = NULL so it never consumes
the live set's preallocated RX buffers, and mana_port_debugfs =
ERR_PTR(-ENODEV). debugfs_start_creating() returns early on an IS_ERR()
parent and debugfs_remove() ignores IS_ERR_OR_NULL, which makes every
create and remove a no-op for swapped-in sets. Without this the two
live sets collide on the same names under vport%d.
mana_dealloc_queues()'s inline TX drain moves into mana_drain_txqs() so
the new teardown path gets it too: a retiring queue set can still hold
packets the device has not completed, and freeing the SQs and the SKB
queues underneath them would leak those SKBs and their DMA mappings.
While moving it, the reset that the drain falls back to when the
hardware stops responding becomes pci_try_reset_function() instead of
an open-coded pcie_flr(). pcie_flr() resets the function without saving
and restoring config space, so BARs, MSI-X state and bus master enable
are wiped while the PCI core still believes its cached values are
live. pci_try_reset_function() brackets the reset with
pci_dev_save_and_disable() and pci_dev_restore(). The trylock variant
is required rather than pci_reset_function(): this runs under RTNL,
while driver removal takes the device lock first and RTNL second, so
blocking on the device lock here could deadlock.
Other than that reset change, no functional change: 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 | 520 ++++++++++++++++--
include/net/mana/mana.h | 64 +++
3 files changed, 577 insertions(+), 38 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..bf15222deb77679257f46d36800fdc4006c612a0 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,143 @@ int mana_attach(struct net_device *ndev)
return 0;
}
-static int mana_dealloc_queues(struct net_device *ndev)
+/* Drain the TX queues of a set that is about to be destroyed.
+ *
+ * No new packet can reach these queues: either the port is down
+ * (mana_dealloc_queues()) or the set has already been unpublished from the
+ * live port context (mana_free_qset()). Wait for the hardware to complete
+ * what it still owns, then release every SKB left mapped.
+ *
+ * A total timeout of 120 seconds is used across all the queues. This breaks
+ * the loop when the h/w is not responding; the device is then reset, because
+ * the buffers are about to be freed while it may still DMA into them. This
+ * value of 120 has been decided considering the max number of queues. If that
+ * reset also fails, the buffers are deliberately leaked rather than handed
+ * back to a device that can still reach them.
+ *
+ * Returns true if the device was successfully reset, which takes down every
+ * queue on the function, not just the ones being retired here. A reset that
+ * was attempted but failed returns false: nothing was taken down, so the
+ * other ports have no reason to rebuild.
+ */
+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 has been reset since this queue was created, so
+ * the device has already stopped touching its buffers. The
+ * completions being waited for below can never arrive: without
+ * this the port a reset took down would spend the full timeout
+ * here, under RTNL, and then reset the function again on its
+ * way out, taking every other port down with it in turn.
+ */
+ 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 it has to be
+ * reset before they are freed.
+ *
+ * pci_try_reset_function() rather than a bare
+ * pcie_flr(): it saves and restores config space (BARs,
+ * MSI-X, bus master) around the reset, which an
+ * open-coded FLR wipes without the PCI core ever
+ * knowing, leaving the device wedged. The trylock form
+ * is deliberate: RTNL is held here, while the driver
+ * remove path takes the device lock first and RTNL
+ * second, so blocking on the device lock could deadlock.
+ */
+ 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' queues down with it. Reporting
+ * one that failed would send every sibling
+ * through a needless down/up while their queues
+ * are still perfectly good.
+ */
+ reset = true;
+ }
+ break;
+ }
+ }
+
+ /* The reset is what makes freeing these buffers safe. Without it the
+ * device still owns them: unmapping would fault the IOMMU on the next
+ * descriptor it reads, and on a system without one it would go on
+ * reading memory that has already been handed to someone else and put
+ * whatever it finds there on the wire. Leaking is the lesser evil.
+ *
+ * What is leaked is bounded: at most one SQ ring's worth of skbs per
+ * queue, since mana_can_tx() stops the txq once the ring is full. And
+ * it can only recur after another full drain timeout followed by
+ * another failed function reset. Reaching this point at all means the
+ * device left TX outstanding for two minutes, so it is already broken;
+ * pci_try_reset_function() then failing for a transient reason, such
+ * as the trylock losing to a concurrent PCI operation, only decides
+ * whether these particular buffers can be recovered, not whether the
+ * device was healthy.
+ */
+ 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 +3835,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 port that is already down returns early from its
+ * handler.
*/
+ 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 +3879,324 @@ 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 (ac, ndev,
+ * port_handle, indir_table_sz, mac_addr, hashkey, ...) but owns no queues.
+ * The existing per-apc allocators and destroyers can then be run against
+ * it without ever 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);
+}
+
+/**
+ * mana_alloc_qset - build a complete queue set in @scratch
+ * @scratch: scratch context from mana_qset_scratch_alloc()
+ * @num_queues: number of queues in the new set
+ * @rx_queue_size: new RX ring size
+ * @tx_queue_size: new TX ring size
+ * @priv_flags: new priv-flag word (affects full-page RX)
+ * @out: output qset, populated on success
+ *
+ * The live port context is not referenced at all, so the currently
+ * running queue set keeps serving traffic throughout. 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;
+}
+
+/**
+ * mana_free_qset - tear down all queues in @qset
+ * @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)
+{
+ 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
+ * returns early.
+ */
+ 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 +4674,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/include/net/mana/mana.h b/include/net/mana/mana.h
index 83b7eff4646ead7aef1382c6ce565a573a940af4..4727c231bf0391bd9a1e9ff15c65815a6a3d01dd 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,14 @@ struct mana_context {
u8 bm_hostmode;
struct mana_ethtool_hc_stats hc_stats;
+
+ /* Bumped every time the PCI function is reset to unstick a TX queue.
+ * A queue created before the current value cannot be touched by the
+ * device any more, so nothing has to be waited for before its buffers
+ * are released. 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 +679,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 +721,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 path. Allocation and teardown run
+ * against a scratch context so the live port context is never made to
+ * point at queues that are still being built or freed.
+ */
+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 +747,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] 11+ messages in thread
* [PATCH net-next v2 02/13] net: mana: swap queue sets in mana_set_channels
2026-08-11 6:34 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
2026-08-11 6:34 ` [PATCH net-next v2 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
@ 2026-08-11 6:34 ` Long Li
2026-08-11 6:35 ` [PATCH net-next v2 03/13] net: mana: swap queue sets in mana_set_ringparam Long Li
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Long Li @ 2026-08-11 6:34 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
Add mana_publish_qset(), which installs a queue set built by
mana_alloc_qset() onto the live port context and hands the previous set
back to the caller, and use it to convert mana_set_channels() away from
detach/attach.
The publish ordering is:
carrier off -> netif_tx_disable -> swap pointers -> update netdev
queue counts -> reprogram RSS/steering -> netif_tx_start_all_queues
-> carrier restore
netif_tx_disable() is load-bearing. mana_start_xmit() dereferences
apc->tx_qp[] guarded only by apc->port_is_up, so swapping the array with
TX live would let an in-flight xmit use a pointer that the caller is
about to free, and would let ndo_select_queue() hand out an index
derived from a stale real_num_tx_queues.
The old set's NAPI keeps running between the swap and mana_free_qset().
That is safe: the old CQs and RQs are still fully constructed, and
mana_destroy_txq()/mana_destroy_rxq() perform napi_synchronize() and
napi_disable_locked() before freeing anything.
If mana_publish_qset() fails partway it reinstalls the old set and
points steering back at it, so the caller only has to free the new set.
For mana_set_channels() this removes the failure mode entirely. The new
queues are built before anything is torn down, so an allocation failure
returns the error with the running configuration untouched, and the
value the user asked for is never silently replaced by a fallback. The
vport is never released, so vport_use_count stays above zero and RDMA
cannot claim it mid-reconfiguration.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 7 +
drivers/net/ethernet/microsoft/mana/mana_en.c | 402 +++++++++++++++++-
.../ethernet/microsoft/mana/mana_ethtool.c | 103 ++++-
include/net/mana/mana.h | 13 +
4 files changed, 502 insertions(+), 23 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 bf15222deb77679257f46d36800fdc4006c612a0..5f682e3d5a706149954764d083b70f25906f28df 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -363,6 +363,23 @@ 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 queue set can be
+ * replaced while the port stays up, and the gate above is what says
+ * the new one is installed. The check below and the dereference must
+ * not be satisfied from before it, or a stale apc->num_queues would
+ * admit an index that is past the end of a freshly shrunk
+ * apc->tx_qp[]. A control dependency does not order loads.
+ */
+ smp_rmb();
+
+ /* The stack keeps skb->queue_mapping below real_num_tx_queues, but an
+ * XDP_TX from a queue set that is being retired carries the index of
+ * its own RX queue, which can be past the end of a smaller replacement
+ * apc->tx_qp[]. Drop rather than dereference outside the array.
+ */
+ if (unlikely(txq_idx >= apc->num_queues))
+ goto tx_drop_count;
+
if (skb_cow_head(skb, MANA_HEADROOM))
goto tx_drop_count;
@@ -1042,6 +1059,12 @@ 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 sized for
+ * the new queue count, 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 +2038,12 @@ static void mana_poll_tx_cq(struct mana_cq *cq)
/* Ensure checking txq_stopped before apc->port_is_up. */
smp_rmb();
+ /* The retiring test is ordered by the same barrier. Reaching it with
+ * txq_stopped set means the queue that replaced this one has already
+ * run and stopped net_txq, which happens strictly after
+ * mana_publish_qset() marked this queue retiring and reopened the
+ * gate, so the flag read here cannot predate that store.
+ */
if (txq_stopped && !READ_ONCE(txq->retiring) && apc->port_is_up &&
avail_space >= MAX_TX_WQE_SIZE) {
netif_tx_wake_queue(net_txq);
@@ -3890,10 +3919,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
@@ -4079,6 +4114,353 @@ int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
return err;
}
+/* Stop the device delivering into whichever queue set steering still names.
+ *
+ * Used when mana_publish_qset() cannot restore a consistent configuration:
+ * the caller is about to free the set that was being published, and steering
+ * may still point at it. Disabling vport RX stops delivery to every RQ of
+ * this vport, so destroying those queues afterwards is safe; the fence then
+ * waits for RX the device had already accepted. Same order as the teardown
+ * in mana_dealloc_queues(). The port is left down; a down/up rebuilds it.
+ */
+/* Last resort when a swap fails and the previous queue set cannot be put back
+ * the way it was.
+ *
+ * Leaving the port up is not an option: the netdev queue counts no longer
+ * describe apc->tx_qp[], or steering no longer matches apc->rxqs[]. Leaving it
+ * merely stopped is not either, because apc->port_is_up would be false while
+ * the queues are still allocated, and mana_detach() only tears queues down
+ * when it observes the port up. A later close would skip the teardown and the
+ * next open would trip WARN_ON(apc->eqs) in mana_create_eq(), leaving the port
+ * unusable for good.
+ *
+ * So close it properly: destroy what is installed and leave the clean
+ * "administratively down" state that mana_open() knows how to rebuild from.
+ */
+/**
+ * mana_publish_close_if_needed - close a port mana_publish_qset() gave up on
+ * @apc: port context
+ *
+ * mana_publish_qset() cannot do this itself. Closing destroys the shared EQ
+ * pool, and the queue set it failed to publish still owns CQs attached to
+ * those EQs, so the EQs have to go last - the same order mana_dealloc_queues()
+ * uses. Only the caller knows how to release that set, since a rebuild owns
+ * its queues outright while a grow or a shrink shares some with the live set.
+ * So the caller releases it first and calls this afterwards.
+ *
+ * RX delivery is already off by then: mana_publish_qset() disables vport RX
+ * before it gives up, so the caller is never freeing queues the device can
+ * still write into.
+ *
+ * Does nothing unless mana_publish_qset() actually hit that dead end. Must be
+ * called under RTNL.
+ */
+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 the netdev queues that can actually take work.
+ *
+ * netif_tx_disable() stops every queue for the duration of a swap, including
+ * one that is carried over with its ring already full. Starting that queue
+ * again unconditionally would hand the stack a queue with no room, and
+ * mana_start_xmit() would drop the packet it gets. Leave those stopped: the
+ * completion that frees space wakes them through mana_poll_tx_cq(), the same
+ * way it would have without a swap. Must run after apc->port_is_up is set,
+ * or that wakeup path is still 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));
+ }
+}
+
+/* Mark or unmark every TX queue in @qset as on its way out.
+ *
+ * A retiring queue shares its struct netdev_queue with whatever replaced it
+ * at the same index, and it 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 because its ring was full.
+ *
+ * A queue that both sets own, which is what a grow or a shrink carries over,
+ * must end up unmarked: callers apply this to the set that is leaving first
+ * and to the set that is becoming live 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: the port has to go down, and the caller is about to free
+ * a queue set that steering may still point at.
+ *
+ * mana_free_qset() skips the vport-level RX disable because a completed
+ * publish has already repointed steering at the set that stays. Here it has
+ * not: restoring steering is exactly what failed. So stop the device
+ * delivering now, before the caller destroys those RQs and frees the buffers
+ * the device would otherwise keep writing into.
+ *
+ * This is the narrow steering request - no key, indirection table or
+ * default-rxobj update - so it can still land where the full
+ * mana_config_rss() restore did not.
+ *
+ * Only RX delivery is stopped here. Destroying the port is left to
+ * mana_publish_close_if_needed(), because that tears down the shared EQ pool,
+ * which has to outlive the CQs the caller has yet to release.
+ */
+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;
+}
+
+/**
+ * mana_publish_qset - swap @newq onto @apc, returning the old set
+ * @apc: live port context
+ * @newq: new queue set (from mana_alloc_qset)
+ * @out_old: filled with the previous queue set, for the caller to free
+ *
+ * Publishes the new set in this order:
+ *
+ * carrier off -> netif_tx_disable -> swap pointers -> update netdev
+ * queue counts -> reprogram RSS/steering -> reattach XDP ->
+ * netif_tx_start_all_queues -> carrier restore
+ *
+ * netif_tx_disable() is load-bearing: mana_start_xmit() dereferences
+ * apc->tx_qp[] guarded only by apc->port_is_up, so swapping the array with
+ * TX live would let an in-flight xmit use a stale pointer. Stopping the
+ * queues also keeps ndo_select_queue() from handing out an index derived
+ * from a stale real_num_tx_queues. ndo_xdp_xmit() does not go through the
+ * txq-stopped checks at all, so apc->port_is_up is cleared across the same
+ * window to fence it out.
+ *
+ * On failure the old set is reinstalled and steering pointed back at it,
+ * so the caller only has to free @newq.
+ *
+ * Must be called under RTNL.
+ *
+ * NOTE: the old set's NAPI keeps running until mana_free_qset(); that is
+ * safe because the old CQs/RQs are still fully constructed, and
+ * mana_destroy_txq()/mana_destroy_rxq() perform napi_synchronize() +
+ * napi_disable_locked() before freeing anything.
+ */
+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, and pair the store with
+ * the smp_rmb() in mana_poll_tx_cq(). That reader samples
+ * netif_tx_queue_stopped() first, so publishing in this order means a
+ * completion that sees a queue this function stopped also sees
+ * port_is_up false and will not wake it back up mid-swap. This is the
+ * same protocol mana_detach() uses; doing it the other way round let a
+ * TX completion re-enable a queue that had just been disabled.
+ *
+ * It also fences ndo_xdp_xmit(): mana_xdp_xmit() runs from a
+ * redirecting device's NAPI, bypasses the txq-stopped checks and is
+ * gated only by apc->port_is_up. Left running it could pick q_idx from
+ * a real_num_tx_queues that no longer matches the freshly installed
+ * (possibly smaller) apc->tx_qp[], or dereference the retiring array.
+ */
+ 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 below, not after it.
+ * A completion that samples the flag clear must not still be in
+ * flight once the new set is installed and the gate reopens, or it
+ * could resume and wake a netdev queue that the queue replacing it
+ * had already stopped because its ring was full.
+ */
+ 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();
+
+ /* Whatever the incoming set carries over from the outgoing one is
+ * staying, so it is not retiring. Ordered after the marking above,
+ * and still 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 to the new RXQs before steering can
+ * reach them; they were created with bpf_prog == NULL, so a packet
+ * arriving first would take the pass path despite a program being
+ * attached. 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;
+}
+
+/**
+ * mana_qset_debugfs_publish - give live queues the debugfs nodes they lack
+ * @apc: live port context
+ *
+ * Queues built through a scratch context are created with debugfs suppressed,
+ * so that the incoming and outgoing sets do not fight over the same names
+ * under vport%d while both are alive. Once the retiring set is gone its names
+ * are free, and the queues that are now serving traffic can take them.
+ *
+ * Idempotent: a queue that already has a node, such as one carried over by a
+ * grow or a shrink, is left alone. Suppressed creation leaves the error
+ * pointer from the scratch parent behind rather than NULL, so both have to
+ * count as "no node". Must be called 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);
+ }
+}
+
/**
* mana_free_qset - tear down all queues in @qset
* @scratch: scratch context from mana_qset_scratch_alloc()
@@ -4128,6 +4510,15 @@ void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
mana_qset_install(scratch, qset);
+ /* Teardown from here follows the same order as mana_dealloc_queues(),
+ * with one exception: no vport-level RX disable. mana_publish_qset()
+ * has already pointed steering at the incoming set, so nothing new is
+ * delivered to these queues, and disabling vport RX would stop the set
+ * that is now live. In the one case where it could not repoint
+ * steering, it disabled vport RX itself before returning, so the
+ * "nothing new is delivered" invariant holds there too.
+ */
+
/* 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
@@ -4193,6 +4584,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 ece7ff9cc409a806b6a6de70a85b44874bfa6dad..43ae71eb6b5fecc8973bf7aea4ebbfffd19bd7cd 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -650,50 +650,111 @@ static int mana_set_coalesce(struct net_device *ndev,
/* 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.
+ * Uses the pre-allocate + swap path (mana_alloc_qset / mana_publish_qset
+ * / mana_free_qset). If allocation of the new queue set fails, the
+ * existing queues keep running unchanged and we simply return -ENOMEM;
+ * the user's requested setting is never silently mutated to a fallback
+ * value. The vport is never torn down, so RDMA cannot race in and take
+ * ownership of it during the reconfiguration window.
*/
static int mana_set_channels(struct net_device *ndev,
struct ethtool_channels *channels)
{
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;
+
+ /* If the port is administratively down there are no queues to swap,
+ * so record the new count and resize the arrays that are indexed by
+ * it. mana_open() will use the new value.
+ *
+ * apc->rxqs has to grow here: it is sized by apc->num_queues in
+ * mana_init_port_context(), and mana_open() goes straight to
+ * mana_alloc_queues() without rebuilding the port context, so
+ * mana_add_rx_queues() would write past the end of the old array.
+ *
+ * RDMA can still own the vport while the ethernet port is down, and
+ * it derives an EQ from apc->eqs[] modulo apc->num_queues, so the
+ * count must not move underneath it. Take the same mutex the swap
+ * path uses and refuse the change while the vport 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. Unlike the
+ * detach/attach path this is a much shorter window and the vport
+ * never actually goes idle (vport_use_count stays > 0).
+ *
+ * Deliberately no vport_use_count test here, unlike the port-down
+ * branch above: bringing the port up takes the vport itself
+ * (mana_create_vport()), so the count is always non-zero at this
+ * point and testing it would fail every call. That reference is also
+ * what makes the swap safe, since mana_cfg_vport() refuses a second
+ * holder and 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 4727c231bf0391bd9a1e9ff15c65815a6a3d01dd..e4aaad7bec34297cf406181560a1839d3e6a73df 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -634,6 +634,16 @@ struct mana_port_context {
*/
bool channel_changing;
+ /* mana_publish_qset() could neither publish the new queue set nor put
+ * the previous one back. It has already disabled vport RX, so nothing
+ * is being delivered; what is left is closing the port, and that can
+ * only happen after the caller has torn down the set it was trying to
+ * publish: those queues own CQs attached to the shared EQ pool, and
+ * closing destroys that pool. Checked through
+ * mana_publish_close_if_needed().
+ */
+ bool publish_dead_end;
+
/* Net shaper handle*/
struct net_shaper_handle handle;
@@ -730,6 +740,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] 11+ messages in thread
* [PATCH net-next v2 03/13] net: mana: swap queue sets in mana_set_ringparam
2026-08-11 6:34 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
2026-08-11 6:34 ` [PATCH net-next v2 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
2026-08-11 6:34 ` [PATCH net-next v2 02/13] net: mana: swap queue sets in mana_set_channels Long Li
@ 2026-08-11 6:35 ` Long Li
2026-08-11 6:35 ` [PATCH net-next v2 04/13] net: mana: swap queue sets in mana_set_priv_flags Long Li
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ 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
Convert the ring size path to the pre-allocate and swap helpers, for the
same reasons as mana_set_channels(): the new queues are built before
anything is torn down, so an allocation failure returns the error with
the running configuration untouched, and the vport is never released so
RDMA cannot claim it mid-reconfiguration.
This also drops the fallback behaviour on failure. Previously, when
mana_attach() failed, the code retried with ring sizes chosen to
maximise the chance of recovery - the previous values, or the defaults,
or the minimums - and scheduled queue_reset_work(). A user who asked for
a specific ring size could therefore end up with a different one, with
no indication beyond dmesg. There is nothing to recover from now, so the
error is simply returned and the requested value is never silently
replaced.
Add an early return 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 | 81 +++++++++++++------
1 file changed, 58 insertions(+), 23 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 43ae71eb6b5fecc8973bf7aea4ebbfffd19bd7cd..bff6f69a9457c04c3555e9ef418b0e83b8e54d0d 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -775,19 +775,25 @@ static void mana_get_ringparam(struct net_device *ndev,
ring->tx_max_pending = MAX_TX_BUFFERS_PER_QUEUE;
}
+/* mana_set_ringparam - resize the RX/TX rings
+ *
+ * Uses the pre-allocate + swap path: the new queue set is built with the
+ * requested ring sizes while the current one keeps serving traffic. If the
+ * allocation fails the running configuration is untouched and the error is
+ * returned - the requested sizes are never silently replaced by fallback
+ * values.
+ */
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);
@@ -805,32 +811,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);
+
+ scratch = mana_qset_scratch_alloc(apc);
+ if (!scratch) {
+ err = -ENOMEM;
+ goto clear_flag;
}
- apc->tx_queue_size = new_tx;
- apc->rx_queue_size = new_rx;
+ err = mana_alloc_qset(scratch, apc->num_queues, new_rx, new_tx,
+ apc->priv_flags, &newq);
+ if (err) {
+ NL_SET_ERR_MSG_FMT(extack, "failed to change ring params: %d",
+ err);
+ goto free_scratch; /* current qset untouched */
+ }
- err = mana_attach(ndev);
+ err = mana_publish_qset(apc, &newq, &oldq);
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);
+ 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;
+ mutex_unlock(&apc->vport_mutex);
return err;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH net-next v2 04/13] net: mana: swap queue sets in mana_set_priv_flags
2026-08-11 6:34 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (2 preceding siblings ...)
2026-08-11 6:35 ` [PATCH net-next v2 03/13] net: mana: swap queue sets in mana_set_ringparam Long Li
@ 2026-08-11 6:35 ` Long Li
2026-08-11 6:35 ` [PATCH net-next v2 05/13] net: mana: swap queue sets in mana_change_mtu Long Li
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ 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
MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF changes the RX buffer layout, so
toggling it rebuilds the queues. Convert that path to the pre-allocate
and swap helpers, for the same reasons as the channel count and ring
size paths: an allocation failure now returns the error with both the
queues and the flag word untouched, and the vport is never released so
RDMA cannot claim it mid-reconfiguration.
The flag word is no longer written before the rebuild and rolled back on
failure. It is passed to mana_alloc_qset() as part of the queue-set
configuration and installed by mana_publish_qset() only once the new set
is serving traffic, so there is no window where apc->priv_flags
describes queues that do not exist.
Scheduling queue_reset_work() on failure is dropped along with it. After
this patch the TX timeout handler is the only remaining user of
queue_reset_work().
The existing shortcuts are unchanged in behaviour - a down port, or a
configuration where single-buffer-per-page is already forced by a jumbo
MTU or an attached XDP program, still just records the new value - but
they now share one condition instead of being spread across the
function.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../ethernet/microsoft/mana/mana_ethtool.c | 85 ++++++++++---------
1 file changed, 47 insertions(+), 38 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index bff6f69a9457c04c3555e9ef418b0e83b8e54d0d..9392b82d3d48a2638512a53f9c004629b0c679e5 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -891,11 +891,18 @@ static u32 mana_get_priv_flags(struct net_device *ndev)
return apc->priv_flags;
}
+/* mana_set_priv_flags - apply a change to the driver private flags
+ *
+ * MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF changes the RX buffer layout, so the
+ * queues must be rebuilt. Uses the pre-allocate + swap path, so a failed
+ * allocation leaves both the queues and the flag word untouched.
+ */
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)
@@ -905,54 +912,56 @@ 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:
+ /* 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] 11+ messages in thread
* [PATCH net-next v2 05/13] net: mana: swap queue sets in mana_change_mtu
2026-08-11 6:34 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (3 preceding siblings ...)
2026-08-11 6:35 ` [PATCH net-next v2 04/13] net: mana: swap queue sets in mana_set_priv_flags Long Li
@ 2026-08-11 6:35 ` Long Li
2026-08-11 6:35 ` [PATCH net-next v2 06/13] net: mana: swap queue sets in mana_xdp_set Long Li
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ 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 RX buffer layout depends on the MTU - see mana_get_rxbuf_cfg() - so
changing it rebuilds the queues. Convert mana_change_mtu() to the
pre-allocate and swap helpers.
The MTU becomes part of the queue-set configuration:
- struct mana_qset gains mtu, so it is swapped atomically with the
queues it describes and restored by the rollback path.
- mana_port_context gains configured_mtu, the MTU the current RX queues
were built for. mana_create_rxq() now sizes buffers from it rather
than ndev->mtu, so a new set can be built for a different MTU while
ndev->mtu still advertises the old one.
- mana_publish_qset() commits ndev->mtu once the new set is serving
traffic. It is a no-op for callers that do not change the MTU.
Previously ndev->mtu was written before mana_attach() and rolled back on
failure, so a failed change was briefly visible to the stack.
mana_pre_alloc_rxbufs() is no longer needed here; de-risking allocation
after teardown is pointless when there is no teardown.
Signed-off-by: Long Li <longli@microsoft.com>
---
drivers/net/ethernet/microsoft/mana/mana_en.c | 75 ++++++++++++++-----
.../ethernet/microsoft/mana/mana_ethtool.c | 8 +-
include/net/mana/mana.h | 14 +++-
3 files changed, 74 insertions(+), 23 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 5f682e3d5a706149954764d083b70f25906f28df..4c79d6e82d9a93786ea877fbcef2b3f9d4093a6c 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -889,35 +889,54 @@ int mana_pre_alloc_rxbufs(struct mana_port_context *mpc, int new_mtu, int num_qu
return -ENOMEM;
}
+/* mana_change_mtu - resize RX buffers for a new MTU
+ *
+ * Uses the pre-allocate + swap path: the new queue set is built for
+ * @new_mtu while the current one keeps serving traffic at the old MTU.
+ * ndev->mtu is only updated once the new set is live (see
+ * mana_publish_qset), so a failed allocation leaves both 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;
}
@@ -3097,7 +3116,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);
@@ -3965,6 +3985,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;
}
@@ -3986,6 +4007,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;
}
@@ -4049,6 +4071,7 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
* @rx_queue_size: new RX ring size
* @tx_queue_size: new TX ring size
* @priv_flags: new priv-flag word (affects full-page RX)
+ * @mtu: MTU the new set is sized for
* @out: output qset, populated on success
*
* The live port context is not referenced at all, so the currently
@@ -4057,7 +4080,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;
@@ -4069,6 +4092,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;
@@ -4361,6 +4390,11 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
if (err)
goto rollback;
+ /* The new set is serving traffic: advertise its MTU. A no-op for
+ * callers that are not changing the MTU.
+ */
+ 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.
*/
@@ -4413,6 +4447,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.
*/
@@ -4658,6 +4694,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 9392b82d3d48a2638512a53f9c004629b0c679e5..2a6325256fad3edf1aafbc72f0cd5dc18d61384e 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -737,7 +737,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 */
@@ -839,7 +840,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);
@@ -940,7 +941,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 e4aaad7bec34297cf406181560a1839d3e6a73df..2fc75e6cd41a63878788182cd099379c8c698b33 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -621,6 +621,12 @@ struct mana_port_context {
unsigned int rx_queue_size;
unsigned int tx_queue_size;
+ /* MTU the current RX queues were built for. Normally equal to
+ * ndev->mtu, but during a queue-set swap the new set is built for
+ * the new MTU while ndev->mtu still advertises the old one.
+ */
+ int configured_mtu;
+
mana_handle_t port_handle;
mana_handle_t pf_filter_handle;
@@ -714,6 +720,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
@@ -739,7 +751,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] 11+ messages in thread
* [PATCH net-next v2 06/13] net: mana: swap queue sets in mana_xdp_set
2026-08-11 6:34 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (4 preceding siblings ...)
2026-08-11 6:35 ` [PATCH net-next v2 05/13] net: mana: swap queue sets in mana_change_mtu Long Li
@ 2026-08-11 6:35 ` Long Li
2026-08-11 6:35 ` [PATCH net-next v2 07/13] net: mana: do not bail out of mana_detach on dealloc failure Long Li
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ 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
Attaching or detaching an XDP program changes the RX buffer layout -
full pages with headroom rather than page fragments, see
mana_get_rxbuf_cfg() - so it rebuilds the queues. Convert
mana_xdp_set() to the pre-allocate and swap helpers, completing the
removal of detach/attach from the reconfiguration paths.
struct mana_qset gains bpf_prog, so the program the queues were built
for is swapped atomically with them and restored by the rollback path.
Refcounting is handled by the swap helpers. mana_publish_qset() already
re-attaches apc->bpf_prog to the new RX queues, which are created with
bpf_prog == NULL; installing qset->bpf_prog first means that same call
now attaches the new program. mana_free_qset() drops the old set's
per-queue references. The caller's reference is still consumed by the
port context and the old one released once, as before.
mana_pre_alloc_rxbufs() is no longer needed here. The TX timeout reset
handler is now its only caller.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 75 +++++++++++--------
drivers/net/ethernet/microsoft/mana/mana_en.c | 15 ++--
.../ethernet/microsoft/mana/mana_ethtool.c | 7 +-
include/net/mana/mana.h | 16 ++--
4 files changed, 65 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..1a6a490a84f2e5b4c9895645ff6f40beb55f497c 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
@@ -175,10 +175,24 @@ void mana_chn_setxdp(struct mana_port_context *apc, struct bpf_prog *prog)
bpf_prog_put(old_prog);
}
+/* mana_xdp_set - attach or detach an XDP program
+ *
+ * Attaching or detaching XDP changes the RX buffer layout (full pages with
+ * headroom vs. page fragments), so the RX queues must be rebuilt. Uses the
+ * pre-allocate + swap path: the new set is built for @prog while the
+ * current one keeps serving traffic. On allocation failure nothing changes
+ * and the error is returned to the caller.
+ *
+ * mana_publish_qset() installs qset->bpf_prog onto apc and re-attaches it
+ * to the new RX queues, and mana_free_qset() drops the old set's per-queue
+ * references, so refcounting is handled by the swap helpers.
+ */
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 +212,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 +264,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 4c79d6e82d9a93786ea877fbcef2b3f9d4093a6c..e554a776e8a941f80831047610066eed115c02b9 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -919,7 +919,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 */
@@ -3986,6 +3986,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;
}
@@ -4008,6 +4009,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;
}
@@ -4072,6 +4074,7 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
* @tx_queue_size: new TX ring size
* @priv_flags: new priv-flag word (affects full-page RX)
* @mtu: MTU the new set is sized for
+ * @bpf_prog: XDP program the new set is sized for, may be NULL
* @out: output qset, populated on success
*
* The live port context is not referenced at all, so the currently
@@ -4080,7 +4083,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;
@@ -4092,11 +4096,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 2a6325256fad3edf1aafbc72f0cd5dc18d61384e..2cadd0f0d74e358c1b670dd6980603e0358918eb 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -738,7 +738,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 */
@@ -840,7 +840,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);
@@ -942,7 +943,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 2fc75e6cd41a63878788182cd099379c8c698b33..bc9808f826f83df26c7af98daa181d91b7d2661d 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -590,7 +590,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;
@@ -720,11 +720,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
@@ -743,15 +744,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 path. Allocation and teardown run
- * against a scratch context so the live port context is never made to
- * point at queues that are still being built or freed.
+/* 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] 11+ messages in thread
* [PATCH net-next v2 07/13] net: mana: do not bail out of mana_detach on dealloc failure
2026-08-11 6:34 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (5 preceding siblings ...)
2026-08-11 6:35 ` [PATCH net-next v2 06/13] net: mana: swap queue sets in mana_xdp_set Long Li
@ 2026-08-11 6:35 ` Long Li
2026-08-11 6:35 ` [PATCH net-next v2 08/13] net: mana: keep per-queue statistics in the port context Long Li
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ 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, 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 e554a776e8a941f80831047610066eed115c02b9..89d5215db160572f633e32711f251a6b1d82b276 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -4659,10 +4659,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] 11+ messages in thread
* [PATCH net-next v2 08/13] net: mana: keep per-queue statistics in the port context
2026-08-11 6:34 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (6 preceding siblings ...)
2026-08-11 6:35 ` [PATCH net-next v2 07/13] net: mana: do not bail out of mana_detach on dealloc failure Long Li
@ 2026-08-11 6:35 ` Long Li
2026-08-11 6:35 ` [PATCH net-next v2 09/13] net: mana: share the EQ pool across a queue-set swap Long Li
2026-08-11 16:40 ` [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Jakub Kicinski
9 siblings, 0 replies; 11+ 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
struct mana_rxq and struct mana_txq embed their statistics by value, and
mana_get_stats64()/mana_get_ethtool_stats() read them through
apc->rxqs[q] and apc->tx_qp[q]. The queues are destroyed and recreated
by every reconfiguration - ethtool channel count, ring size and private
flags, MTU changes and XDP attach - so the interface counters are reset
each time and rx_bytes/tx_bytes can be observed going backwards:
rx_bytes before: 4475831638
rx_bytes after: 526629152
Move the statistics into arrays owned by the port context, sized to
max_queues, allocated in mana_probe_port() and freed in mana_remove().
The queues keep pointers into those arrays, so a queue set can be freed
without losing the counters it accumulated.
mana_get_stats64() now walks max_queues rather than num_queues, so
counters accumulated on queues that a later reconfiguration removed are
still reported and the totals stay monotonic. The ethtool per-queue
statistics keep iterating num_queues, since mana_get_sset_count() sizes
the string table the same way.
Note this is not specific to the queue-set swap: the detach/attach path
reset the counters in exactly the same way. It matters more now only
because reconfiguration is hitless and therefore likely to be 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 | 112 +++++++++++++++---
.../ethernet/microsoft/mana/mana_ethtool.c | 4 +-
include/net/mana/mana.h | 27 ++++-
4 files changed, 122 insertions(+), 25 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index 1a6a490a84f2e5b4c9895645ff6f40beb55f497c..ff953cbfda0ea8b27d18367fd8047d2183f3591b 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 89d5215db160572f633e32711f251a6b1d82b276..60ea12e7e866385688c9095759e7c03f28c7771f 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -386,7 +386,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 &&
@@ -565,7 +565,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);
@@ -611,9 +611,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;
@@ -621,6 +621,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)
@@ -629,7 +635,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);
@@ -642,7 +648,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);
@@ -1069,6 +1075,67 @@ static void mana_cleanup_port_context(struct mana_port_context *apc)
apc->rxqs = NULL;
}
+/* Allocate the port-owned per-queue counters.
+ *
+ * Per-queue statistics are owned by the port context, not by the queues, so
+ * that a queue-set replacement (ethtool channel/ring/priv-flag change, MTU
+ * change, XDP attach) does not reset the interface counters. Sized to
+ * max_queues and allocated once.
+ *
+ * A swap adds no writer to a TX slot: mana_start_xmit() and mana_xdp_xmit()
+ * reach a txq only through apc->tx_qp[], and mana_publish_qset() swaps that
+ * array behind netif_tx_disable() and synchronize_net(), so a retiring txq
+ * can no longer be reached once the new set is installed. Those two callers
+ * can still race each other on one slot, but that is the existing lockless
+ * ndo_xdp_xmit() arrangement and not a consequence of replacing a queue set.
+ *
+ * The RX slots do overlap briefly. A retiring rxq keeps its NAPI until
+ * mana_free_qset() destroys it, so between mana_config_rss() steering onto
+ * the new RQs and that teardown, both generations at one index can update the
+ * same slot. MANA is 64-bit only, so u64_stats_sync carries no seqcount and
+ * there is nothing to corrupt; at worst a couple of increments are lost.
+ * Exposing counters that survive a rebuild is worth that.
+ *
+ * Closing that window is deliberately not worth it. Serialising the writers
+ * would put a lock in the per-packet receive path to protect a counter, and
+ * giving each queue set its own slots to merge later would make
+ * ndo_get_stats64() report a dip for the duration of the swap - the totals
+ * going backwards is the very thing this is meant to prevent, and a worse
+ * artefact than the occasional lost increment.
+ */
+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;
@@ -2127,7 +2194,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;
@@ -2441,13 +2508,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);
@@ -2579,8 +2646,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);
}
@@ -2797,7 +2864,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);
@@ -3112,6 +3179,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;
@@ -3262,7 +3331,6 @@ static int mana_add_rx_queues(struct mana_port_context *apc,
goto out;
}
- u64_stats_init(&rxq->stats.syncp);
apc->rxqs[i] = rxq;
@@ -4712,6 +4780,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;
@@ -4734,7 +4806,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)
@@ -4771,6 +4843,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);
@@ -5111,6 +5188,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 2cadd0f0d74e358c1b670dd6980603e0358918eb..4c8799a2be86ca1f7e52cb23f6fb71afdf38acb5 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 bc9808f826f83df26c7af98daa181d91b7d2661d..32035605c70c66299b9f9707924ee267fa63cd9a 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -102,7 +102,11 @@ struct mana_stats_rx {
u64 pkt_len0_err;
u64 coalesced_cqe[MANA_CQE_COAL_PKTS_8 - 1];
struct u64_stats_sync syncp;
-};
+ /* Kept in a per-port array indexed by queue, so give each entry its
+ * own cache line. Without it two queues polled on different CPUs
+ * would share one, and every packet would bounce it between them.
+ */
+} ____cacheline_aligned_in_smp;
struct mana_stats_tx {
u64 packets;
@@ -117,7 +121,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;
@@ -153,7 +158,10 @@ struct mana_txq {
*/
bool retiring;
- struct mana_stats_tx stats;
+ /* Points into apc->txq_stats[]. Owned by the port context, not by
+ * this queue, so counters survive queue-set replacement.
+ */
+ struct mana_stats_tx *stats;
};
/* skb data and frags dma mappings */
@@ -415,7 +423,10 @@ struct mana_rxq {
u32 buf_index;
- struct mana_stats_rx stats;
+ /* Points into apc->rxq_stats[]. Owned by the port context, not by
+ * this queue, so counters survive queue-set replacement.
+ */
+ struct mana_stats_rx *stats;
struct bpf_prog __rcu *bpf_prog;
struct xdp_rxq_info xdp_rxq;
@@ -618,6 +629,14 @@ struct mana_port_context {
unsigned int max_queues;
unsigned int num_queues;
+ /* Per-queue statistics, max_queues entries each. Allocated once at
+ * probe and freed at remove, never on queue teardown, so counters
+ * are not reset by an ethtool/MTU/XDP reconfiguration. The queues
+ * point into these arrays.
+ */
+ 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] 11+ messages in thread
* [PATCH net-next v2 09/13] net: mana: share the EQ pool across a queue-set swap
2026-08-11 6:34 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (7 preceding siblings ...)
2026-08-11 6:35 ` [PATCH net-next v2 08/13] net: mana: keep per-queue statistics in the port context Long Li
@ 2026-08-11 6:35 ` Long Li
2026-08-11 16:40 ` [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Jakub Kicinski
9 siblings, 0 replies; 11+ 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
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
mana 7870:00:00.0 ens1: mana_alloc_qset(num_queues=32) failed: -28
mana_alloc_qset() called mana_create_eq() to build a complete second set
of EQs while the running set still held its own. EQs are bound to MSI-X
vectors taken from gc->msi_bitmap, so peak demand was
old_num_queues + new_num_queues and had to fit in gc->num_msix_usable.
On a VM with 32 usable vectors and a driver that comes up at 16 queues,
16 -> 17 already needs 33 and fails, so the advertised maximum channel
count is unreachable and a ring resize is impossible at 32 channels.
Fix it by making the EQ pool belong to the port rather than to a queue
set. Both sets share it across a swap, so peak usage is max(old, new)
instead of the sum:
- struct mana_qset no longer carries eqs or the EQ debugfs dir.
- mana_port_context gains num_eqs, a high-water mark of how many EQs
have been created. mana_create_eq() now sizes the array to
max_queues, so growing it later never reallocates - the CQs of a live
queue set hold pointers taken from those slots.
- mana_grow_eqs() creates only the EQs a larger set needs. It is
grow-only: EQs above the current queue count are kept for a later
increase. The ceiling is apc->max_queues, the same value ethtool
reports as maximum combined, so those vectors are by definition
obtainable.
- mana_alloc_qset() takes the live port context as well as the scratch
one, since the pool it grows belongs to the former.
- Neither mana_alloc_qset()'s error path nor mana_free_qset() tears the
pool down any more; it is released by mana_destroy_eq() on detach as
before.
mana_destroy_eq() now iterates num_eqs rather than num_queues. Those were
always equal before this patch, but with a grow-only pool they are not,
and iterating num_queues would leak the EQs above it.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 2 +-
drivers/net/ethernet/microsoft/mana/mana_en.c | 126 +++++++++++++++---
.../ethernet/microsoft/mana/mana_ethtool.c | 6 +-
include/net/mana/mana.h | 22 +--
4 files changed, 119 insertions(+), 37 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index ff953cbfda0ea8b27d18367fd8047d2183f3591b..1bae4174e268f7f53b0880c5d1098cd0ef687f25 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
@@ -220,7 +220,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 60ea12e7e866385688c9095759e7c03f28c7771f..71cbdebc5f3f7126495b2b11f0c673955fe0f8df 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -923,7 +923,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)
@@ -1839,7 +1839,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;
@@ -1851,6 +1851,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");
@@ -1879,9 +1880,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;
@@ -1911,6 +1917,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;
@@ -1920,6 +1927,78 @@ 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, which are a fixed per-device resource.
+ * Creating a second full set while the running one is still live would
+ * need old + new vectors and fails with -ENOSPC once that exceeds the
+ * pool, so the EQ pool is owned by the port and shared by both queue
+ * sets across a swap. Peak usage is therefore max(old, new), never the
+ * sum.
+ *
+ * Grow-only: EQs above the current queue count are kept for a later
+ * increase. The ceiling is apc->max_queues, which is the same value
+ * ethtool reports as the maximum combined channel count, so the vectors
+ * are by definition obtainable.
+ */
+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 = {};
@@ -4026,9 +4105,8 @@ static int mana_dealloc_queues(struct net_device *ndev)
* 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.
+ * old+new queues. The EQs are not doubled up: they belong to a port-owned
+ * pool that both sets share, so a swap never needs old+new MSI-X vectors.
*
* 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
@@ -4042,7 +4120,6 @@ static int mana_dealloc_queues(struct net_device *ndev)
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;
@@ -4055,7 +4132,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,
@@ -4065,7 +4141,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;
@@ -4078,7 +4153,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;
}
/**
@@ -4101,13 +4175,14 @@ 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
@@ -4136,6 +4211,7 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
/**
* mana_alloc_qset - build a complete queue set in @scratch
+ * @apc: live port context, owner of the shared EQ pool
* @scratch: scratch context from mana_qset_scratch_alloc()
* @num_queues: number of queues in the new set
* @rx_queue_size: new RX ring size
@@ -4145,11 +4221,13 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
* @bpf_prog: XDP program the new set is sized for, may be NULL
* @out: output qset, populated on success
*
- * The live port context is not referenced at all, so the currently
- * running queue set keeps serving traffic throughout. On error nothing
- * is left allocated.
+ * 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.
*/
-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)
@@ -4179,13 +4257,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)
@@ -4203,8 +4288,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:
@@ -4585,7 +4668,7 @@ 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
@@ -4687,7 +4770,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 4c8799a2be86ca1f7e52cb23f6fb71afdf38acb5..03fe657334c49a69ebe9c2677b2b8268321a162d 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -736,7 +736,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)
@@ -839,7 +839,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) {
@@ -941,7 +941,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 32035605c70c66299b9f9707924ee267fa63cd9a..d09bf2c7cec0be06c4190caab1f0723a5c4956d6 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -588,7 +588,13 @@ struct mana_port_context {
u8 mac_addr[ETH_ALEN];
+ /* EQ pool. Owned by the port, not by a queue set: EQs are bound to
+ * MSI-X vectors, so a queue-set swap must not double-book them.
+ * The array is sized to max_queues and num_eqs is a high-water mark
+ * of how many have actually been created.
+ */
struct mana_eq *eqs;
+ unsigned int num_eqs;
struct dentry *mana_eqs_debugfs;
enum TRI_STATE rss_state;
@@ -725,7 +731,6 @@ struct mana_port_context {
* can never race in during reconfiguration.
*/
struct mana_qset {
- struct mana_eq *eqs;
struct mana_tx_qp **tx_qp;
struct mana_rxq **rxqs;
@@ -745,13 +750,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);
@@ -763,13 +761,15 @@ 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
+/* Pre-allocate + swap reconfiguration path. Allocation and
* teardown run against a scratch context so the live port context is only
- * mutated inside mana_publish_qset(), with TX disabled.
+ * mutated inside mana_publish_qset(), with TX disabled. The EQs live in a
+ * port-owned pool that both queue sets share across a swap.
*/
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] 11+ messages in thread
* Re: [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set
2026-08-11 6:34 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (8 preceding siblings ...)
2026-08-11 6:35 ` [PATCH net-next v2 09/13] net: mana: share the EQ pool across a queue-set swap Long Li
@ 2026-08-11 16:40 ` Jakub Kicinski
9 siblings, 0 replies; 11+ messages in thread
From: Jakub Kicinski @ 2026-08-11 16:40 UTC (permalink / raw)
To: Long Li
Cc: Konstantin Taranov, 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, netdev, linux-rdma, linux-hyperv,
linux-kernel
On Mon, 10 Aug 2026 23:34:57 -0700 Long Li wrote:
> 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.
This posting is broken. Please *wait 2 days*, practice sending patches
to yourself (--suppress-cc=all) and the repost.
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-08-11 16:40 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-11 6:34 [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Long Li
2026-08-11 6:34 ` [PATCH net-next v2 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
2026-08-11 6:34 ` [PATCH net-next v2 02/13] net: mana: swap queue sets in mana_set_channels Long Li
2026-08-11 6:35 ` [PATCH net-next v2 03/13] net: mana: swap queue sets in mana_set_ringparam Long Li
2026-08-11 6:35 ` [PATCH net-next v2 04/13] net: mana: swap queue sets in mana_set_priv_flags Long Li
2026-08-11 6:35 ` [PATCH net-next v2 05/13] net: mana: swap queue sets in mana_change_mtu Long Li
2026-08-11 6:35 ` [PATCH net-next v2 06/13] net: mana: swap queue sets in mana_xdp_set Long Li
2026-08-11 6:35 ` [PATCH net-next v2 07/13] net: mana: do not bail out of mana_detach on dealloc failure Long Li
2026-08-11 6:35 ` [PATCH net-next v2 08/13] net: mana: keep per-queue statistics in the port context Long Li
2026-08-11 6:35 ` [PATCH net-next v2 09/13] net: mana: share the EQ pool across a queue-set swap Long Li
2026-08-11 16:40 ` [PATCH net-next v2 00/13] net: mana: reconfigure by replacing the queue set Jakub Kicinski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox