* [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set
@ 2026-09-01 1:44 Long Li
2026-09-01 1:44 ` [PATCH net-next v3 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
` (12 more replies)
0 siblings, 13 replies; 33+ messages in thread
From: Long Li @ 2026-09-01 1:44 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,
shirazsaleem
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.
netif_tx_disable -> swap the queue pointers -> update the netdev queue
counts -> reprogram RSS/steering -> reattach XDP ->
netif_tx_start_all_queues
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. The carrier is left alone
throughout, so a reconfiguration is not reported to userspace as a link
flap; only the paths that give up entirely take it down.
Patch layout:
1 the queue-set allocate/publish/free helpers.
2 give the port a shared EQ pool, so a swap never needs old + new
MSI-X vectors at once. This lands before the first converted
caller, so no patch in the series carries that window.
3-7 convert the five callers: ethtool channels, rings and private
flags, the MTU change and XDP attach.
8 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.
9-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, 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.
Testing, on Standard_D32ds_v6 (MANA) running Ubuntu 24.04:
- the failure above, made deterministic with fail_page_alloc: before the
series the port ends up down and unrecoverable; after it, ethtool
returns -ENOMEM, the ring sizes are unchanged, the link stays up and
traffic is uninterrupted.
- 33-case functional matrix over channel counts 1/2/4/8/32, ring sizes,
MTU, the private flag and XDP attach/detach across each, including
reconfiguration while the port is administratively down.
- XDP: all four verdicts exercised. PASS and TX across every
reconfiguration, DROP and REDIRECT verified by counter and by effect.
The program stays attached and RX keeps flowing across channel
shrink/grow, ring resize, MTU change and private-flag toggle, with
16.8 Gbit/s of received traffic running through the program while the
queue set is replaced underneath it.
- a queue-set allocation forced to fail while an XDP program is attached,
to exercise the unpublished-set teardown.
- carrier: no transition is reported across a successful reconfiguration,
and a port left down by a give-up path comes back with "ip link set
down/up".
- no KASAN reports, BUGs or WARNs in any of the above.
- every patch builds with W=1 with no new warnings, checkpatch clean, and
scripts/kernel-doc -Wall is clean on every patch rather than only the
tip.
On the review process point raised on v1: the AI review of that series is
what prompted this rework rather than a fix on top of it, since the
comments were about the detach/attach model itself. This series has been
through further rounds of the same review since v2. Where a comment was
acted on the change is in the patches - the carrier and RX statistics items
in the changelog below both came from that. The classes not acted on are
the pre-existing lockless ndo_xdp_xmit() arrangement and the existing
teardown-after-failed-reset behaviour, both of which predate and are
unchanged by this series. I am happy to go through any of them in detail.
Changes in v3:
- Move the shared EQ pool from patch 9 to patch 2, so no intermediate
patch needs old + new MSI-X vectors at once. Confirmed on hardware:
with the v2 order, "ethtool -L ens1 combined 32" applied at patch 2
fails with "netlink error: No space left on device" and "No free MSI
vectors available" in dmesg; with the new order the same command
succeeds.
- RX statistics across a swap: a retiring queue now counts into its own
storage, which the port folds back when the queue is destroyed. The two
generations no longer share a counter slot during the window between
steering moving to the new queues and the old ones being freed. v2's
cover letter documented that overlap as an accepted trade-off; it is
fixed rather than documented now.
- Carrier handling:
* a successful swap no longer takes the carrier down and back up, so
a reconfiguration is not seen by userspace as a link flap;
* the paths that give up and force the carrier down record that, and
the next successful open restores it, which is what makes the
documented "recoverable with a down/up" actually true;
* a real link event clears that record, so a stale software claim
cannot override the hardware state;
* open does not restore the carrier if a disconnect is pending.
- kernel-doc: add the missing "Return:" descriptions on mana_grow_eqs()
and mana_qset_scratch_alloc(), and drop the /** markers from
mana_alloc_qset() and mana_publish_qset(), which were never meant to be
kernel-doc. Found by running scripts/kernel-doc with -Wall, which our
gate was not doing.
- Patch 10: drop an incorrect claim from the commit message. v2 said the
MSI-X vectors are released along with the EQs. They are not -
mana_gd_setup_irqs() references every vector at probe, so
"grep -c mana /proc/interrupts" is unchanged at every queue count. What
a shrink returns is the pool slot, so a later increase can take it
again.
- Rebased on net-next. The driver capability flag this series adds now
sits at BIT(31), since BIT(30) was taken by
GDMA_DRV_CAP_FLAG_1_NON_CONTIGUOUS_BUFFERS in the meantime.
Changes in v2:
- Complete rework, as described above: replace the queue set rather than
recover after a failed mana_attach().
[1] v1: https://lore.kernel.org/netdev/20260711041415.3008868-1-dipayanroy@linux.microsoft.com/
[2] v2: https://lore.kernel.org/all/20260813050418.2906468-1-longli@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: share the EQ pool across a queue-set swap
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: release EQs left idle by a channel-count reduction
net: mana: keep a user-configured RSS table across a queue rebuild
net: mana: keep the surviving queues when the channel count is reduced
net: mana: keep the existing queues when the channel count is raised
.../net/ethernet/microsoft/mana/mana_bpf.c | 110 +-
drivers/net/ethernet/microsoft/mana/mana_en.c | 1629 ++++++++++++++++-
.../ethernet/microsoft/mana/mana_ethtool.c | 332 +++-
include/net/mana/gdma.h | 11 +-
include/net/mana/mana.h | 155 +-
5 files changed, 2011 insertions(+), 226 deletions(-)
base-commit: 1bb784eb6e38fd73143f021608e4ef3095d0c0d7
--
2.43.0
^ permalink raw reply [flat|nested] 33+ messages in thread
* [PATCH net-next v3 01/13] net: mana: add queue-set allocation and teardown helpers
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
@ 2026-09-01 1:44 ` Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 02/13] net: mana: share the EQ pool across a queue-set swap Long Li
` (11 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Long Li @ 2026-09-01 1:44 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,
shirazsaleem
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
The ethtool reconfiguration paths (channel count, ring size, private
flags), mana_change_mtu() and mana_xdp_set() rebuild the queues with
mana_detach() then mana_attach(). That tears the vport down, so RDMA can
claim it while released, and a failed mana_attach() leaves the port down
with no way back but manual intervention.
Add the data model and helpers for pre-allocate and swap: a queue set
built, published and torn down independently of the vport, against a
scratch port context so the live one never points at queues still being
built or freed. Building in place is not an option: mana_start_xmit()
dereferences apc->tx_qp[] guarded only by apc->port_is_up.
The TX drain moves out of mana_dealloc_queues() so the new teardown path
gets it too, and its fallback reset becomes pci_try_reset_function()
rather than an open-coded pcie_flr(), which does not save and restore
config space. Trylock because this runs under RTNL while removal takes
the device lock first.
No functional change otherwise: nothing calls the new helpers yet.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 31 ++
drivers/net/ethernet/microsoft/mana/mana_en.c | 448 ++++++++++++++++--
include/net/mana/mana.h | 53 +++
3 files changed, 493 insertions(+), 39 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 7a1ac853e3abcd28c4a1e5c6987ec631a18ad840..b9d9543d1a6dc90b01f6ca378b8a32bd045d6da8 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -2018,7 +2018,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++;
}
@@ -2754,6 +2755,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);
@@ -3009,11 +3011,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;
@@ -3679,15 +3684,115 @@ int mana_attach(struct net_device *ndev)
return 0;
}
-static int mana_dealloc_queues(struct net_device *ndev)
+/* Drain a set about to be destroyed: nothing new can reach it, so wait for the
+ * hardware to finish what it owns, then release every mapped SKB.
+ *
+ * The 120s budget is shared across all queues. On timeout the device is reset,
+ * since its buffers are about to be freed while it may still DMA into them; if
+ * that fails too they are leaked.
+ *
+ * Returns true only if a reset happened, taking every queue on the function
+ * down with it.
+ */
+static bool mana_drain_txqs(struct mana_port_context *apc)
{
- struct mana_port_context *apc = netdev_priv(ndev);
unsigned long timeout = jiffies + 120 * HZ;
- struct gdma_dev *gd = apc->ac->gdma_dev;
+ struct gdma_context *gc = apc->ac->gdma_dev->gdma_context;
+ bool quiesced = true;
+ bool reset = false;
struct mana_txq *txq;
struct sk_buff *skb;
- int i, err;
u32 tsleep;
+ int i, err;
+
+ if (!apc->tx_qp)
+ return false;
+
+ for (i = 0; i < apc->num_queues; i++) {
+ if (!apc->tx_qp[i])
+ continue;
+
+ txq = &apc->tx_qp[i]->txq;
+
+ /* The function was reset after this queue was created, so the
+ * device has stopped touching its buffers and the completions
+ * waited for below can never arrive. Without this the port
+ * would burn the full timeout under RTNL, then reset the
+ * function again on the way out.
+ */
+ if (READ_ONCE(apc->ac->reset_gen) != txq->reset_gen)
+ continue;
+
+ tsleep = 1000;
+ while (atomic_read(&txq->pending_sends) > 0 &&
+ time_before(jiffies, timeout)) {
+ usleep_range(tsleep, tsleep + 1000);
+ tsleep <<= 1;
+ }
+ if (atomic_read(&txq->pending_sends)) {
+ /* The device still owns these buffers, so reset it
+ * before they are freed. pci_try_reset_function()
+ * rather than pcie_flr(): it saves and restores config
+ * space, which a bare FLR wipes behind the PCI core's
+ * back. Trylock because RTNL is held here while the
+ * remove path takes the device lock first.
+ */
+ err = pci_try_reset_function(to_pci_dev(gc->dev));
+ if (err) {
+ netdev_err(apc->ndev,
+ "function reset failed: %d, %d pkts pending in txq %u\n",
+ err,
+ atomic_read(&txq->pending_sends),
+ txq->gdma_txq_id);
+ quiesced = false;
+ } else {
+ /* Every queue on the function is dead now,
+ * including the ones this loop has not reached
+ * and those of the other ports.
+ */
+ WRITE_ONCE(apc->ac->reset_gen,
+ apc->ac->reset_gen + 1);
+
+ /* Only a reset that actually happened takes the
+ * other ports down with it; reporting a failed
+ * one would rebuild them for nothing.
+ */
+ reset = true;
+ }
+ break;
+ }
+ }
+
+ /* Only a reset that actually happened makes freeing these safe; without
+ * one the device still owns them. Leak instead, bounded at one SQ ring
+ * of skbs per queue.
+ */
+ if (!quiesced) {
+ netdev_err(apc->ndev,
+ "device not quiesced, leaking pending TX buffers instead of unmapping memory it can still DMA from\n");
+ return reset;
+ }
+
+ for (i = 0; i < apc->num_queues; i++) {
+ if (!apc->tx_qp[i])
+ continue;
+
+ txq = &apc->tx_qp[i]->txq;
+ while ((skb = skb_dequeue(&txq->pending_skbs))) {
+ mana_unmap_skb(skb, apc);
+ dev_kfree_skb_any(skb);
+ }
+ atomic_set(&txq->pending_sends, 0);
+ }
+
+ return reset;
+}
+
+static int mana_dealloc_queues(struct net_device *ndev)
+{
+ struct mana_port_context *apc = netdev_priv(ndev);
+ struct gdma_dev *gd = apc->ac->gdma_dev;
+ int err;
if (apc->port_is_up)
return -EINVAL;
@@ -3705,41 +3810,27 @@ static int mana_dealloc_queues(struct net_device *ndev)
* new packets due to apc->port_is_up being false.
*
* Drain all the in-flight TX packets.
- * A timeout of 120 seconds for all the queues is used.
- * This will break the while loop when h/w is not responding.
- * This value of 120 has been decided here considering max
- * number of queues.
+ *
+ * If the drain had to reset the function to get there, every other
+ * port on the adapter lost its queues too, so schedule them for a
+ * rebuild. This port is being torn down here and needs no such
+ * treatment, and a down port stays down: with port_st_save false,
+ * detach and attach both skip the queue work.
*/
+ if (mana_drain_txqs(apc)) {
+ struct mana_context *ac = apc->ac;
+ unsigned int i;
- if (apc->tx_qp) {
- for (i = 0; i < apc->num_queues; i++) {
- txq = &apc->tx_qp[i]->txq;
- tsleep = 1000;
- while (atomic_read(&txq->pending_sends) > 0 &&
- time_before(jiffies, timeout)) {
- usleep_range(tsleep, tsleep + 1000);
- tsleep <<= 1;
- }
- if (atomic_read(&txq->pending_sends)) {
- err =
- pcie_flr(to_pci_dev(gd->gdma_context->dev));
- if (err) {
- netdev_err(ndev, "flr failed %d with %d pkts pending in txq %u\n",
- err,
- atomic_read(&txq->pending_sends),
- txq->gdma_txq_id);
- }
- break;
- }
- }
+ for (i = 0; i < ac->num_ports; i++) {
+ struct mana_port_context *sib;
- for (i = 0; i < apc->num_queues; i++) {
- txq = &apc->tx_qp[i]->txq;
- while ((skb = skb_dequeue(&txq->pending_skbs))) {
- mana_unmap_skb(skb, apc);
- dev_kfree_skb_any(skb);
- }
- atomic_set(&txq->pending_sends, 0);
+ if (!ac->ports[i] || ac->ports[i] == ndev)
+ continue;
+ sib = netdev_priv(ac->ports[i]);
+ netdev_err(ac->ports[i],
+ "queues reset by a sibling port, scheduling rebuild\n");
+ queue_work(ac->per_port_queue_reset_wq,
+ &sib->queue_reset_work);
}
}
@@ -3763,6 +3854,278 @@ static int mana_dealloc_queues(struct net_device *ndev)
return 0;
}
+/*
+ * Pre-allocate and swap reconfiguration.
+ *
+ * Build a new queue set while the current one serves traffic, publish it, then
+ * destroy the old one. A failed allocation leaves the running config untouched,
+ * and the vport is never torn down, so RDMA cannot take it mid-swap. The cost
+ * is room for both sets at once, so a rebuild at the vport's maximum queue
+ * count can be refused, and both sets' EQs - and so their MSI-X vectors - are
+ * live at once.
+ *
+ * Everything builds in a scratch mana_port_context, since mana_start_xmit()
+ * dereferences apc->tx_qp[] guarded only by port_is_up. Per-queue debugfs is
+ * suppressed meanwhile, as the names would collide.
+ */
+
+/* Snapshot the queue-set fields of @ctx into @out. */
+static void mana_qset_snapshot(const struct mana_port_context *ctx,
+ struct mana_qset *out)
+{
+ out->eqs = ctx->eqs;
+ out->tx_qp = ctx->tx_qp;
+ out->rxqs = ctx->rxqs;
+ out->indir_table = ctx->indir_table;
+ 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;
+}
+
+/* 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;
+}
+
+/**
+ * mana_qset_scratch_alloc - build a scratch port context for queue work
+ * @apc: the live port context to shadow
+ *
+ * Builds a heap copy of @apc that shares its vport identity but owns no
+ * queues, so the existing allocators and destroyers can run against it
+ * without touching the live context.
+ *
+ * Return: the scratch context, or NULL if it could not be allocated.
+ */
+struct mana_port_context *mana_qset_scratch_alloc(struct mana_port_context *apc)
+{
+ struct mana_port_context *scratch;
+
+ scratch = kvzalloc_obj(*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; the swap path
+ * has no post-teardown allocation to de-risk.
+ */
+ scratch->rxbufs_pre = NULL;
+ scratch->das_pre = NULL;
+ scratch->rxbpre_total = 0;
+
+ /* Two sets are alive at once and would collide on the same names under
+ * vport%d. An IS_ERR() parent makes every create and remove a no-op.
+ */
+ scratch->mana_port_debugfs = ERR_PTR(-ENODEV);
+
+ return scratch;
+}
+
+void mana_qset_scratch_free(struct mana_port_context *scratch)
+{
+ kvfree(scratch);
+}
+
+/* Build a queue set in @scratch, sized and configured from the arguments. The
+ * installed set keeps serving traffic meanwhile. On error nothing is left
+ * allocated.
+ */
+int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
+ unsigned int rx_queue_size, unsigned int tx_queue_size,
+ u32 priv_flags, struct mana_qset *out)
+{
+ struct net_device *ndev = scratch->ndev;
+ int err;
+
+ ASSERT_RTNL();
+
+ scratch->num_queues = num_queues;
+ scratch->rx_queue_size = rx_queue_size;
+ scratch->tx_queue_size = tx_queue_size;
+ scratch->priv_flags = priv_flags;
+
+ err = mana_init_port_context(scratch);
+ if (err)
+ goto out_err;
+
+ err = mana_rss_table_alloc(scratch);
+ if (err)
+ goto cleanup_rxq_array;
+
+ err = mana_create_eq(scratch);
+ if (err)
+ goto cleanup_rss;
+
+ err = mana_create_txq(scratch, ndev);
+ if (err)
+ goto cleanup_eq;
+
+ err = mana_add_rx_queues(scratch, ndev);
+ if (err)
+ goto cleanup_rxq;
+
+ mana_rss_table_init(scratch);
+
+ mana_qset_snapshot(scratch, out);
+ return 0;
+
+cleanup_rxq:
+ /* mana_add_rx_queues() may have created queues before failing; they
+ * own RQ/CQ objects, NAPI state and page pools, so tear down whatever
+ * made it into scratch->rxqs[] before dropping the array.
+ */
+ mana_destroy_rxqs(scratch);
+ mana_destroy_txq(scratch);
+cleanup_eq:
+ mana_destroy_eq(scratch);
+cleanup_rss:
+ mana_cleanup_indir_table(scratch);
+cleanup_rxq_array:
+ kfree(scratch->rxqs);
+ scratch->rxqs = NULL;
+out_err:
+ netdev_err(ndev, "%s(num_queues=%u) failed: %d\n", __func__,
+ num_queues, err);
+ return err;
+}
+
+/* Tear down @qset, no longer installed on @apc, against @scratch so the live
+ * context never points at queues being freed.
+ */
+void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
+{
+ struct bpf_prog *retiring_prog;
+ unsigned int retiring_queues;
+
+ ASSERT_RTNL();
+
+ if (!qset->rxqs && !qset->tx_qp && !qset->eqs)
+ return;
+
+ /* Keep their completions off the netdev queues they now share. */
+ 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. The publish step drains
+ * those readers before it installs the incoming set, which cannot
+ * cover one that sampled the retiring pointers between that install
+ * and the gate reopening. mana_xdp_xmit() is the case that matters:
+ * it runs from a redirecting device's NAPI, so the napi_synchronize()
+ * that mana_destroy_txq()/mana_destroy_rxq() do on this port's own
+ * NAPIs never waits for it. Give any such reader a grace period to
+ * finish before its queues are torn down under it. Every caller is a
+ * reconfiguration path holding RTNL, so this is expedited.
+ */
+ synchronize_net();
+
+ mana_qset_install(scratch, qset);
+
+ /* Note what this set owes the XDP program, but leave the queues
+ * pointing at it. They are still polling, and a packet already in a
+ * retiring RQ has to keep running the program rather than slip past
+ * it into the stack. The references are dropped once the queues are
+ * gone, below. XDP_TX from those polls is harmless here: it goes
+ * through mana_start_xmit() on the live port context, so it reaches
+ * the queue set that replaced this one, not the one being drained.
+ */
+ retiring_prog = mana_chn_xdp_peek(scratch);
+ retiring_queues = scratch->num_queues;
+
+ /* The retiring TX queues may still hold packets the device has not
+ * completed. Drain them before the SQs and the SKB queues go away,
+ * or those SKBs and their DMA mappings are leaked.
+ *
+ * This runs before any RX teardown, the order mana_dealloc_queues()
+ * uses. A device wedged badly enough to need the reset below is also
+ * one whose RQ teardown will not complete, and unmapping RX buffers
+ * first would leave it free to keep writing into them for as long as
+ * the drain takes.
+ */
+ if (mana_drain_txqs(scratch)) {
+ /* The drain had to reset the function to stop the device
+ * touching those buffers. A function reset takes down every
+ * port on the adapter, not just this one, so rebuild them all
+ * - the same recovery mana_tx_timeout() relies on. A port that
+ * is already down has nothing to rebuild and its handler
+ * leaves it down.
+ */
+ struct mana_port_context *apc = netdev_priv(scratch->ndev);
+ struct mana_context *ac = apc->ac;
+ struct mana_port_context *sib;
+ 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;
+ sib = netdev_priv(ac->ports[i]);
+ queue_work(ac->per_port_queue_reset_wq,
+ &sib->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);
@@ -4240,6 +4603,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..356aaa652fa6f8d3498383e2b7c976da677cfa22 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 on every PCI function reset. A queue created before the
+ * current value can no longer be reached by the device, so its buffers
+ * need no drain. All access is under RTNL: the bump is a non-atomic
+ * read-modify-write, so a second writer would lose an increment.
+ */
+ 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,27 @@ struct mana_port_context {
u32 steer_cqe_coalescing;
};
+/* The queue-related fields of mana_port_context that can be swapped as a
+ * unit. The vport (port_handle, vport_use_count) is not part of it and is
+ * never touched by a swap.
+ */
+struct mana_qset {
+ struct mana_eq *eqs;
+ struct mana_tx_qp **tx_qp;
+ struct mana_rxq **rxqs;
+
+ 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;
+
+};
+
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 +709,18 @@ int mana_alloc_queues(struct net_device *ndev);
int mana_attach(struct net_device *ndev);
int mana_detach(struct net_device *ndev, bool from_close);
+/* Pre-allocate + swap reconfiguration. Allocation and teardown run against a
+ * scratch context, so the live port context is only ever mutated 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, 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 +736,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] 33+ messages in thread
* [PATCH net-next v3 02/13] net: mana: share the EQ pool across a queue-set swap
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
2026-09-01 1:44 ` [PATCH net-next v3 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
@ 2026-09-01 1:44 ` Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 03/13] net: mana: swap queue sets in mana_set_channels Long Li
` (10 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Long Li @ 2026-09-01 1:44 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,
shirazsaleem
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
The queue-set swap the rest of this series builds on keeps the running
set alive while the new one is allocated, so both exist at once. EQs are
bound to MSI-X vectors, and a set that owned its EQs would make that peak
at old + new vectors rather than max(old, new).
With 32 usable vectors and a driver that comes up at 16 queues, 16 -> 17
would already need 33, so the advertised maximum would be unreachable:
# ethtool -L ens1 combined 32
netlink error: No space left on device
mana 7870:00:00.0: No free MSI vectors available
Give the pool to the port rather than to a queue set, before any caller
is converted, so no intermediate patch in this series can hit that. The
pool only ever grows, up to the maximum channel count ethtool reports,
and is released on detach as before.
Signed-off-by: Long Li <longli@microsoft.com>
---
drivers/net/ethernet/microsoft/mana/mana_en.c | 107 +++++++++++++++---
include/net/mana/mana.h | 13 ++-
2 files changed, 100 insertions(+), 20 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index b9d9543d1a6dc90b01f6ca378b8a32bd045d6da8..bd80658cf3efc4522ca51fc039242ae91669ea88 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -1733,7 +1733,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;
@@ -1745,6 +1745,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");
@@ -1773,9 +1774,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;
@@ -1805,6 +1811,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;
@@ -1814,6 +1821,72 @@ int mana_create_eq(struct mana_port_context *apc)
}
EXPORT_SYMBOL_NS(mana_create_eq, "NET_MANA");
+/**
+ * mana_grow_eqs - make sure the port has at least @need EQs
+ * @apc: port context
+ * @need: number of EQs the new queue set requires
+ *
+ * EQs are bound to MSI-X vectors, so the pool is port-owned and shared across
+ * a swap: peak usage is max(old, new), not the sum. Grow-only, up to
+ * apc->max_queues.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+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 = {};
@@ -3861,8 +3934,7 @@ static int mana_dealloc_queues(struct net_device *ndev)
* destroy the old one. A failed allocation leaves the running config untouched,
* and the vport is never torn down, so RDMA cannot take it mid-swap. The cost
* is room for both sets at once, so a rebuild at the vport's maximum queue
- * count can be refused, and both sets' EQs - and so their MSI-X vectors - are
- * live at once.
+ * count can be refused; EQs are shared from a port-owned pool, not doubled.
*
* Everything builds in a scratch mana_port_context, since mana_start_xmit()
* dereferences apc->tx_qp[] guarded only by port_is_up. Per-queue debugfs is
@@ -3873,7 +3945,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;
@@ -3893,7 +3964,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;
@@ -3926,14 +3996,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; the swap path
* has no post-teardown allocation to de-risk.
@@ -3959,7 +4029,8 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
* installed set keeps serving traffic meanwhile. On error nothing is left
* allocated.
*/
-int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
+int mana_alloc_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, unsigned int num_queues,
unsigned int rx_queue_size, unsigned int tx_queue_size,
u32 priv_flags, struct mana_qset *out)
{
@@ -3981,13 +4052,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)
@@ -4005,8 +4083,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:
@@ -4028,7 +4104,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;
/* Keep their completions off the netdev queues they now share. */
@@ -4116,7 +4192,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/include/net/mana/mana.h b/include/net/mana/mana.h
index 356aaa652fa6f8d3498383e2b7c976da677cfa22..2117a30116c265d6cd9c8223ad4f5ea5d2818455 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -577,7 +577,12 @@ struct mana_port_context {
u8 mac_addr[ETH_ALEN];
+ /* EQ pool, owned by the port rather than a queue set: EQs are bound to
+ * MSI-X vectors, which a swap must not double-book. Sized to
+ * max_queues; num_eqs is how many exist.
+ */
struct mana_eq *eqs;
+ unsigned int num_eqs;
struct dentry *mana_eqs_debugfs;
enum TRI_STATE rss_state;
@@ -684,7 +689,6 @@ struct mana_port_context {
* never touched by a swap.
*/
struct mana_qset {
- struct mana_eq *eqs;
struct mana_tx_qp **tx_qp;
struct mana_rxq **rxqs;
@@ -710,13 +714,14 @@ int mana_attach(struct net_device *ndev);
int mana_detach(struct net_device *ndev, bool from_close);
/* Pre-allocate + swap reconfiguration. Allocation and teardown run against a
- * scratch context, so the live port context is only ever mutated with TX
- * disabled.
+ * scratch context, so the live port context is mutated only inside
+ * mana_publish_qset() with TX disabled. Both sets share a port-owned EQ pool.
*/
struct mana_port_context *
mana_qset_scratch_alloc(struct mana_port_context *apc);
void mana_qset_scratch_free(struct mana_port_context *scratch);
-int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
+int mana_alloc_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, unsigned int num_queues,
unsigned int rx_queue_size, unsigned int tx_queue_size,
u32 priv_flags, struct mana_qset *out);
void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset);
--
2.43.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH net-next v3 03/13] net: mana: swap queue sets in mana_set_channels
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
2026-09-01 1:44 ` [PATCH net-next v3 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
2026-09-01 1:44 ` [PATCH net-next v3 02/13] net: mana: share the EQ pool across a queue-set swap Long Li
@ 2026-09-01 1:44 ` Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 04/13] net: mana: swap queue sets in mana_set_ringparam Long Li
` (9 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Long Li @ 2026-09-01 1:44 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,
shirazsaleem
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
Convert the channel count path away from detach/attach: build the new
queue set while the current one keeps serving traffic, then swap it in
and retire the old one.
An allocation failure now returns the error with the running
configuration untouched, instead of leaving the port down, and the value
the user asked for is never silently replaced by a fallback. The vport is
never released, so RDMA cannot claim it mid-reconfiguration. If the swap
itself fails the previous set is put back and the port keeps running on
it.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 7 +
drivers/net/ethernet/microsoft/mana/mana_en.c | 386 ++++++++++++++++--
.../ethernet/microsoft/mana/mana_ethtool.c | 92 +++--
include/net/mana/mana.h | 18 +
4 files changed, 455 insertions(+), 48 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 bd80658cf3efc4522ca51fc039242ae91669ea88..880a3ba37fd3e872dbfeb101c46ac8f527c4bbdd 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -90,6 +90,25 @@ static int mana_open(struct net_device *ndev)
smp_wmb();
netif_tx_wake_all_queues(ndev);
+
+ /* A failed queue-set swap can dead-end the port and take the carrier
+ * down. The link handler only reacts to real hardware transitions, so
+ * this open is the one place that can undo it.
+ *
+ * Defer to the last thing the hardware said, though: ac->link_event is
+ * written when the event arrives, before its work item gets to run, so
+ * a disconnect already sitting behind RTNL is visible here. Restoring
+ * the carrier on that would advertise a link the device has told us is
+ * gone, and the handler would only correct it afterwards.
+ */
+ if (apc->carrier_forced_off) {
+ u32 ev = READ_ONCE(apc->ac->link_event);
+
+ apc->carrier_forced_off = false;
+ if (ev != HWC_DATA_HW_LINK_DISCONNECT)
+ netif_carrier_on(ndev);
+ }
+
netdev_dbg(ndev, "%s successful\n", __func__);
return 0;
}
@@ -106,6 +125,7 @@ static int mana_close(struct net_device *ndev)
static void mana_link_state_handle(struct work_struct *w)
{
+ struct mana_port_context *apc;
struct mana_context *ac;
struct net_device *ndev;
u32 link_event;
@@ -131,6 +151,16 @@ static void mana_link_state_handle(struct work_struct *w)
if (!ndev)
continue;
+ /* The hardware has spoken, so it owns the carrier from here.
+ * Drop any claim a failed queue-set swap left behind: the
+ * flag records that this driver removed a working carrier,
+ * which stops being true the moment the physical state
+ * changes. Leaving it set would let a later open assert
+ * carrier-up on a link the hardware has just reported down.
+ */
+ apc = netdev_priv(ndev);
+ apc->carrier_forced_off = false;
+
if (link_up) {
netif_carrier_on(ndev);
@@ -366,6 +396,18 @@ netdev_tx_t mana_start_xmit(struct sk_buff *skb, struct net_device *ndev)
if (unlikely(!apc->port_is_up))
goto tx_drop;
+ /* Pair with the smp_wmb() in mana_publish_qset(). A control dependency
+ * does not order loads, and a stale apc->num_queues would admit an
+ * index past the end of a freshly shrunk apc->tx_qp[].
+ */
+ smp_rmb();
+
+ /* XDP_TX from a retiring set carries its own RX queue index, which can
+ * be past the end of a smaller replacement apc->tx_qp[].
+ */
+ if (unlikely(txq_idx >= apc->num_queues))
+ goto tx_drop_count;
+
if (skb_cow_head(skb, MANA_HEADROOM))
goto tx_drop_count;
@@ -1045,6 +1087,11 @@ static void mana_cleanup_indir_table(struct mana_port_context *apc)
static int mana_init_port_context(struct mana_port_context *apc)
{
+ /* A port reconfigured while down already has an apc->rxqs, and
+ * mana_detach() takes its "already detached" early return without
+ * releasing it. Free it rather than overwrite the pointer.
+ */
+ kfree(apc->rxqs);
apc->rxqs = kzalloc_objs(struct mana_rxq *, apc->num_queues);
return !apc->rxqs ? -ENOMEM : 0;
@@ -2091,6 +2138,10 @@ static void mana_poll_tx_cq(struct mana_cq *cq)
/* Ensure checking txq_stopped before apc->port_is_up. */
smp_rmb();
+ /* Ordered by the same barrier: reaching here with txq_stopped set means
+ * the replacement queue has already run, which is strictly after this
+ * queue was marked retiring.
+ */
if (txq_stopped && !READ_ONCE(txq->retiring) && apc->port_is_up &&
avail_space >= MAX_TX_WQE_SIZE) {
netif_tx_wake_queue(net_txq);
@@ -4094,6 +4145,289 @@ int mana_alloc_qset(struct mana_port_context *apc,
return err;
}
+/* Close a port mana_publish_qset() gave up on; does nothing otherwise. Under
+ * RTNL.
+ *
+ * The caller releases the unpublished set first: closing destroys the shared
+ * EQ pool its CQs attach to, and only the caller knows whether it owns its
+ * queues or shares them with the live set. RX is already off.
+ *
+ * Merely stopping the port would leave port_is_up false with queues still
+ * allocated, so mana_detach() skips teardown and the next open trips
+ * WARN_ON(apc->eqs).
+ */
+void mana_publish_close_if_needed(struct mana_port_context *apc)
+{
+ ASSERT_RTNL();
+
+ if (!apc->publish_dead_end)
+ return;
+
+ apc->publish_dead_end = false;
+
+ /* mana_dealloc_queues() requires the port already marked down, which
+ * mana_publish_qset() did before the swap it is unwinding.
+ */
+ if (mana_dealloc_queues(apc->ndev))
+ netdev_err(apc->ndev,
+ "failed to close the port after a failed rollback\n");
+}
+
+/* Start only the netdev queues that can take work. A carried-over queue may
+ * still have a full ring, and restarting it would just make mana_start_xmit()
+ * drop; leave it for mana_poll_tx_cq() to wake. Must run after port_is_up is
+ * set, or that wakeup is gated off.
+ */
+static void mana_start_txqs(struct mana_port_context *apc)
+{
+ struct net_device *ndev = apc->ndev;
+ unsigned int i;
+
+ if (!apc->tx_qp)
+ return;
+
+ for (i = 0; i < apc->num_queues; i++) {
+ if (!apc->tx_qp[i])
+ continue;
+
+ if (mana_can_tx(apc->tx_qp[i]->txq.gdma_sq))
+ netif_tx_wake_queue(netdev_get_tx_queue(ndev, i));
+ }
+}
+
+/* A retiring queue shares its struct netdev_queue with whatever replaced it
+ * at the same index, and only ever drains, so it always looks like it has
+ * room. Without this flag its completions would wake a netdev queue that the
+ * live queue stopped on a full ring.
+ *
+ * A queue both sets own must end up unmarked, so callers mark the leaving set
+ * first and unmark the incoming one second.
+ */
+static void mana_qset_set_retiring(struct mana_qset *qset, bool retiring)
+{
+ unsigned int q;
+
+ if (!qset->tx_qp)
+ return;
+
+ for (q = 0; q < qset->num_queues; q++) {
+ if (qset->tx_qp[q])
+ WRITE_ONCE(qset->tx_qp[q]->txq.retiring, retiring);
+ }
+}
+
+/* Give up on a swap. Steering may still point at the set the caller is about
+ * to free, and restoring it is exactly what failed, so stop delivery before
+ * those RQs and their buffers go away. This is the narrow steering request -
+ * no key, table or default-rxobj update - so it can land where the full
+ * mana_config_rss() restore did not.
+ *
+ * The carrier goes down here and stays down: this is the one outcome where
+ * the port really is out of service, so the link state should say so.
+ *
+ * Closing the port is left to mana_publish_close_if_needed(), which must run
+ * after the caller has released that set.
+ */
+static void mana_publish_give_up(struct mana_port_context *apc)
+{
+ int err;
+
+ apc->rss_state = TRI_STATE_FALSE;
+
+ err = mana_disable_vport_rx(apc);
+ if (err && mana_en_need_log(apc, err))
+ netdev_err(apc->ndev, "failed to disable vPort RX: %d\n", err);
+
+ /* Only claim the carrier if it was actually up: the flag means "this
+ * driver took a working carrier away", which is the only case
+ * mana_open() may undo. If the link was already down for a hardware
+ * reason there is nothing to restore, and forcing it on at reopen
+ * would report a link that does not exist - the physical state is
+ * event-driven and mana_link_state_handle() stores none for us to
+ * consult, so it must stay the authority in that case.
+ */
+ apc->carrier_forced_off = netif_carrier_ok(apc->ndev);
+ netif_carrier_off(apc->ndev);
+ apc->publish_dead_end = true;
+}
+
+/* Swap @newq onto @apc, handing the previous set back in @out_old for the
+ * caller to free. On failure the old set is reinstalled and the caller frees
+ * only @newq. Must be called under RTNL.
+ *
+ * netif_tx_disable() is load-bearing: mana_start_xmit() dereferences
+ * apc->tx_qp[] guarded only by apc->port_is_up, and ndo_xdp_xmit() bypasses
+ * the txq-stopped checks, so port_is_up is cleared over the same window.
+ */
+int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
+ struct mana_qset *out_old)
+{
+ struct net_device *ndev = apc->ndev;
+ int err;
+
+ ASSERT_RTNL();
+
+ /* The carrier is deliberately left alone. A swap that succeeds, and one
+ * that rolls back onto the old set, both end with the port serving
+ * traffic on the same link, so reporting a carrier transition would
+ * make a reconfiguration look like a link flap to userspace - DHCP
+ * renewals, bond failovers, monitoring alerts. Only the dead-end paths
+ * take the carrier down, in mana_publish_give_up().
+ *
+ * Nothing is lost by that: netif_carrier_off() only sets a bit and
+ * schedules linkwatch, which cannot run until this function drops
+ * RTNL. The transmit quiesce below is what actually stops traffic.
+ */
+
+ /* Clear port_is_up before stopping the queues, pairing with the
+ * smp_rmb() in mana_poll_tx_cq(): that reader samples
+ * netif_tx_queue_stopped() first, so a completion seeing a queue
+ * stopped here also sees port_is_up false and will not wake it
+ * mid-swap. It also fences mana_xdp_xmit(), which is gated only by
+ * port_is_up and would otherwise index a stale apc->tx_qp[].
+ */
+ WRITE_ONCE(apc->port_is_up, false);
+
+ /* Ensure port state updated before txq state */
+ smp_wmb();
+
+ netif_tx_disable(ndev);
+
+ mana_qset_snapshot(apc, out_old);
+
+ /* Mark the outgoing set before the grace period, not after: a
+ * completion that saw the flag clear must not still be in flight when
+ * the gate reopens, or it could wake a netdev queue that its
+ * replacement had stopped on a full ring.
+ */
+ mana_qset_set_retiring(out_old, true);
+
+ /* Wait out any transmit or ndo_xdp_xmit() that was already past the
+ * port_is_up test before the swap touches apc->tx_qp / the counts,
+ * and any completion that still saw the flag clear above.
+ */
+ synchronize_net();
+
+ /* Anything the incoming set carries over is staying, so clear the flag
+ * again - after the marking above, before the gate reopens.
+ */
+ mana_qset_set_retiring(newq, false);
+
+ mana_qset_install(apc, newq);
+ apc->rss_state = apc->num_queues > 1 ? TRI_STATE_TRUE : TRI_STATE_FALSE;
+
+ err = netif_set_real_num_tx_queues(ndev, apc->num_queues);
+ if (err)
+ goto rollback;
+
+ err = netif_set_real_num_rx_queues(ndev, apc->num_queues);
+ if (err)
+ goto rollback;
+
+ /* Carry the XDP program over before steering can reach the new RXQs:
+ * they were created with bpf_prog == NULL, so a packet arriving first
+ * would bypass an attached program. This also takes the per-queue
+ * references that mana_free_qset() drops for the old set.
+ */
+ mana_chn_setxdp(apc, mana_xdp_get(apc));
+
+ err = mana_config_rss(apc, TRI_STATE_TRUE, true, true);
+ if (err)
+ goto rollback;
+
+ /* Pair with the queue-state stores above: a datapath reader that sees
+ * the gate open must also see the queue set it is about to index.
+ */
+ smp_wmb();
+
+ WRITE_ONCE(apc->port_is_up, true);
+ mana_start_txqs(apc);
+
+ return 0;
+
+rollback:
+ netdev_err(ndev, "%s failed: %d, restoring previous queue set\n",
+ __func__, 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.
+ *
+ * @err stays the failure that started the rollback: that is
+ * what the caller asked about, and the port being left down is
+ * reported above.
+ */
+ 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);
+
+ /* out_old is live again on apc; caller must only free newq. */
+ return err;
+}
+
+/* Give live queues the debugfs nodes suppressed while they were built in a
+ * scratch context, whose names would collide under vport%d. Once the retiring
+ * set is gone the survivors take them.
+ *
+ * Idempotent: a carried-over queue keeps its node; suppressed creation leaves
+ * an error pointer, not NULL, so both read as "no node". Under RTNL.
+ */
+static void mana_qset_debugfs_publish(struct mana_port_context *apc)
+{
+ unsigned int i;
+
+ ASSERT_RTNL();
+
+ if (IS_ERR_OR_NULL(apc->mana_port_debugfs))
+ return;
+
+ for (i = 0; i < apc->num_queues; i++) {
+ if (apc->tx_qp && apc->tx_qp[i] &&
+ IS_ERR_OR_NULL(apc->tx_qp[i]->mana_tx_debugfs))
+ mana_create_txq_debugfs(apc, i);
+
+ if (apc->rxqs && apc->rxqs[i] &&
+ IS_ERR_OR_NULL(apc->rxqs[i]->mana_rx_debugfs))
+ mana_create_rxq_debugfs(apc, i);
+ }
+}
+
/* Tear down @qset, no longer installed on @apc, against @scratch so the live
* context never points at queues being freed.
*/
@@ -4117,41 +4451,36 @@ void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
}
}
- /* The datapath gates on apc->port_is_up and then dereferences
- * apc->tx_qp[] / apc->rxqs[] with no lock. The publish step drains
- * those readers before it installs the incoming set, which cannot
- * cover one that sampled the retiring pointers between that install
- * and the gate reopening. mana_xdp_xmit() is the case that matters:
- * it runs from a redirecting device's NAPI, so the napi_synchronize()
- * that mana_destroy_txq()/mana_destroy_rxq() do on this port's own
- * NAPIs never waits for it. Give any such reader a grace period to
- * finish before its queues are torn down under it. Every caller is a
- * reconfiguration path holding RTNL, so this is expedited.
+ /* A reader that sampled the retiring pointers after mana_publish_qset()
+ * installed the new set is not covered by the drain it did earlier.
+ * mana_xdp_xmit() is the case that matters: it runs from another
+ * device's NAPI, which this port's napi_synchronize() never waits for.
*/
synchronize_net();
mana_qset_install(scratch, qset);
- /* Note what this set owes the XDP program, but leave the queues
- * pointing at it. They are still polling, and a packet already in a
- * retiring RQ has to keep running the program rather than slip past
- * it into the stack. The references are dropped once the queues are
- * gone, below. XDP_TX from those polls is harmless here: it goes
- * through mana_start_xmit() on the live port context, so it reaches
- * the queue set that replaced this one, not the one being drained.
+ /* Teardown follows mana_dealloc_queues()' order, minus the vport RX
+ * disable: steering already points at the incoming set, and disabling
+ * vport RX would stop the set that is now live. Where publish could
+ * not repoint steering it disabled vport RX itself, so nothing is
+ * delivered here either way.
+ */
+
+ /* Note what this set owes the XDP program but leave the queues
+ * pointing at it: they are still polling, and a packet already in a
+ * retiring RQ must keep running the program rather than slip into the
+ * stack. The references are dropped once the queues are gone, below.
*/
retiring_prog = mana_chn_xdp_peek(scratch);
retiring_queues = scratch->num_queues;
- /* The retiring TX queues may still hold packets the device has not
- * completed. Drain them before the SQs and the SKB queues go away,
- * or those SKBs and their DMA mappings are leaked.
+ /* Drain packets the device has not completed before the SQs and SKB
+ * queues go away, or those SKBs and their DMA mappings are leaked.
*
- * This runs before any RX teardown, the order mana_dealloc_queues()
- * uses. A device wedged badly enough to need the reset below is also
- * one whose RQ teardown will not complete, and unmapping RX buffers
- * first would leave it free to keep writing into them for as long as
- * the drain takes.
+ * This runs before any RX teardown, as mana_dealloc_queues() does:
+ * unmapping RX buffers first would leave a wedged device free to keep
+ * writing into them for as long as the drain takes.
*/
if (mana_drain_txqs(scratch)) {
/* The drain had to reset the function to stop the device
@@ -4197,6 +4526,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..2ef3d461e707287f6c9dbb04288c8d63a4bc9993 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -648,52 +648,98 @@ static int mana_set_coalesce(struct net_device *ndev,
return 0;
}
-/* mana_set_channels - change the number of queues on a port
- *
- * Returns -EBUSY if RDMA holds the vport with EQs sized to the
- * current num_queues.
+/* Build the new queue set while the current one keeps serving traffic, then
+ * swap. On failure the existing queues keep running and the requested value is
+ * never replaced by a fallback. The vport is never torn down, so RDMA cannot
+ * take it mid-reconfiguration.
*/
static int mana_set_channels(struct net_device *ndev,
struct ethtool_channels *channels)
{
struct mana_port_context *apc = netdev_priv(ndev);
unsigned int new_count = channels->combined_count;
- unsigned int old_count = apc->num_queues;
+ struct mana_port_context *scratch;
+ struct mana_qset newq, oldq;
int err;
- /* Set channel_changing to block RDMA from grabbing the vport
- * during the detach/attach window. mana_cfg_vport() checks
- * this flag under vport_mutex and returns -EBUSY if set.
+ if (new_count < 1 || new_count > apc->max_queues) {
+ netdev_err(ndev, "Invalid combined_count %u (max %u)\n",
+ new_count, apc->max_queues);
+ return -EINVAL;
+ }
+
+ if (new_count == apc->num_queues)
+ return 0;
+
+ /* Down: no queues to swap, so record the count and resize the arrays
+ * indexed by it. apc->rxqs has to grow here because mana_open() goes
+ * straight to mana_alloc_queues() without rebuilding the port context.
+ *
+ * RDMA can own the vport while the port is down and derives an EQ from
+ * apc->eqs[] modulo apc->num_queues, so refuse while it is in use.
*/
mutex_lock(&apc->vport_mutex);
- if (!apc->port_is_up && apc->vport_use_count) {
+ if (!apc->port_is_up) {
+ struct mana_rxq **rxqs;
+
+ if (apc->vport_use_count) {
+ mutex_unlock(&apc->vport_mutex);
+ return -EBUSY;
+ }
+
+ rxqs = kzalloc_objs(struct mana_rxq *, new_count);
+ if (!rxqs) {
+ mutex_unlock(&apc->vport_mutex);
+ return -ENOMEM;
+ }
+
+ kfree(apc->rxqs);
+ apc->rxqs = rxqs;
+ apc->num_queues = new_count;
+ mutex_unlock(&apc->vport_mutex);
+ return 0;
+ }
+
+ /* Block RDMA from acquiring the vport for the duration.
+ *
+ * No vport_use_count test here, unlike the branch above: bringing the
+ * port up takes the vport itself, so the count is always non-zero. That
+ * reference is also what makes the swap safe - RAW QPs, the only users
+ * of apc->eqs[] modulo apc->num_queues, cannot exist while the ethernet
+ * port owns it.
+ */
+ if (apc->channel_changing) {
mutex_unlock(&apc->vport_mutex);
return -EBUSY;
}
apc->channel_changing = true;
mutex_unlock(&apc->vport_mutex);
- err = mana_pre_alloc_rxbufs(apc, ndev->mtu, new_count);
- if (err) {
- netdev_err(ndev, "Insufficient memory for new allocations");
+ scratch = mana_qset_scratch_alloc(apc);
+ if (!scratch) {
+ err = -ENOMEM;
goto clear_flag;
}
- err = mana_detach(ndev, false);
- if (err) {
- netdev_err(ndev, "mana_detach failed: %d\n", err);
- goto out;
- }
+ err = mana_alloc_qset(apc, 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 2117a30116c265d6cd9c8223ad4f5ea5d2818455..dd767ab623912a149b4445cea7453034c8d69e37 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -639,6 +639,21 @@ struct mana_port_context {
*/
bool channel_changing;
+ /* mana_publish_qset() could neither publish the new set nor restore the
+ * old one. Vport RX is already off; the port still has to be closed,
+ * which mana_publish_close_if_needed() does once the caller has
+ * released the set that failed.
+ */
+ bool publish_dead_end;
+
+ /* The dead end above took the carrier down for a software reason, not
+ * a link event, so nothing else will ever put it back: the link
+ * handler only runs on a real HWC_DATA_HW_LINK_* transition. Remember
+ * it so the next successful open restores it, which is what makes the
+ * documented "recoverable with a down/up" actually true.
+ */
+ bool carrier_forced_off;
+
/* Net shaper handle*/
struct net_shaper_handle handle;
@@ -724,6 +739,9 @@ 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, 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] 33+ messages in thread
* [PATCH net-next v3 04/13] net: mana: swap queue sets in mana_set_ringparam
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (2 preceding siblings ...)
2026-09-01 1:44 ` [PATCH net-next v3 03/13] net: mana: swap queue sets in mana_set_channels Long Li
@ 2026-09-01 1:44 ` Long Li
2026-09-02 1:45 ` sashiko-bot
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 05/13] net: mana: swap queue sets in mana_set_priv_flags Long Li
` (8 subsequent siblings)
12 siblings, 2 replies; 33+ messages in thread
From: Long Li @ 2026-09-01 1:44 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,
shirazsaleem
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
Convert the ring size path to pre-allocate and swap, for the same reasons
as the channel count path: an allocation failure returns the error with
the running configuration untouched, and the vport is never released.
This drops the fallback on failure. Previously a failed mana_attach() was
retried with the previous values, or the defaults, or the minimums, so a
user who asked for a specific size could end up with a different one with
no indication beyond dmesg. There is nothing to recover from now, so the
error is returned.
Also return early when the requested sizes round to the values already in
use.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../ethernet/microsoft/mana/mana_ethtool.c | 73 +++++++++++++------
1 file changed, 50 insertions(+), 23 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 2ef3d461e707287f6c9dbb04288c8d63a4bc9993..24353dfddde3ce44367b1f27bd1ca3ee81e2bd60 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -766,13 +766,11 @@ static int mana_set_ringparam(struct net_device *ndev,
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);
@@ -790,32 +788,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(apc, 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] 33+ messages in thread
* [PATCH net-next v3 05/13] net: mana: swap queue sets in mana_set_priv_flags
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (3 preceding siblings ...)
2026-09-01 1:44 ` [PATCH net-next v3 04/13] net: mana: swap queue sets in mana_set_ringparam Long Li
@ 2026-09-01 1:44 ` Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 06/13] net: mana: swap queue sets in mana_change_mtu Long Li
` (7 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Long Li @ 2026-09-01 1:44 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,
shirazsaleem
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF changes the RX buffer layout, so
toggling it rebuilds the queues. Convert that path to pre-allocate and
swap: a failure returns the error with both the queues and the flag word
untouched.
The flag becomes part of the queue-set configuration rather than being
written before the rebuild and rolled back on failure, so apc->priv_flags
never describes queues that do not exist. Scheduling queue_reset_work()
on failure goes with it, leaving the TX timeout handler as its only user.
The existing shortcuts keep their behaviour - a down port, or full-page
RX already forced by a jumbo MTU or an attached XDP program, still just
records the value - but now share one condition.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../ethernet/microsoft/mana/mana_ethtool.c | 79 ++++++++++---------
1 file changed, 41 insertions(+), 38 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 24353dfddde3ce44367b1f27bd1ca3ee81e2bd60..eab7df3fb888b3e0cc2e0657965da2ba4190ecd8 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -868,11 +868,15 @@ static u32 mana_get_priv_flags(struct net_device *ndev)
return apc->priv_flags;
}
+/* MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF changes the RX buffer layout, so the
+ * queues have to be rebuilt.
+ */
static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
{
struct mana_port_context *apc = netdev_priv(ndev);
u32 changed = apc->priv_flags ^ priv_flags;
- u32 old_priv_flags = apc->priv_flags;
+ struct mana_port_context *scratch;
+ struct mana_qset newq, oldq;
int err = 0;
if (!changed)
@@ -882,54 +886,53 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
if (priv_flags & ~GENMASK(MANA_PRIV_FLAG_MAX - 1, 0))
return -EINVAL;
- apc->priv_flags = priv_flags;
-
- if (changed & BIT(MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF)) {
- if (!apc->port_is_up)
- return 0;
-
- /* If XDP is attached or MTU is jumbo, single-buffer-per-page
- * is already forced regardless of this flag. Skip the
- * expensive detach/attach cycle since nothing changes.
- */
- if (ndev->mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 ||
- mana_xdp_get(apc))
- return 0;
+ /* Only the RX buffer layout flag requires a queue rebuild. Anything
+ * else, a down port, or a configuration where single-buffer-per-page
+ * is already forced, just records the new value.
+ */
+ if (!(changed & BIT(MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF)) ||
+ !apc->port_is_up ||
+ ndev->mtu + MANA_RXBUF_PAD > PAGE_SIZE / 2 ||
+ mana_xdp_get(apc)) {
+ apc->priv_flags = priv_flags;
+ return 0;
+ }
- /* Block RDMA from grabbing the vport during detach/attach */
- mutex_lock(&apc->vport_mutex);
- apc->channel_changing = true;
+ /* Block RDMA from acquiring the vport for the duration. */
+ mutex_lock(&apc->vport_mutex);
+ if (apc->channel_changing) {
mutex_unlock(&apc->vport_mutex);
+ return -EBUSY;
+ }
+ apc->channel_changing = true;
+ mutex_unlock(&apc->vport_mutex);
- err = mana_pre_alloc_rxbufs(apc, ndev->mtu, apc->num_queues);
- if (err) {
- netdev_err(ndev,
- "Insufficient memory for new allocations\n");
- apc->priv_flags = old_priv_flags;
- goto clear_flag;
- }
+ scratch = mana_qset_scratch_alloc(apc);
+ if (!scratch) {
+ err = -ENOMEM;
+ goto clear_flag;
+ }
- err = mana_detach(ndev, false);
- if (err) {
- netdev_err(ndev, "mana_detach failed: %d\n", err);
- apc->priv_flags = old_priv_flags;
- goto out;
- }
+ err = mana_alloc_qset(apc, scratch, apc->num_queues, apc->rx_queue_size,
+ apc->tx_queue_size, priv_flags, &newq);
+ if (err)
+ goto free_scratch; /* current qset and priv_flags untouched */
- err = mana_attach(ndev);
- if (err) {
- netdev_err(ndev, "mana_attach failed: %d\n", err);
- apc->priv_flags = old_priv_flags;
- }
+ err = mana_publish_qset(apc, &newq, &oldq);
+ if (err) {
+ mana_free_qset(scratch, &newq);
+ goto free_scratch;
}
-out:
- mana_pre_dealloc_rxbufs(apc);
+ mana_free_qset(scratch, &oldq);
+
+free_scratch:
+ mana_publish_close_if_needed(apc);
+ mana_qset_scratch_free(scratch);
clear_flag:
mutex_lock(&apc->vport_mutex);
apc->channel_changing = false;
mutex_unlock(&apc->vport_mutex);
-
return err;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH net-next v3 06/13] net: mana: swap queue sets in mana_change_mtu
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (4 preceding siblings ...)
2026-09-01 1:44 ` [PATCH net-next v3 05/13] net: mana: swap queue sets in mana_set_priv_flags Long Li
@ 2026-09-01 1:44 ` Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 07/13] net: mana: swap queue sets in mana_xdp_set Long Li
` (6 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Long Li @ 2026-09-01 1:44 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,
shirazsaleem
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
The RX buffer layout depends on the MTU, so changing it rebuilds the
queues. Convert mana_change_mtu() to pre-allocate and swap.
The MTU becomes part of the queue-set configuration, so a new set can be
built for the new MTU while the running one still serves traffic at the
old one, and ndev->mtu is updated only once the new set is live.
Previously it was written before mana_attach() and rolled back on
failure, so a failed change was briefly visible to the stack.
Signed-off-by: Long Li <longli@microsoft.com>
---
drivers/net/ethernet/microsoft/mana/mana_en.c | 69 ++++++++++++++-----
.../ethernet/microsoft/mana/mana_ethtool.c | 10 +--
include/net/mana/mana.h | 12 +++-
3 files changed, 67 insertions(+), 24 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 880a3ba37fd3e872dbfeb101c46ac8f527c4bbdd..2c5aa5e5d1a114e1492b99b5213bb532153d1147 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -917,35 +917,49 @@ int mana_pre_alloc_rxbufs(struct mana_port_context *mpc, int new_mtu, int num_qu
return -ENOMEM;
}
+/* ndev->mtu is updated only once the new set is live (mana_publish_qset), so
+ * a failed allocation leaves the queues and the advertised MTU untouched.
+ */
static int mana_change_mtu(struct net_device *ndev, int new_mtu)
{
struct mana_port_context *mpc = netdev_priv(ndev);
- unsigned int old_mtu = ndev->mtu;
+ struct mana_port_context *scratch;
+ struct mana_qset newq, oldq;
int err;
- /* Pre-allocate buffers to prevent failure in mana_attach later */
- err = mana_pre_alloc_rxbufs(mpc, new_mtu, mpc->num_queues);
- if (err) {
- netdev_err(ndev, "Insufficient memory for new MTU\n");
- return err;
+ /* Port is down: no queues to rebuild, just record the new MTU.
+ * mana_open() will size the RX buffers accordingly.
+ */
+ if (!mpc->port_is_up) {
+ mpc->configured_mtu = new_mtu;
+ WRITE_ONCE(ndev->mtu, new_mtu);
+ return 0;
}
- err = mana_detach(ndev, false);
- if (err) {
- netdev_err(ndev, "mana_detach failed: %d\n", err);
- goto out;
- }
+ scratch = mana_qset_scratch_alloc(mpc);
+ if (!scratch)
+ return -ENOMEM;
- WRITE_ONCE(ndev->mtu, new_mtu);
+ err = mana_alloc_qset(mpc, 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;
}
@@ -3195,7 +3209,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);
@@ -4006,6 +4021,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;
}
/* Install @qset's fields onto @ctx. The vport (port_handle,
@@ -4025,6 +4041,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;
}
/**
@@ -4083,7 +4100,7 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
int mana_alloc_qset(struct mana_port_context *apc,
struct mana_port_context *scratch, unsigned int num_queues,
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;
@@ -4095,6 +4112,12 @@ int mana_alloc_qset(struct mana_port_context *apc,
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;
@@ -4335,6 +4358,11 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
if (err)
goto rollback;
+ /* The new set is serving traffic, so advertise its MTU. A no-op unless
+ * the caller is changing it.
+ */
+ WRITE_ONCE(ndev->mtu, apc->configured_mtu);
+
/* Pair with the queue-state stores above: a datapath reader that sees
* the gate open must also see the queue set it is about to index.
*/
@@ -4389,6 +4417,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.
*/
@@ -4600,6 +4630,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 eab7df3fb888b3e0cc2e0657965da2ba4190ecd8..d01add523576f97bea7c02dfdab1073cb24cbca1 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -722,7 +722,8 @@ static int mana_set_channels(struct net_device *ndev,
}
err = mana_alloc_qset(apc, 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 */
@@ -816,7 +817,7 @@ static int mana_set_ringparam(struct net_device *ndev,
}
err = mana_alloc_qset(apc, 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);
@@ -913,8 +914,9 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
goto clear_flag;
}
- err = mana_alloc_qset(apc, scratch, apc->num_queues, apc->rx_queue_size,
- apc->tx_queue_size, priv_flags, &newq);
+ err = mana_alloc_qset(apc, scratch, apc->num_queues,
+ apc->rx_queue_size, 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 dd767ab623912a149b4445cea7453034c8d69e37..765eb5358e9ca2b9977096631db9aab37cedacfd 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -626,6 +626,11 @@ struct mana_port_context {
unsigned int rx_queue_size;
unsigned int tx_queue_size;
+ /* MTU the RX queues were built for. Equal to ndev->mtu except during a
+ * swap, when the new set is built before ndev->mtu is updated.
+ */
+ int configured_mtu;
+
mana_handle_t port_handle;
mana_handle_t pf_filter_handle;
@@ -717,6 +722,11 @@ 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;
};
netdev_tx_t mana_start_xmit(struct sk_buff *skb, struct net_device *ndev);
@@ -738,7 +748,7 @@ void mana_qset_scratch_free(struct mana_port_context *scratch);
int mana_alloc_qset(struct mana_port_context *apc,
struct mana_port_context *scratch, unsigned int num_queues,
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] 33+ messages in thread
* [PATCH net-next v3 07/13] net: mana: swap queue sets in mana_xdp_set
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (5 preceding siblings ...)
2026-09-01 1:44 ` [PATCH net-next v3 06/13] net: mana: swap queue sets in mana_change_mtu Long Li
@ 2026-09-01 1:44 ` Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 08/13] net: mana: do not bail out of mana_detach on dealloc failure Long Li
` (5 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Long Li @ 2026-09-01 1:44 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,
shirazsaleem
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
Attaching or detaching an XDP program changes the RX buffer layout -
full pages with headroom rather than page fragments - so it rebuilds the
queues. Convert mana_xdp_set() to pre-allocate and swap, completing the
removal of detach/attach from the reconfiguration paths.
The program becomes part of the queue-set configuration, so it is
swapped atomically with the queues it was built for and restored by the
rollback path. Program references follow the same swap, so attaching no
longer leaves the queues briefly running without one.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 68 ++++++++++---------
drivers/net/ethernet/microsoft/mana/mana_en.c | 18 +++--
.../ethernet/microsoft/mana/mana_ethtool.c | 11 +--
include/net/mana/mana.h | 9 ++-
4 files changed, 58 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..d2093625956bbef30d0d6c63121de8363b633489 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
@@ -175,10 +175,17 @@ void mana_chn_setxdp(struct mana_port_context *apc, struct bpf_prog *prog)
bpf_prog_put(old_prog);
}
+/* Attaching or detaching XDP changes the RX buffer layout (full pages vs
+ * fragments), so the RX queues are rebuilt. The swap helpers handle
+ * refcounting: mana_publish_qset() attaches the program to the new queues,
+ * mana_free_qset() drops the old set's references.
+ */
static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
struct netlink_ext_ack *extack)
{
struct mana_port_context *apc = netdev_priv(ndev);
+ struct mana_port_context *scratch;
+ struct mana_qset newq, oldq;
struct bpf_prog *old_prog;
struct gdma_context *gc;
int err;
@@ -198,46 +205,46 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
return -EOPNOTSUPP;
}
- /* One refcnt of the prog is hold by the caller already, so
- * don't increase refcnt for this one.
- */
- apc->bpf_prog = prog;
-
if (apc->port_is_up) {
- /* Re-create rxq's after xdp prog was loaded or unloaded.
- * Ex: re create rxq's to switch from full pages to smaller
- * size page fragments when xdp prog is unloaded and
- * vice-versa.
- */
-
- /* Pre-allocate buffers to prevent failure in mana_attach */
- err = mana_pre_alloc_rxbufs(apc, ndev->mtu, apc->num_queues);
- if (err) {
+ scratch = mana_qset_scratch_alloc(apc);
+ if (!scratch) {
NL_SET_ERR_MSG_MOD(extack,
- "XDP: Insufficient memory for tx/rx re-config");
- return err;
+ "XDP: Insufficient memory for re-config");
+ return -ENOMEM;
}
- err = mana_detach(ndev, false);
+ err = mana_alloc_qset(apc, scratch, apc->num_queues,
+ apc->rx_queue_size, apc->tx_queue_size,
+ apc->priv_flags, apc->configured_mtu,
+ prog, &newq);
if (err) {
- netdev_err(ndev,
- "mana_detach failed at xdp set: %d\n", err);
NL_SET_ERR_MSG_MOD(extack,
- "XDP: Re-config failed at detach");
- goto err_dealloc_rxbuffs;
+ "XDP: Re-config failed at alloc");
+ mana_qset_scratch_free(scratch);
+ return err;
}
- err = mana_attach(ndev);
+ err = mana_publish_qset(apc, &newq, &oldq);
if (err) {
- netdev_err(ndev,
- "mana_attach failed at xdp set: %d\n", err);
NL_SET_ERR_MSG_MOD(extack,
- "XDP: Re-config failed at attach");
- goto err_dealloc_rxbuffs;
+ "XDP: Re-config failed at publish");
+ mana_free_qset(scratch, &newq);
+ /* After the cleanup above: closing destroys the EQ pool
+ * those queues' CQs were attached to.
+ */
+ mana_publish_close_if_needed(apc);
+ mana_qset_scratch_free(scratch);
+ return err;
}
- mana_chn_setxdp(apc, prog);
- mana_pre_dealloc_rxbufs(apc);
+ mana_free_qset(scratch, &oldq);
+ mana_qset_scratch_free(scratch);
+ } else {
+ /* No queues to rebuild; mana_open() will size the RX buffers
+ * for this program. One refcnt is held by the caller already,
+ * so don't take another.
+ */
+ apc->bpf_prog = prog;
}
if (old_prog)
@@ -250,11 +257,6 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
ndev->max_mtu = gc->adapter_mtu - ETH_HLEN;
return 0;
-
-err_dealloc_rxbuffs:
- apc->bpf_prog = old_prog;
- mana_pre_dealloc_rxbufs(apc);
- return err;
}
int mana_bpf(struct net_device *ndev, struct netdev_bpf *bpf)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 2c5aa5e5d1a114e1492b99b5213bb532153d1147..af2fdd5e876eb100cfafb00f2eb0ad7aa9a165c7 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -940,9 +940,9 @@ static int mana_change_mtu(struct net_device *ndev, int new_mtu)
if (!scratch)
return -ENOMEM;
- err = mana_alloc_qset(mpc, scratch, mpc->num_queues,
- mpc->rx_queue_size, mpc->tx_queue_size,
- mpc->priv_flags, new_mtu, &newq);
+ 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)
goto free_scratch; /* current qset and ndev->mtu untouched */
@@ -4022,6 +4022,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;
}
/* Install @qset's fields onto @ctx. The vport (port_handle,
@@ -4042,6 +4043,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;
}
/**
@@ -4100,7 +4102,8 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
int mana_alloc_qset(struct mana_port_context *apc,
struct mana_port_context *scratch, unsigned int num_queues,
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;
@@ -4112,11 +4115,12 @@ int mana_alloc_qset(struct mana_port_context *apc,
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 d01add523576f97bea7c02dfdab1073cb24cbca1..82bfd22cfe820c17aea66d6d3f6289165bf32894 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -723,7 +723,7 @@ static int mana_set_channels(struct net_device *ndev,
err = mana_alloc_qset(apc, 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 */
@@ -817,7 +817,8 @@ static int mana_set_ringparam(struct net_device *ndev,
}
err = mana_alloc_qset(apc, 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);
@@ -914,9 +915,9 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
goto clear_flag;
}
- err = mana_alloc_qset(apc, scratch, apc->num_queues,
- apc->rx_queue_size, apc->tx_queue_size,
- priv_flags, apc->configured_mtu, &newq);
+ 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)
goto free_scratch; /* current qset and priv_flags untouched */
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index 765eb5358e9ca2b9977096631db9aab37cedacfd..61d136736cf6c455a132d7297badf5ab15b4693d 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -722,11 +722,13 @@ 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;
+
};
netdev_tx_t mana_start_xmit(struct sk_buff *skb, struct net_device *ndev);
@@ -748,7 +750,8 @@ void mana_qset_scratch_free(struct mana_port_context *scratch);
int mana_alloc_qset(struct mana_port_context *apc,
struct mana_port_context *scratch, unsigned int num_queues,
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] 33+ messages in thread
* [PATCH net-next v3 08/13] net: mana: do not bail out of mana_detach on dealloc failure
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (6 preceding siblings ...)
2026-09-01 1:44 ` [PATCH net-next v3 07/13] net: mana: swap queue sets in mana_xdp_set Long Li
@ 2026-09-01 1:44 ` Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 09/13] net: mana: keep per-queue statistics in the port context Long Li
` (4 subsequent siblings)
12 siblings, 1 reply; 33+ messages in thread
From: Long Li @ 2026-09-01 1:44 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,
shirazsaleem
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 af2fdd5e876eb100cfafb00f2eb0ad7aa9a165c7..5c5d1edd20f84d0d1a20d2a5bc0ecefea0d7aaed 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -4594,10 +4594,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] 33+ messages in thread
* [PATCH net-next v3 09/13] net: mana: keep per-queue statistics in the port context
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (7 preceding siblings ...)
2026-09-01 1:44 ` [PATCH net-next v3 08/13] net: mana: do not bail out of mana_detach on dealloc failure Long Li
@ 2026-09-01 1:44 ` Long Li
2026-09-02 1:45 ` sashiko-bot
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 10/13] net: mana: release EQs left idle by a channel-count reduction Long Li
` (3 subsequent siblings)
12 siblings, 2 replies; 33+ messages in thread
From: Long Li @ 2026-09-01 1:44 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,
shirazsaleem
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
struct mana_rxq and struct mana_txq embed their statistics by value, so
every reconfiguration - ethtool channel count, ring size and private
flags, MTU changes, XDP attach - destroys and recreates them, resetting
the interface counters. rx_bytes can be observed going backwards:
rx_bytes before: 4475831638
rx_bytes after: 526629152
Move them into port-context arrays sized to max_queues and allocated for
the lifetime of the port, so a queue set can be freed without losing what
it accumulated. ndo_get_stats64() walks max_queues, so counters from
queues a later reconfiguration removed are still reported and the totals
stay monotonic. The ethtool per-queue statistics keep iterating the
current count, which is what sizes their string table.
Counters reset on detach/attach the same way before this, and are kept
across it now too; it matters more as reconfiguration is hitless and so
typically done on a live link.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 4 +-
drivers/net/ethernet/microsoft/mana/mana_en.c | 248 ++++++++++++++++--
.../ethernet/microsoft/mana/mana_ethtool.c | 37 ++-
include/net/mana/mana.h | 58 +++-
4 files changed, 308 insertions(+), 39 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index d2093625956bbef30d0d6c63121de8363b633489..0981327c284413f8a0e93856939a4b3c0f632d80 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 = mana_rxq_stats(rxq);
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 5c5d1edd20f84d0d1a20d2a5bc0ecefea0d7aaed..69e34fab8fe8ead53ecac5820ab4932c84af5371 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -414,7 +414,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 &&
@@ -593,7 +593,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);
@@ -639,9 +639,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;
@@ -649,6 +649,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)
@@ -656,8 +662,22 @@ static void mana_get_stats64(struct net_device *ndev,
st->rx_missed_errors = apc->ac->hc_stats.hc_rx_discards_no_wqe;
+ /* The live queue at each index and whatever retired there both count,
+ * so add the two slots.
+ */
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);
+ packets = rx_stats->packets;
+ bytes = rx_stats->bytes;
+ } while (u64_stats_fetch_retry(&rx_stats->syncp, start));
+
+ st->rx_packets += packets;
+ st->rx_bytes += bytes;
+
+ rx_stats = &apc->rxq_stats_ret[q];
do {
start = u64_stats_fetch_begin(&rx_stats->syncp);
@@ -670,7 +690,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);
@@ -1092,6 +1112,122 @@ static void mana_cleanup_port_context(struct mana_port_context *apc)
apc->rxqs = NULL;
}
+/* Counters belong to the port, not the queues, so a queue-set replacement
+ * does not reset them. Sized to max_queues, allocated once.
+ *
+ * A swap adds no writer to a TX slot. RX slots do overlap briefly, since a
+ * retiring rxq keeps its NAPI until mana_free_qset() destroys it. MANA is
+ * 64-bit only, so u64_stats_sync has no seqcount and at worst a few
+ * increments are lost; the alternatives are a lock in the receive path or
+ * per-set slots that make ndo_get_stats64() dip during a swap.
+ */
+static int mana_alloc_queue_stats(struct mana_port_context *apc)
+{
+ unsigned int i;
+
+ apc->rxq_stats = kcalloc(apc->max_queues, sizeof(*apc->rxq_stats),
+ GFP_KERNEL);
+ if (!apc->rxq_stats)
+ return -ENOMEM;
+
+ apc->rxq_stats_ret = kcalloc(apc->max_queues,
+ sizeof(*apc->rxq_stats_ret), GFP_KERNEL);
+ if (!apc->rxq_stats_ret)
+ goto free_rxq_stats;
+
+ apc->txq_stats = kcalloc(apc->max_queues, sizeof(*apc->txq_stats),
+ GFP_KERNEL);
+ if (!apc->txq_stats)
+ goto free_rxq_stats_ret;
+
+ for (i = 0; i < apc->max_queues; i++) {
+ u64_stats_init(&apc->rxq_stats[i].syncp);
+ u64_stats_init(&apc->rxq_stats_ret[i].syncp);
+ u64_stats_init(&apc->txq_stats[i].syncp);
+ }
+
+ return 0;
+
+free_rxq_stats_ret:
+ kfree(apc->rxq_stats_ret);
+ apc->rxq_stats_ret = NULL;
+free_rxq_stats:
+ kfree(apc->rxq_stats);
+ apc->rxq_stats = NULL;
+ return -ENOMEM;
+}
+
+static void mana_free_queue_stats(struct mana_port_context *apc)
+{
+ kfree(apc->rxq_stats);
+ apc->rxq_stats = NULL;
+ kfree(apc->rxq_stats_ret);
+ apc->rxq_stats_ret = NULL;
+ kfree(apc->txq_stats);
+ apc->txq_stats = NULL;
+}
+
+/* Add what @rxq counted while retiring to the per-index total. Must run under
+ * RTNL with the queue no longer writing to @drain_stats, so this is the only
+ * writer of the retired slot.
+ *
+ * Clears @drain_stats as it goes: a queue that survives a failed swap resumes
+ * counting into its live slot, and must not have these counts folded a second
+ * time when it is eventually destroyed.
+ */
+static void mana_fold_rxq_stats(struct mana_port_context *apc,
+ struct mana_rxq *rxq)
+{
+ struct mana_stats_rx *src = &rxq->drain_stats;
+ struct mana_stats_rx *dst;
+ unsigned int i;
+
+ ASSERT_RTNL();
+
+ if (!apc->rxq_stats_ret || rxq->rxq_idx >= apc->max_queues)
+ return;
+
+ dst = &apc->rxq_stats_ret[rxq->rxq_idx];
+
+ u64_stats_update_begin(&dst->syncp);
+ dst->packets += src->packets;
+ dst->bytes += src->bytes;
+ dst->xdp_drop += src->xdp_drop;
+ dst->xdp_tx += src->xdp_tx;
+ dst->xdp_redirect += src->xdp_redirect;
+ dst->pkt_len0_err += src->pkt_len0_err;
+ for (i = 0; i < ARRAY_SIZE(dst->coalesced_cqe); i++)
+ dst->coalesced_cqe[i] += src->coalesced_cqe[i];
+ u64_stats_update_end(&dst->syncp);
+
+ src->packets = 0;
+ src->bytes = 0;
+ src->xdp_drop = 0;
+ src->xdp_tx = 0;
+ src->xdp_redirect = 0;
+ src->pkt_len0_err = 0;
+ for (i = 0; i < ARRAY_SIZE(src->coalesced_cqe); i++)
+ src->coalesced_cqe[i] = 0;
+}
+
+/* Publish what every queue in @qset counted while it was marked retiring.
+ * For a set that is being destroyed this happens queue by queue; a set handed
+ * back by a failed swap needs it done in one pass, before it serves again.
+ */
+static void mana_fold_qset_rx_stats(struct mana_port_context *apc,
+ struct mana_qset *qset)
+{
+ unsigned int q;
+
+ if (!qset->rxqs)
+ return;
+
+ for (q = 0; q < qset->num_queues; q++) {
+ if (qset->rxqs[q])
+ mana_fold_rxq_stats(apc, qset->rxqs[q]);
+ }
+}
+
static void mana_cleanup_indir_table(struct mana_port_context *apc)
{
apc->indir_table_sz = 0;
@@ -2220,7 +2356,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 = mana_rxq_stats(rxq);
struct net_device *ndev = rxq->ndev;
u16 rxq_idx = rxq->rxq_idx;
struct napi_struct *napi;
@@ -2453,6 +2589,7 @@ static void mana_process_rx_cqe(struct mana_rxq *rxq, struct mana_cq *cq,
struct net_device *ndev = rxq->ndev;
struct mana_recv_buf_oob *rxbuf_oob;
struct mana_port_context *apc;
+ struct mana_stats_rx *rx_stats;
struct device *dev = gc->dev;
bool coalesced_8 = false;
bool coalesced = false;
@@ -2534,13 +2671,15 @@ 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);
+ rx_stats = mana_rxq_stats(rxq);
+ u64_stats_update_begin(&rx_stats->syncp);
+ rx_stats->coalesced_cqe[pkt_i - 2]++;
+ u64_stats_update_end(&rx_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);
+ rx_stats = mana_rxq_stats(rxq);
+ u64_stats_update_begin(&rx_stats->syncp);
+ rx_stats->pkt_len0_err++;
+ u64_stats_update_end(&rx_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);
@@ -2672,8 +2811,15 @@ 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);
+ /* A retiring queue counts elsewhere and is about to be destroyed, so
+ * there is no moderation left to tune and its samples would step off
+ * the shared slot onto a counter that restarts at zero.
+ */
+ if (READ_ONCE(rxq->retiring))
+ return;
+
+ dim_update_sample(READ_ONCE(cq->dim_event_ctr), rxq->stats->packets,
+ rxq->stats->bytes, &dim_sample);
net_dim(&cq->dim, &dim_sample);
}
@@ -2890,7 +3036,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);
@@ -3017,6 +3163,11 @@ static void mana_destroy_rxq(struct mana_port_context *apc,
netif_napi_del_locked(napi);
}
+ /* No poller left, so this is the last chance to keep what the queue
+ * counted after it stopped being the live one.
+ */
+ mana_fold_rxq_stats(apc, rxq);
+
if (xdp_rxq_info_is_reg(&rxq->xdp_rxq))
xdp_rxq_info_unreg(&rxq->xdp_rxq);
@@ -3205,6 +3356,9 @@ 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];
+ u64_stats_init(&rxq->drain_stats.syncp);
rxq->num_rx_buf = apc->rx_queue_size;
rxq->rxq_idx = rxq_idx;
rxq->rxobj = INVALID_MANA_HANDLE;
@@ -3355,8 +3509,6 @@ static int mana_add_rx_queues(struct mana_port_context *apc,
goto out;
}
- u64_stats_init(&rxq->stats.syncp);
-
apc->rxqs[i] = rxq;
mana_create_rxq_debugfs(apc, i);
@@ -4230,16 +4382,33 @@ static void mana_start_txqs(struct mana_port_context *apc)
* A queue both sets own must end up unmarked, so callers mark the leaving set
* first and unmark the incoming one second.
*/
-static void mana_qset_set_retiring(struct mana_qset *qset, bool retiring)
+static void mana_qset_set_retiring(struct mana_qset *qset,
+ const struct mana_qset *keep, bool retiring)
{
unsigned int q;
- if (!qset->tx_qp)
- return;
-
for (q = 0; q < qset->num_queues; q++) {
- if (qset->tx_qp[q])
+ if (qset->tx_qp && qset->tx_qp[q])
WRITE_ONCE(qset->tx_qp[q]->txq.retiring, retiring);
+
+ if (!qset->rxqs || !qset->rxqs[q])
+ continue;
+
+ /* A queue @keep carries over serves the same index before and
+ * after, so it stays the live writer of that index. Marking it
+ * would strand the counts it takes during the swap in
+ * drain_stats, which only mana_destroy_rxq() drains.
+ */
+ if (retiring && keep && q < keep->num_queues &&
+ keep->rxqs && keep->rxqs[q] == qset->rxqs[q])
+ continue;
+
+ /* Send this queue's counters to its own storage rather than
+ * the shared per-index slot, which its replacement is about
+ * to own. The caller's synchronize_net() makes the change
+ * visible before that replacement can receive.
+ */
+ WRITE_ONCE(qset->rxqs[q]->retiring, retiring);
}
}
@@ -4327,7 +4496,7 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
* the gate reopens, or it could wake a netdev queue that its
* replacement had stopped on a full ring.
*/
- mana_qset_set_retiring(out_old, true);
+ mana_qset_set_retiring(out_old, newq, 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,
@@ -4338,7 +4507,7 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
/* Anything the incoming set carries over is staying, so clear the flag
* again - after the marking above, before the gate reopens.
*/
- mana_qset_set_retiring(newq, false);
+ mana_qset_set_retiring(newq, NULL, false);
mana_qset_install(apc, newq);
apc->rss_state = apc->num_queues > 1 ? TRI_STATE_TRUE : TRI_STATE_FALSE;
@@ -4384,8 +4553,23 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
/* 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_set_retiring(newq, out_old, true);
+
+ /* Same grace period as the forward path: a poll that sampled the flag
+ * before the line above must finish before @out_old is unmarked, or
+ * both sets would briefly count into apc->rxq_stats[].
+ */
+ synchronize_net();
+
+ mana_qset_set_retiring(out_old, NULL, false);
+
+ /* @out_old counted into drain_stats while it was marked, and it is
+ * about to serve again rather than be destroyed, so nothing else
+ * would ever publish those packets. Fold them now, once the polls
+ * that still saw the flag above have finished writing.
+ */
+ synchronize_net();
+ mana_fold_qset_rx_stats(apc, out_old);
mana_qset_install(apc, out_old);
apc->rss_state = apc->num_queues > 1 ? TRI_STATE_TRUE : TRI_STATE_FALSE;
@@ -4647,6 +4831,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;
@@ -4669,7 +4857,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)
@@ -4706,6 +4894,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);
@@ -5046,6 +5239,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 82bfd22cfe820c17aea66d6d3f6289165bf32894..4d7e64b1d32d9eca5fc006e13941f72f2176a131 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -242,6 +242,12 @@ static void mana_get_ethtool_stats(struct net_device *ndev,
u64 xdp_tx;
u64 pkt_len0_err;
u64 coalesced_cqe[MANA_CQE_COAL_PKTS_8 - 1];
+ u64 ret_coalesced_cqe[MANA_CQE_COAL_PKTS_8 - 1];
+ u64 ret_packets, ret_bytes;
+ u64 ret_xdp_redirect;
+ u64 ret_pkt_len0_err;
+ u64 ret_xdp_drop;
+ u64 ret_xdp_tx;
u64 tso_packets;
u64 tso_bytes;
u64 tso_inner_packets;
@@ -271,7 +277,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);
@@ -285,6 +291,33 @@ static void mana_get_ethtool_stats(struct net_device *ndev,
coalesced_cqe[j] = rx_stats->coalesced_cqe[j];
} while (u64_stats_fetch_retry(&rx_stats->syncp, start));
+ /* Same index, counted by queues that have since retired. Read
+ * into its own snapshot, since a retry must not add twice.
+ */
+ rx_stats = &apc->rxq_stats_ret[q];
+
+ do {
+ start = u64_stats_fetch_begin(&rx_stats->syncp);
+ ret_packets = rx_stats->packets;
+ ret_bytes = rx_stats->bytes;
+ ret_xdp_drop = rx_stats->xdp_drop;
+ ret_xdp_tx = rx_stats->xdp_tx;
+ ret_xdp_redirect = rx_stats->xdp_redirect;
+ ret_pkt_len0_err = rx_stats->pkt_len0_err;
+ for (j = 0; j < MANA_CQE_COAL_PKTS_8 - 1; j++)
+ ret_coalesced_cqe[j] =
+ rx_stats->coalesced_cqe[j];
+ } while (u64_stats_fetch_retry(&rx_stats->syncp, start));
+
+ packets += ret_packets;
+ bytes += ret_bytes;
+ xdp_drop += ret_xdp_drop;
+ xdp_tx += ret_xdp_tx;
+ xdp_redirect += ret_xdp_redirect;
+ pkt_len0_err += ret_pkt_len0_err;
+ for (j = 0; j < MANA_CQE_COAL_PKTS_8 - 1; j++)
+ coalesced_cqe[j] += ret_coalesced_cqe[j];
+
data[i++] = packets;
data[i++] = bytes;
data[i++] = xdp_drop;
@@ -296,7 +329,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 61d136736cf6c455a132d7297badf5ab15b4693d..9a43856760776fb2c23786bbdbb36fdbe5d2be81 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -102,7 +102,10 @@ struct mana_stats_rx {
u64 pkt_len0_err;
u64 coalesced_cqe[MANA_CQE_COAL_PKTS_8 - 1];
struct u64_stats_sync syncp;
-};
+ /* Per-port array indexed by queue, so keep entries on separate cache
+ * lines: queues polled on different CPUs would bounce a shared one.
+ */
+} ____cacheline_aligned_in_smp;
struct mana_stats_tx {
u64 packets;
@@ -117,7 +120,8 @@ struct mana_stats_tx {
u64 csum_partial;
u64 mana_map_err;
struct u64_stats_sync syncp;
-};
+ /* Per-queue array entry, same cache line reasoning as the RX side. */
+} ____cacheline_aligned_in_smp;
struct mana_txq {
struct gdma_queue *gdma_sq;
@@ -146,14 +150,14 @@ struct mana_txq {
/* Value of mana_context.reset_gen when this queue was created. */
u32 reset_gen;
- /* Set once this queue has been unpublished and is on its way out.
- * Its completions must not touch flow control any more: net_txq is
- * shared with the queue that replaced it at the same index, and a
- * draining queue always looks like it has room.
+ /* Unpublished and draining. Its completions must leave flow control
+ * alone: net_txq is shared with its replacement, and a draining queue
+ * always looks like it has room.
*/
bool retiring;
- struct mana_stats_tx stats;
+ /* Points into apc->txq_stats[], which outlives the queue. */
+ struct mana_stats_tx *stats;
};
/* skb data and frags dma mappings */
@@ -415,7 +419,23 @@ struct mana_rxq {
u32 buf_index;
- struct mana_stats_rx stats;
+ /* Points into apc->rxq_stats[], which outlives the queue. Only the
+ * live queue at this index writes there; once retiring is set this
+ * queue counts into drain_stats instead, so the slot has one writer.
+ * Use mana_rxq_stats() rather than either directly.
+ */
+ struct mana_stats_rx *stats;
+
+ /* Set under RTNL before a different queue takes over this index. A
+ * queue carried across a swap keeps serving its index and is never
+ * marked.
+ */
+ bool retiring;
+
+ /* What this queue counted after it stopped being the live one.
+ * Folded into apc->rxq_stats_ret[] when the queue is destroyed.
+ */
+ struct mana_stats_rx drain_stats;
struct bpf_prog __rcu *bpf_prog;
struct xdp_rxq_info xdp_rxq;
@@ -623,6 +643,19 @@ struct mana_port_context {
unsigned int max_queues;
unsigned int num_queues;
+ /* Per-queue counters, max_queues entries each. Allocated at probe and
+ * freed at remove, never on queue teardown, so a reconfiguration does
+ * not reset them.
+ *
+ * rxq_stats[] is written by the live RX queue at that index and
+ * rxq_stats_ret[] only under RTNL, by mana_destroy_rxq() folding in
+ * what a retiring queue counted while it drained. One writer each;
+ * readers add the two.
+ */
+ struct mana_stats_rx *rxq_stats;
+ struct mana_stats_rx *rxq_stats_ret;
+ struct mana_stats_tx *txq_stats;
+
unsigned int rx_queue_size;
unsigned int tx_queue_size;
@@ -747,6 +780,15 @@ int mana_detach(struct net_device *ndev, bool from_close);
struct mana_port_context *
mana_qset_scratch_alloc(struct mana_port_context *apc);
void mana_qset_scratch_free(struct mana_port_context *scratch);
+/* Where @rxq counts. A retiring queue is no longer the one serving its index,
+ * so it counts into its own storage and leaves the shared slot to whatever
+ * replaced it. Nothing is lost: mana_destroy_rxq() folds it back.
+ */
+static inline struct mana_stats_rx *mana_rxq_stats(struct mana_rxq *rxq)
+{
+ return READ_ONCE(rxq->retiring) ? &rxq->drain_stats : rxq->stats;
+}
+
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,
--
2.43.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH net-next v3 10/13] net: mana: release EQs left idle by a channel-count reduction
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (8 preceding siblings ...)
2026-09-01 1:44 ` [PATCH net-next v3 09/13] net: mana: keep per-queue statistics in the port context Long Li
@ 2026-09-01 1:44 ` Long Li
2026-09-02 1:45 ` sashiko-bot
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 11/13] net: mana: keep a user-configured RSS table across a queue rebuild Long Li
` (2 subsequent siblings)
12 siblings, 2 replies; 33+ messages in thread
From: Long Li @ 2026-09-01 1:44 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,
shirazsaleem
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
The shared EQ pool only grows, so it sits at the high-water mark of every
channel count the port has ever used. After "ethtool -L ens1 combined 32"
then "combined 4" the port still holds 32 EQs while using four:
# ls /sys/kernel/debug/mana/7870:00:00.0/vport0/EQs | wc -l
32
The pre-swap path recreated every EQ per reconfiguration, so this is new.
Release the EQs above the live queue count once a retiring set has been
torn down. That is the only safe point: a CQ holds the gdma_queue pointer
of its parent EQ, so an EQ may only be destroyed once the set referencing
it is gone.
The interrupts stay listed in /proc/interrupts either way, since
mana_gd_setup_irqs() references every vector at probe. What a shrink
returns is the pool slot, so a later increase can take it again.
mana_create_eq_debugfs() stored the new dentry in a stack copy rather than
in apc->eqs[i], leaving that field NULL. Nothing read it back before, since
teardown removed the parent directory recursively. The shrink above removes
one EQ's directory, so it needs the dentry actually recorded.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 4 +-
drivers/net/ethernet/microsoft/mana/mana_en.c | 73 +++++++++++++++----
.../ethernet/microsoft/mana/mana_ethtool.c | 15 ++--
include/net/mana/mana.h | 3 +-
4 files changed, 70 insertions(+), 25 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index 0981327c284413f8a0e93856939a4b3c0f632d80..547fc8c769cb4c4f440a80d19092cab86e19894b 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
@@ -228,7 +228,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
if (err) {
NL_SET_ERR_MSG_MOD(extack,
"XDP: Re-config failed at publish");
- mana_free_qset(scratch, &newq);
+ mana_free_qset(apc, scratch, &newq);
/* After the cleanup above: closing destroys the EQ pool
* those queues' CQs were attached to.
*/
@@ -237,7 +237,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
return err;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(apc, scratch, &oldq);
mana_qset_scratch_free(scratch);
} else {
/* No queues to rebuild; mana_open() will size the RX buffers
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 69e34fab8fe8ead53ecac5820ab4932c84af5371..2295769da0306888f0a0bd7bb7a6098e908289f1 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -968,16 +968,13 @@ static int mana_change_mtu(struct net_device *ndev, int new_mtu)
err = mana_publish_qset(mpc, &newq, &oldq);
if (err) {
- mana_free_qset(scratch, &newq);
+ mana_free_qset(mpc, scratch, &newq);
goto free_scratch;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(mpc, scratch, &oldq);
free_scratch:
- /* After the caller-side cleanup above, so the EQ pool outlives the
- * CQs that reference it.
- */
mana_publish_close_if_needed(mpc);
mana_qset_scratch_free(scratch);
return err;
@@ -1938,6 +1935,9 @@ void mana_destroy_eq(struct mana_port_context *apc)
msi = eq->eq.msix_index;
mana_gd_destroy_queue(gc, eq);
mana_gd_put_gic(gc, !gc->msi_sharing, msi);
+ apc->eqs[i].eq = NULL;
+ /* Freed with the parent by debugfs_remove_recursive() above. */
+ apc->eqs[i].mana_eq_debugfs = NULL;
}
kfree(apc->eqs);
@@ -1948,15 +1948,16 @@ EXPORT_SYMBOL_NS(mana_destroy_eq, "NET_MANA");
static void mana_create_eq_debugfs(struct mana_port_context *apc, int i)
{
- struct mana_eq eq = apc->eqs[i];
+ struct mana_eq *eq = &apc->eqs[i];
char eqnum[32];
sprintf(eqnum, "eq%d", i);
- eq.mana_eq_debugfs = debugfs_create_dir(eqnum, apc->mana_eqs_debugfs);
- debugfs_create_u32("head", 0400, eq.mana_eq_debugfs, &eq.eq->head);
- debugfs_create_u32("tail", 0400, eq.mana_eq_debugfs, &eq.eq->tail);
- debugfs_create_u32("irq", 0400, eq.mana_eq_debugfs, &eq.eq->eq.irq);
- debugfs_create_file("eq_dump", 0400, eq.mana_eq_debugfs, eq.eq, &mana_dbg_q_fops);
+ eq->mana_eq_debugfs = debugfs_create_dir(eqnum, apc->mana_eqs_debugfs);
+ debugfs_create_u32("head", 0400, eq->mana_eq_debugfs, &eq->eq->head);
+ debugfs_create_u32("tail", 0400, eq->mana_eq_debugfs, &eq->eq->tail);
+ debugfs_create_u32("irq", 0400, eq->mana_eq_debugfs, &eq->eq->eq.irq);
+ debugfs_create_file("eq_dump", 0400, eq->mana_eq_debugfs, eq->eq,
+ &mana_dbg_q_fops);
}
int mana_create_eq(struct mana_port_context *apc)
@@ -2084,6 +2085,37 @@ static int mana_grow_eqs(struct mana_port_context *apc, unsigned int need)
return err;
}
+/* Release EQs above @keep, returning the MSI-X vectors freed. Only safe once
+ * no set references them, i.e. after mana_free_qset(), or a live CQ would
+ * point at a destroyed EQ.
+ */
+static void mana_shrink_eqs(struct mana_port_context *apc, unsigned int keep)
+{
+ struct gdma_context *gc = apc->ac->gdma_dev->gdma_context;
+ struct gdma_queue *eq;
+ unsigned int msi;
+ unsigned int i;
+
+ if (!apc->eqs || keep >= apc->num_eqs)
+ return;
+
+ for (i = keep; i < apc->num_eqs; i++) {
+ eq = apc->eqs[i].eq;
+ if (!eq)
+ continue;
+
+ debugfs_remove_recursive(apc->eqs[i].mana_eq_debugfs);
+ apc->eqs[i].mana_eq_debugfs = NULL;
+
+ msi = eq->eq.msix_index;
+ mana_gd_destroy_queue(gc, eq);
+ mana_gd_put_gic(gc, !gc->msi_sharing, msi);
+ apc->eqs[i].eq = NULL;
+ }
+
+ apc->num_eqs = keep;
+}
+
static int mana_fence_rq(struct mana_port_context *apc, struct mana_rxq *rxq)
{
struct mana_fence_rq_resp resp = {};
@@ -4319,6 +4351,13 @@ int mana_alloc_qset(struct mana_port_context *apc,
kfree(scratch->rxqs);
scratch->rxqs = NULL;
out_err:
+ /* Give back any EQ this attempt added to the shared pool rather than
+ * holding its MSI-X vectors until some later teardown: the live set
+ * still needs only apc->num_queues of them. Safe here because this
+ * set's CQs have already been destroyed above.
+ */
+ mana_shrink_eqs(apc, apc->num_queues);
+
netdev_err(ndev, "%s(num_queues=%u) failed: %d\n", __func__,
num_queues, err);
return err;
@@ -4649,7 +4688,8 @@ static void mana_qset_debugfs_publish(struct mana_port_context *apc)
/* Tear down @qset, no longer installed on @apc, against @scratch so the live
* context never points at queues being freed.
*/
-void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
+void mana_free_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, struct mana_qset *qset)
{
struct bpf_prog *retiring_prog;
unsigned int retiring_queues;
@@ -4745,12 +4785,19 @@ void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
memset(qset, 0, sizeof(*qset));
+ /* This set is gone, so any EQ above the live queue count is now
+ * unreferenced. Release those vectors instead of holding them at the
+ * high-water mark. Safe here and only here: the retiring set's CQs
+ * have just been destroyed.
+ */
+ mana_shrink_eqs(apc, apc->num_queues);
+
/* Queues built through a scratch context carry no debugfs nodes,
* because both sets are alive during the swap and would collide on
* the same names. The retiring set's nodes are gone now, so the
* published queues can finally take those names.
*/
- mana_qset_debugfs_publish(netdev_priv(scratch->ndev));
+ mana_qset_debugfs_publish(apc);
}
/* --- end of pre-allocate + swap reconfiguration path ---------------------- */
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 4d7e64b1d32d9eca5fc006e13941f72f2176a131..c7ddd26cada258cdbb2dc8410e6af14d2cedae1e 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -762,16 +762,13 @@ static int mana_set_channels(struct net_device *ndev,
err = mana_publish_qset(apc, &newq, &oldq);
if (err) {
- mana_free_qset(scratch, &newq);
+ mana_free_qset(apc, scratch, &newq);
goto free_scratch;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(apc, scratch, &oldq);
free_scratch:
- /* After the caller-side cleanup above, so the EQ pool outlives the
- * CQs that reference it.
- */
mana_publish_close_if_needed(apc);
mana_qset_scratch_free(scratch);
clear_flag:
@@ -862,11 +859,11 @@ static int mana_set_ringparam(struct net_device *ndev,
if (err) {
NL_SET_ERR_MSG_FMT(extack, "failed to change ring params: %d",
err);
- mana_free_qset(scratch, &newq);
+ mana_free_qset(apc, scratch, &newq);
goto free_scratch;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(apc, scratch, &oldq);
free_scratch:
/* After the caller-side cleanup above, so the EQ pool outlives the
@@ -956,11 +953,11 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
err = mana_publish_qset(apc, &newq, &oldq);
if (err) {
- mana_free_qset(scratch, &newq);
+ mana_free_qset(apc, scratch, &newq);
goto free_scratch;
}
- mana_free_qset(scratch, &oldq);
+ mana_free_qset(apc, scratch, &oldq);
free_scratch:
mana_publish_close_if_needed(apc);
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index 9a43856760776fb2c23786bbdbb36fdbe5d2be81..140948b2b45d5afa91e37d72219bb89783a030d5 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -797,7 +797,8 @@ int mana_alloc_qset(struct mana_port_context *apc,
int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
struct mana_qset *out_old);
void mana_publish_close_if_needed(struct mana_port_context *apc);
-void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset);
+void mana_free_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, struct mana_qset *qset);
void mana_dim_change(struct mana_cq *cq, bool enable);
--
2.43.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH net-next v3 11/13] net: mana: keep a user-configured RSS table across a queue rebuild
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (9 preceding siblings ...)
2026-09-01 1:44 ` [PATCH net-next v3 10/13] net: mana: release EQs left idle by a channel-count reduction Long Li
@ 2026-09-01 1:44 ` Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 12/13] net: mana: keep the surviving queues when the channel count is reduced Long Li
2026-09-01 1:44 ` [PATCH net-next v3 13/13] net: mana: keep the existing queues when the channel count is raised Long Li
12 siblings, 1 reply; 33+ messages in thread
From: Long Li @ 2026-09-01 1:44 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,
shirazsaleem
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
A queue rebuild regenerates the RSS indirection table from the driver
default, so a table the user installed with "ethtool -X" is silently
replaced by any reconfiguration that rebuilds the queues.
Carry it over instead when the queue count is unchanged: the entries are
queue indices, so they stay meaningful. A driver-generated table is
still regenerated, and a table that cannot be honoured for the new queue
count is reported to the core as lost - but only once the new set is
actually live, so a failed reconfiguration does not clear
IFF_RXFH_CONFIGURED on a port that kept its old queues.
Signed-off-by: Long Li <longli@microsoft.com>
---
drivers/net/ethernet/microsoft/mana/mana_en.c | 78 +++++++++++++++++--
include/net/mana/mana.h | 6 ++
2 files changed, 79 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 2295769da0306888f0a0bd7bb7a6098e908289f1..6769faada11bcd898d3991d0c286578a736eba12 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -3634,6 +3634,38 @@ static void mana_rss_table_init(struct mana_port_context *apc)
ethtool_rxfh_indir_default(i, apc->num_queues);
}
+/* Whether @apc's indirection table can be carried to a set of @num_queues,
+ * rather than rebuilt from the driver default.
+ *
+ * Only a user table ("ethtool -X") is kept; a driver one is rebuilt to spread
+ * over the new count. ethtool_check_max_channel() already refuses a reduction
+ * that leaves a user table pointing past the last queue, so the bounds check
+ * below only guards rebuild paths that bypass ethtool.
+ *
+ * @lost reports a table that cannot be carried instead of calling
+ * ethtool_rxfh_indir_lost() here, since the swap may still fail and leave the
+ * port on queues where the table still applies. False when none was set.
+ */
+static bool mana_rss_table_keep(struct mana_port_context *apc,
+ unsigned int num_queues, bool *lost)
+{
+ u32 i;
+
+ *lost = false;
+
+ if (!netif_is_rxfh_configured(apc->ndev))
+ return false;
+
+ for (i = 0; i < apc->indir_table_sz; i++) {
+ if (apc->indir_table[i] >= num_queues) {
+ *lost = true;
+ return false;
+ }
+ }
+
+ return true;
+}
+
int mana_disable_vport_rx(struct mana_port_context *apc)
{
return mana_cfg_vport_steering(apc, TRI_STATE_FALSE, false, false,
@@ -3904,6 +3936,7 @@ int mana_alloc_queues(struct net_device *ndev)
{
struct mana_port_context *apc = netdev_priv(ndev);
struct gdma_dev *gd = apc->ac->gdma_dev;
+ bool indir_lost;
int err;
err = mana_create_vport(apc, ndev);
@@ -3949,7 +3982,18 @@ int mana_alloc_queues(struct net_device *ndev)
goto destroy_rxq;
}
- mana_rss_table_init(apc);
+ /* Keep a user-configured RSS table across a rebuild; the entries are
+ * queue indices, so they stay meaningful as long as the queue count
+ * is unchanged. Only a driver-generated table is regenerated here.
+ *
+ * Nothing to roll back to here, so report the loss as soon as it is
+ * decided and keep the table and the core's view of it in step.
+ */
+ if (!mana_rss_table_keep(apc, apc->num_queues, &indir_lost)) {
+ if (indir_lost)
+ ethtool_rxfh_indir_lost(ndev);
+ mana_rss_table_init(apc);
+ }
err = mana_config_rss(apc, TRI_STATE_TRUE, true, true);
if (err) {
@@ -4207,11 +4251,15 @@ static void mana_qset_snapshot(const struct mana_port_context *ctx,
out->priv_flags = ctx->priv_flags;
out->mtu = ctx->configured_mtu;
out->bpf_prog = ctx->bpf_prog;
+
+ /* A set taken from a live context has nothing pending; the builders
+ * set this after snapshotting if they had to drop the user's table.
+ */
+ out->rxfh_indir_lost = false;
}
-/* Install @qset's fields onto @ctx. The vport (port_handle,
- * vport_use_count) and the port-level debugfs dir are deliberately not
- * touched: they outlive any individual queue set.
+/* The vport (port_handle, vport_use_count) and the port-level debugfs dir are
+ * not touched: they outlive any individual queue set.
*/
static void mana_qset_install(struct mana_port_context *ctx,
const struct mana_qset *qset)
@@ -4290,6 +4338,7 @@ int mana_alloc_qset(struct mana_port_context *apc,
struct mana_qset *out)
{
struct net_device *ndev = scratch->ndev;
+ bool indir_lost;
int err;
ASSERT_RTNL();
@@ -4333,9 +4382,19 @@ int mana_alloc_qset(struct mana_port_context *apc,
if (err)
goto cleanup_rxq;
- mana_rss_table_init(scratch);
+ /* Carry a user-configured RSS table over to the new set. The entries
+ * are queue indices, so mana_config_rss() in mana_publish_qset() maps
+ * them onto the new set's RX objects. A driver-generated table is
+ * rebuilt instead, so it covers every queue of the new set.
+ */
+ if (mana_rss_table_keep(apc, num_queues, &indir_lost))
+ memcpy(scratch->indir_table, apc->indir_table,
+ apc->indir_table_sz * sizeof(*apc->indir_table));
+ else
+ mana_rss_table_init(scratch);
mana_qset_snapshot(scratch, out);
+ out->rxfh_indir_lost = indir_lost;
return 0;
cleanup_rxq:
@@ -4583,6 +4642,15 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
WRITE_ONCE(apc->port_is_up, true);
mana_start_txqs(apc);
+ /* The set that could not carry the user's indirection table is the one
+ * serving traffic now, so the table really is gone. Reporting it here
+ * rather than while the set was being built keeps a failed swap from
+ * clearing IFF_RXFH_CONFIGURED on a port that kept its old queues, and
+ * with them a table that is still valid and still programmed.
+ */
+ if (newq->rxfh_indir_lost)
+ ethtool_rxfh_indir_lost(ndev);
+
return 0;
rollback:
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index 140948b2b45d5afa91e37d72219bb89783a030d5..9d08257bc1fc47e7accc30a2ef2ae3db7acc7b9d 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -762,6 +762,12 @@ struct mana_qset {
int mtu;
struct bpf_prog *bpf_prog;
+ /* The user's RSS indirection table could not be carried onto this set,
+ * so a default one was generated for it. mana_publish_qset() tells the
+ * core once this set is live; until then the port is still running on
+ * a set where the user's table applies.
+ */
+ bool rxfh_indir_lost;
};
netdev_tx_t mana_start_xmit(struct sk_buff *skb, struct net_device *ndev);
--
2.43.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH net-next v3 12/13] net: mana: keep the surviving queues when the channel count is reduced
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (10 preceding siblings ...)
2026-09-01 1:44 ` [PATCH net-next v3 11/13] net: mana: keep a user-configured RSS table across a queue rebuild Long Li
@ 2026-09-01 1:44 ` Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 13/13] net: mana: keep the existing queues when the channel count is raised Long Li
12 siblings, 1 reply; 33+ messages in thread
From: Long Li @ 2026-09-01 1:44 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,
shirazsaleem
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
A channel-count reduction rebuilds every queue it keeps, even though none
of their properties change. Shrinking 16 channels to 8 creates 8 TX and 8
RX queues and then destroys all 16 of each:
set_channels 16 -> 8 created SQ=8 RQ=8 | destroyed SQ=16 RQ=16
A queue is derived from the ring sizes, the MTU/priv-flag/XDP buffer
layout and its EQ, none of which depend on the count, and queue i is
bound to EQ i at any count. Carry the survivors over and retire only the
tail: a reduction allocates two pointer arrays and a steering table
instead of a full queue set, the peak stays at one set rather than two,
and the queues that stay keep their page pools, posted RX buffers and
NAPI state.
A ring resize, MTU change, priv-flag toggle or XDP attach changes a
property of every queue, so those still rebuild.
Signed-off-by: Long Li <longli@microsoft.com>
---
drivers/net/ethernet/microsoft/mana/mana_en.c | 130 +++++++++++++++++-
.../ethernet/microsoft/mana/mana_ethtool.c | 42 +++++-
include/net/mana/mana.h | 4 +
3 files changed, 171 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 6769faada11bcd898d3991d0c286578a736eba12..ae7c664bf76ef2740ccbf73315931b4aca7bd43f 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -4327,7 +4327,135 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
kvfree(scratch);
}
-/* Build a queue set in @scratch, sized and configured from the arguments. The
+/* Carve the live set into a kept prefix [0, @new_count) in @out_new and a
+ * tail to retire in @out_tail. @apc is untouched, on failure too.
+ *
+ * Queue i is built from the ring sizes and the buffer layout and keeps EQ i
+ * at any count, so a reduction carries the survivors over and destroys only
+ * the tail. Allocates two pointer arrays and a steering table, nothing else:
+ * the queues that stay keep their page pools, posted buffers and NAPI.
+ */
+int mana_split_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, unsigned int new_count,
+ struct mana_qset *out_new, struct mana_qset *out_tail)
+{
+ unsigned int old_count = apc->num_queues;
+ struct mana_tx_qp **new_tx, **tail_tx;
+ struct mana_rxq **new_rx, **tail_rx;
+ unsigned int tail_count;
+ bool indir_lost;
+ unsigned int i;
+ int err;
+
+ ASSERT_RTNL();
+
+ if (WARN_ON(new_count == 0 || new_count >= old_count))
+ return -EINVAL;
+ if (WARN_ON(!apc->tx_qp || !apc->rxqs))
+ return -EINVAL;
+
+ tail_count = old_count - new_count;
+
+ /* Build the smaller set's steering table separately: mana_config_rss()
+ * would otherwise index the shorter rxqs[] with entries still referring
+ * to retired queues.
+ */
+ scratch->num_queues = new_count;
+ err = mana_rss_table_alloc(scratch);
+ if (err)
+ return err;
+
+ if (mana_rss_table_keep(apc, new_count, &indir_lost))
+ memcpy(scratch->indir_table, apc->indir_table,
+ apc->indir_table_sz * sizeof(*apc->indir_table));
+ else
+ mana_rss_table_init(scratch);
+
+ new_tx = kzalloc_objs(struct mana_tx_qp *, new_count);
+ new_rx = kzalloc_objs(struct mana_rxq *, new_count);
+ tail_tx = kzalloc_objs(struct mana_tx_qp *, tail_count);
+ tail_rx = kzalloc_objs(struct mana_rxq *, tail_count);
+ if (!new_tx || !new_rx || !tail_tx || !tail_rx) {
+ err = -ENOMEM;
+ goto free_arrays;
+ }
+
+ for (i = 0; i < new_count; i++) {
+ new_tx[i] = apc->tx_qp[i];
+ new_rx[i] = apc->rxqs[i];
+ }
+ for (i = 0; i < tail_count; i++) {
+ tail_tx[i] = apc->tx_qp[new_count + i];
+ tail_rx[i] = apc->rxqs[new_count + i];
+ }
+
+ /* The kept prefix, with the new steering table. */
+ out_new->tx_qp = new_tx;
+ out_new->rxqs = new_rx;
+ out_new->indir_table = scratch->indir_table;
+ out_new->indir_table_sz = scratch->indir_table_sz;
+ out_new->rxobj_table = scratch->rxobj_table;
+ out_new->default_rxobj = apc->rxqs[0]->rxobj;
+ out_new->num_queues = new_count;
+ out_new->rx_queue_size = apc->rx_queue_size;
+ out_new->tx_queue_size = apc->tx_queue_size;
+ out_new->priv_flags = apc->priv_flags;
+ out_new->mtu = apc->configured_mtu;
+ out_new->bpf_prog = apc->bpf_prog;
+ out_new->rxfh_indir_lost = indir_lost;
+
+ /* Ownership of the table moved to @out_new. */
+ scratch->indir_table = NULL;
+ scratch->rxobj_table = NULL;
+
+ /* The tail. It owns no steering table; bpf_prog is carried so that
+ * retiring it drops exactly the tail's per-queue program references
+ * and leaves the kept ones alone.
+ */
+ memset(out_tail, 0, sizeof(*out_tail));
+ out_tail->tx_qp = tail_tx;
+ out_tail->rxqs = tail_rx;
+ out_tail->default_rxobj = INVALID_MANA_HANDLE;
+ out_tail->num_queues = tail_count;
+ out_tail->rx_queue_size = apc->rx_queue_size;
+ out_tail->tx_queue_size = apc->tx_queue_size;
+ out_tail->priv_flags = apc->priv_flags;
+ out_tail->mtu = apc->configured_mtu;
+ out_tail->bpf_prog = apc->bpf_prog;
+
+ return 0;
+
+free_arrays:
+ kfree(new_tx);
+ kfree(new_rx);
+ kfree(tail_tx);
+ kfree(tail_rx);
+ mana_cleanup_indir_table(scratch);
+ return err;
+}
+
+/**
+ * mana_discard_split - drop the containers built by mana_split_qset()
+ * @newq: set that was never published
+ * @tailq: matching tail
+ *
+ * Frees the pointer arrays and the steering table only: the queues they refer
+ * to are still owned by the live port context.
+ */
+void mana_discard_split(struct mana_qset *newq, struct mana_qset *tailq)
+{
+ kfree(newq->tx_qp);
+ kfree(newq->rxqs);
+ kfree(newq->indir_table);
+ kfree(newq->rxobj_table);
+ kfree(tailq->tx_qp);
+ kfree(tailq->rxqs);
+ memset(newq, 0, sizeof(*newq));
+ memset(tailq, 0, sizeof(*tailq));
+}
+
+/* Rebuild the queues at the current count in @scratch, for callers changing a
+ * per-queue property; a reduction goes through mana_split_qset() instead. The
* installed set keeps serving traffic meanwhile. On error nothing is left
* allocated.
*/
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index c7ddd26cada258cdbb2dc8410e6af14d2cedae1e..b5587764333f1f357df6b86795c92ef2985f9ed3 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -681,10 +681,11 @@ static int mana_set_coalesce(struct net_device *ndev,
return 0;
}
-/* Build the new queue set while the current one keeps serving traffic, then
- * swap. On failure the existing queues keep running and the requested value is
- * never replaced by a fallback. The vport is never torn down, so RDMA cannot
- * take it mid-reconfiguration.
+/* A reduction leaves every surviving queue configured as it was, so it retires
+ * the tail rather than rebuilding; an increase still builds a new set. On
+ * failure the existing queues keep running and the requested value is never
+ * replaced by a fallback. The vport is never torn down, so RDMA cannot take it
+ * mid-reconfiguration.
*/
static int mana_set_channels(struct net_device *ndev,
struct ethtool_channels *channels)
@@ -754,6 +755,39 @@ static int mana_set_channels(struct net_device *ndev,
goto clear_flag;
}
+ /* A reduction keeps its queues configured identically, so carry them
+ * over and retire only the tail: no DMA ring, no hardware WQ object,
+ * and no old+new peak.
+ */
+ if (new_count < apc->num_queues) {
+ struct mana_qset tailq;
+
+ err = mana_split_qset(apc, scratch, new_count, &newq, &tailq);
+ if (err)
+ goto free_scratch; /* current qset untouched */
+
+ err = mana_publish_qset(apc, &newq, &oldq);
+ if (err) {
+ /* The old set is live again; drop only the containers
+ * built above, never the queues they point at.
+ */
+ mana_discard_split(&newq, &tailq);
+ goto free_scratch;
+ }
+
+ /* @oldq holds the original arrays and steering table. Every
+ * queue they referenced is now owned by either the published
+ * set or the tail, so only the containers are freed here.
+ */
+ kfree(oldq.tx_qp);
+ kfree(oldq.rxqs);
+ kfree(oldq.indir_table);
+ kfree(oldq.rxobj_table);
+
+ mana_free_qset(apc, scratch, &tailq);
+ goto free_scratch;
+ }
+
err = mana_alloc_qset(apc, scratch, new_count, apc->rx_queue_size,
apc->tx_queue_size, apc->priv_flags,
apc->configured_mtu, apc->bpf_prog, &newq);
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index 9d08257bc1fc47e7accc30a2ef2ae3db7acc7b9d..9fd555164df0de9ecd00fb840891025d566c0143 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -800,6 +800,10 @@ int mana_alloc_qset(struct mana_port_context *apc,
unsigned int rx_queue_size, unsigned int tx_queue_size,
u32 priv_flags, int mtu, struct bpf_prog *bpf_prog,
struct mana_qset *out);
+int mana_split_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, unsigned int new_count,
+ struct mana_qset *out_new, struct mana_qset *out_tail);
+void mana_discard_split(struct mana_qset *newq, struct mana_qset *tailq);
int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
struct mana_qset *out_old);
void mana_publish_close_if_needed(struct mana_port_context *apc);
--
2.43.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PATCH net-next v3 13/13] net: mana: keep the existing queues when the channel count is raised
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
` (11 preceding siblings ...)
2026-09-01 1:44 ` [PATCH net-next v3 12/13] net: mana: keep the surviving queues when the channel count is reduced Long Li
@ 2026-09-01 1:44 ` Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
12 siblings, 1 reply; 33+ messages in thread
From: Long Li @ 2026-09-01 1:44 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,
shirazsaleem
Cc: netdev, linux-rdma, linux-hyperv, linux-kernel
The reduction path already carries its surviving queues over. An increase
still builds a complete second set and throws the running one away, even
though it keeps every queue it already had:
set_channels 4 -> 8 created SQ=8 RQ=8 | destroyed SQ=4 RQ=4
The same reasoning applies in both directions. Carry the running queues
over and build only the new tail, so growing 4 channels to 8 creates 4
SQ/RQ pairs instead of 8 and never holds 12 of each against the vport
maximum.
This completes the conversion, so advertise it to the firmware. Every
resize path - channel count, ring size, MTU, the full-page RX private
flag and XDP attach - now builds the new set before retiring the old and
keeps the old one running if that fails.
Signed-off-by: Long Li <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 2 +-
drivers/net/ethernet/microsoft/mana/mana_en.c | 252 +++++++++++++++---
.../ethernet/microsoft/mana/mana_ethtool.c | 51 ++--
include/net/mana/gdma.h | 11 +-
include/net/mana/mana.h | 7 +-
5 files changed, 266 insertions(+), 57 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index 547fc8c769cb4c4f440a80d19092cab86e19894b..ebc20904e5cdb94edae283cfd1318bbdeef15ae6 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
@@ -213,7 +213,7 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
return -ENOMEM;
}
- err = mana_alloc_qset(apc, scratch, apc->num_queues,
+ err = mana_alloc_qset(apc, scratch,
apc->rx_queue_size, apc->tx_queue_size,
apc->priv_flags, apc->configured_mtu,
prog, &newq);
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index ae7c664bf76ef2740ccbf73315931b4aca7bd43f..67bfea57e6ed2d2f852351ac40440a25b1226bc3 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -960,7 +960,7 @@ static int mana_change_mtu(struct net_device *ndev, int new_mtu)
if (!scratch)
return -ENOMEM;
- err = mana_alloc_qset(mpc, scratch, mpc->num_queues, mpc->rx_queue_size,
+ err = mana_alloc_qset(mpc, scratch, mpc->rx_queue_size,
mpc->tx_queue_size, mpc->priv_flags, new_mtu,
mpc->bpf_prog, &newq);
if (err)
@@ -2958,7 +2958,11 @@ static void mana_deinit_txq(struct mana_port_context *apc, struct mana_txq *txq)
mana_gd_destroy_queue(gd->gdma_context, txq->gdma_sq);
}
-static void mana_destroy_txq(struct mana_port_context *apc)
+/* The array itself is left in place: the grow path tears down only a range,
+ * and the queues below @first are still live and still referenced by it.
+ */
+static void mana_destroy_txq_from(struct mana_port_context *apc,
+ unsigned int first)
{
struct napi_struct *napi;
int i;
@@ -2966,7 +2970,7 @@ static void mana_destroy_txq(struct mana_port_context *apc)
if (!apc->tx_qp)
return;
- for (i = 0; i < apc->num_queues; i++) {
+ for (i = first; i < apc->num_queues; i++) {
if (!apc->tx_qp[i])
continue;
@@ -2991,6 +2995,14 @@ static void mana_destroy_txq(struct mana_port_context *apc)
kvfree(apc->tx_qp[i]);
}
+}
+
+static void mana_destroy_txq(struct mana_port_context *apc)
+{
+ if (!apc->tx_qp)
+ return;
+
+ mana_destroy_txq_from(apc, 0);
kfree(apc->tx_qp);
apc->tx_qp = NULL;
@@ -3021,8 +3033,12 @@ static void mana_create_txq_debugfs(struct mana_port_context *apc, int idx)
tx_qp->tx_cq.gdma_cq, &mana_dbg_q_fops);
}
+/* @first is non-zero only for the grow path, which supplies an already
+ * allocated apc->tx_qp[] holding the carried-over queues. On error only the
+ * queues this call created are torn down.
+ */
static int mana_create_txq(struct mana_port_context *apc,
- struct net_device *net)
+ struct net_device *net, unsigned int first)
{
struct mana_context *ac = apc->ac;
struct gdma_dev *gd = ac->gdma_dev;
@@ -3037,9 +3053,14 @@ static int mana_create_txq(struct mana_port_context *apc,
int err;
int i;
- apc->tx_qp = kzalloc_objs(struct mana_tx_qp *, apc->num_queues);
- if (!apc->tx_qp)
- return -ENOMEM;
+ if (first) {
+ if (WARN_ON(!apc->tx_qp))
+ return -EINVAL;
+ } else {
+ apc->tx_qp = kzalloc_objs(struct mana_tx_qp *, apc->num_queues);
+ if (!apc->tx_qp)
+ return -ENOMEM;
+ }
/* The minimum size of the WQE is 32 bytes, hence
* apc->tx_queue_size represents the maximum number of WQEs
@@ -3056,7 +3077,7 @@ static int mana_create_txq(struct mana_port_context *apc,
gc = gd->gdma_context;
- for (i = 0; i < apc->num_queues; i++) {
+ for (i = first; i < apc->num_queues; i++) {
apc->tx_qp[i] = kvzalloc_obj(*apc->tx_qp[i]);
if (!apc->tx_qp[i]) {
err = -ENOMEM;
@@ -3164,7 +3185,10 @@ static int mana_create_txq(struct mana_port_context *apc,
out:
netdev_err(net, "Failed to create %d TX queues, %d\n",
apc->num_queues, err);
- mana_destroy_txq(apc);
+ if (first)
+ mana_destroy_txq_from(apc, first);
+ else
+ mana_destroy_txq(apc);
return err;
}
@@ -3526,14 +3550,18 @@ static void mana_create_rxq_debugfs(struct mana_port_context *apc, int idx)
&mana_dbg_q_fops);
}
+/* @first is non-zero only for the grow path; the slots below it already hold
+ * carried-over queues. Queues created before a failure are left in
+ * apc->rxqs[] for the caller to tear down.
+ */
static int mana_add_rx_queues(struct mana_port_context *apc,
- struct net_device *ndev)
+ struct net_device *ndev, unsigned int first)
{
struct mana_rxq *rxq;
int err = 0;
int i;
- for (i = 0; i < apc->num_queues; i++) {
+ for (i = first; i < apc->num_queues; i++) {
rxq = mana_create_rxq(apc, i, &apc->eqs[i], ndev);
if (IS_ERR(rxq)) {
err = PTR_ERR(rxq);
@@ -3551,14 +3579,16 @@ static int mana_add_rx_queues(struct mana_port_context *apc,
return err;
}
-static void mana_destroy_rxqs(struct mana_port_context *apc)
+/* The array is left in place; see mana_destroy_txq_from(). */
+static void mana_destroy_rxqs_from(struct mana_port_context *apc,
+ unsigned int first)
{
struct mana_rxq *rxq;
u32 rxq_idx;
if (apc->rxqs) {
- for (rxq_idx = 0; rxq_idx < apc->num_queues; rxq_idx++) {
+ for (rxq_idx = first; rxq_idx < apc->num_queues; rxq_idx++) {
rxq = apc->rxqs[rxq_idx];
if (!rxq)
continue;
@@ -3569,6 +3599,11 @@ static void mana_destroy_rxqs(struct mana_port_context *apc)
}
}
+static void mana_destroy_rxqs(struct mana_port_context *apc)
+{
+ mana_destroy_rxqs_from(apc, 0);
+}
+
static void mana_destroy_vport(struct mana_port_context *apc)
{
struct gdma_dev *gd = apc->ac->gdma_dev;
@@ -3953,7 +3988,7 @@ int mana_alloc_queues(struct net_device *ndev)
goto destroy_vport;
}
- err = mana_create_txq(apc, ndev);
+ err = mana_create_txq(apc, ndev, 0);
if (err) {
netdev_err(ndev, "Failed to create TXQ on vPort %u: %d\n",
apc->port_idx, err);
@@ -3968,7 +4003,7 @@ int mana_alloc_queues(struct net_device *ndev)
goto destroy_txq;
}
- err = mana_add_rx_queues(apc, ndev);
+ err = mana_add_rx_queues(apc, ndev, 0);
if (err)
goto destroy_rxq;
@@ -4454,13 +4489,161 @@ void mana_discard_split(struct mana_qset *newq, struct mana_qset *tailq)
memset(tailq, 0, sizeof(*tailq));
}
+/* The mirror image of mana_split_qset(): carry the existing queues over into
+ * @out_new and build only the [old, @new_count) tail. Growing 4 channels to 8
+ * creates 4 SQ/RQ pairs, not 8, and never holds 12 against the vport maximum.
+ *
+ * @out_fresh names just the queues created here, so a failed publish retires
+ * exactly those. On failure @apc is untouched.
+ */
+int mana_grow_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, unsigned int new_count,
+ struct mana_qset *out_new, struct mana_qset *out_fresh)
+{
+ unsigned int old_count = apc->num_queues;
+ struct mana_tx_qp **new_tx, **fresh_tx;
+ struct mana_rxq **new_rx, **fresh_rx;
+ struct net_device *ndev = apc->ndev;
+ unsigned int fresh_count;
+ bool indir_lost;
+ unsigned int i;
+ int err;
+
+ ASSERT_RTNL();
+
+ if (WARN_ON(new_count <= old_count))
+ return -EINVAL;
+ if (WARN_ON(!apc->tx_qp || !apc->rxqs))
+ return -EINVAL;
+
+ fresh_count = new_count - old_count;
+
+ new_tx = kzalloc_objs(struct mana_tx_qp *, new_count);
+ new_rx = kzalloc_objs(struct mana_rxq *, new_count);
+ fresh_tx = kzalloc_objs(struct mana_tx_qp *, fresh_count);
+ fresh_rx = kzalloc_objs(struct mana_rxq *, fresh_count);
+ if (!new_tx || !new_rx || !fresh_tx || !fresh_rx) {
+ err = -ENOMEM;
+ goto free_arrays;
+ }
+
+ for (i = 0; i < old_count; i++) {
+ new_tx[i] = apc->tx_qp[i];
+ new_rx[i] = apc->rxqs[i];
+ }
+
+ /* @scratch now describes the merged set; the builders fill only the
+ * [old_count, new_count) slots.
+ */
+ scratch->num_queues = new_count;
+ scratch->tx_qp = new_tx;
+ scratch->rxqs = new_rx;
+
+ err = mana_rss_table_alloc(scratch);
+ if (err)
+ goto free_arrays;
+
+ /* Same shared, port-owned EQ pool as a full rebuild; this only adds
+ * the vectors the extra queues need.
+ */
+ err = mana_grow_eqs(apc, new_count);
+ if (err)
+ goto cleanup_rss;
+
+ scratch->eqs = apc->eqs;
+ scratch->num_eqs = apc->num_eqs;
+
+ err = mana_create_txq(scratch, ndev, old_count);
+ if (err)
+ goto cleanup_rss; /* create_txq already undid its own work */
+
+ err = mana_add_rx_queues(scratch, ndev, old_count);
+ if (err)
+ goto cleanup_rxq;
+
+ if (mana_rss_table_keep(apc, new_count, &indir_lost))
+ memcpy(scratch->indir_table, apc->indir_table,
+ apc->indir_table_sz * sizeof(*apc->indir_table));
+ else
+ mana_rss_table_init(scratch);
+
+ mana_qset_snapshot(scratch, out_new);
+ out_new->rxfh_indir_lost = indir_lost;
+
+ for (i = 0; i < fresh_count; i++) {
+ fresh_tx[i] = new_tx[old_count + i];
+ fresh_rx[i] = new_rx[old_count + i];
+ }
+
+ memset(out_fresh, 0, sizeof(*out_fresh));
+ out_fresh->tx_qp = fresh_tx;
+ out_fresh->rxqs = fresh_rx;
+ out_fresh->default_rxobj = INVALID_MANA_HANDLE;
+ out_fresh->num_queues = fresh_count;
+ out_fresh->rx_queue_size = apc->rx_queue_size;
+ out_fresh->tx_queue_size = apc->tx_queue_size;
+ out_fresh->priv_flags = apc->priv_flags;
+ out_fresh->mtu = apc->configured_mtu;
+ out_fresh->bpf_prog = apc->bpf_prog;
+
+ /* mana_publish_qset() cannot do this: mana_chn_setxdp() decides from
+ * rxqs[0], a carried-over queue that already holds the program, and
+ * returns early. Address only the new queues through @out_fresh so
+ * exactly fresh_count references are taken.
+ */
+ mana_qset_install(scratch, out_fresh);
+ mana_chn_setxdp(scratch, mana_xdp_get(apc));
+
+ return 0;
+
+cleanup_rxq:
+ mana_destroy_rxqs_from(scratch, old_count);
+ mana_destroy_txq_from(scratch, old_count);
+cleanup_rss:
+ mana_cleanup_indir_table(scratch);
+free_arrays:
+ /* Only the containers: every queue they name is still live on @apc. */
+ scratch->tx_qp = NULL;
+ scratch->rxqs = NULL;
+ kfree(new_tx);
+ kfree(new_rx);
+ kfree(fresh_tx);
+ kfree(fresh_rx);
+
+ /* Give back any EQ this attempt added rather than holding its MSI-X
+ * vectors: the live set still needs only apc->num_queues of them, and
+ * every CQ this call created has been destroyed above.
+ */
+ mana_shrink_eqs(apc, apc->num_queues);
+
+ netdev_err(ndev, "%s(num_queues=%u) failed: %d\n", __func__,
+ new_count, err);
+ return err;
+}
+
+/**
+ * mana_discard_grow - drop the merged containers built by mana_grow_qset()
+ * @newq: set that was never published
+ *
+ * Frees the pointer arrays and steering table only: carried-over queues
+ * belong to the live context, fresh ones are retired through @out_fresh.
+ */
+void mana_discard_grow(struct mana_qset *newq)
+{
+ kfree(newq->tx_qp);
+ kfree(newq->rxqs);
+ kfree(newq->indir_table);
+ kfree(newq->rxobj_table);
+ memset(newq, 0, sizeof(*newq));
+}
+
/* Rebuild the queues at the current count in @scratch, for callers changing a
- * per-queue property; a reduction goes through mana_split_qset() instead. The
- * installed set keeps serving traffic meanwhile. On error nothing is left
- * allocated.
+ * per-queue property; a count change goes through mana_split_qset() or
+ * mana_grow_qset(), so this never has to add an EQ. The installed set keeps
+ * serving traffic meanwhile. On error nothing is left allocated.
*/
int mana_alloc_qset(struct mana_port_context *apc,
- struct mana_port_context *scratch, unsigned int num_queues,
+ struct mana_port_context *scratch,
unsigned int rx_queue_size, unsigned int tx_queue_size,
u32 priv_flags, int mtu, struct bpf_prog *bpf_prog,
struct mana_qset *out)
@@ -4471,7 +4654,7 @@ int mana_alloc_qset(struct mana_port_context *apc,
ASSERT_RTNL();
- scratch->num_queues = num_queues;
+ scratch->num_queues = apc->num_queues;
scratch->rx_queue_size = rx_queue_size;
scratch->tx_queue_size = tx_queue_size;
scratch->priv_flags = priv_flags;
@@ -4491,22 +4674,19 @@ int mana_alloc_qset(struct mana_port_context *apc,
if (err)
goto cleanup_rxq_array;
- /* Grow the port's shared EQ pool if this set needs more. The pool
- * belongs to @apc, not to either queue set, so both sets can be
- * live at once without double-booking MSI-X vectors.
+ /* The queue count is unchanged, so the port's shared EQ pool already
+ * has an EQ for every queue this set will build. Both sets reference
+ * the same pool while they are live, so a swap never needs old + new
+ * MSI-X vectors.
*/
- err = mana_grow_eqs(apc, num_queues);
- if (err)
- goto cleanup_rss;
-
scratch->eqs = apc->eqs;
scratch->num_eqs = apc->num_eqs;
- err = mana_create_txq(scratch, ndev);
+ err = mana_create_txq(scratch, ndev, 0);
if (err)
goto cleanup_rss;
- err = mana_add_rx_queues(scratch, ndev);
+ err = mana_add_rx_queues(scratch, ndev, 0);
if (err)
goto cleanup_rxq;
@@ -4515,7 +4695,7 @@ int mana_alloc_qset(struct mana_port_context *apc,
* them onto the new set's RX objects. A driver-generated table is
* rebuilt instead, so it covers every queue of the new set.
*/
- if (mana_rss_table_keep(apc, num_queues, &indir_lost))
+ if (mana_rss_table_keep(apc, scratch->num_queues, &indir_lost))
memcpy(scratch->indir_table, apc->indir_table,
apc->indir_table_sz * sizeof(*apc->indir_table));
else
@@ -4538,15 +4718,11 @@ int mana_alloc_qset(struct mana_port_context *apc,
kfree(scratch->rxqs);
scratch->rxqs = NULL;
out_err:
- /* Give back any EQ this attempt added to the shared pool rather than
- * holding its MSI-X vectors until some later teardown: the live set
- * still needs only apc->num_queues of them. Safe here because this
- * set's CQs have already been destroyed above.
+ /* No EQ to give back: this path never adds one, it reuses the pool
+ * the live set is already using.
*/
- mana_shrink_eqs(apc, apc->num_queues);
-
netdev_err(ndev, "%s(num_queues=%u) failed: %d\n", __func__,
- num_queues, err);
+ apc->num_queues, err);
return err;
}
@@ -4861,7 +5037,7 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
* Idempotent: a carried-over queue keeps its node; suppressed creation leaves
* an error pointer, not NULL, so both read as "no node". Under RTNL.
*/
-static void mana_qset_debugfs_publish(struct mana_port_context *apc)
+void mana_qset_debugfs_publish(struct mana_port_context *apc)
{
unsigned int i;
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index b5587764333f1f357df6b86795c92ef2985f9ed3..4e5da22f52dcd6b7f245ca896aacadd7dc899f92 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -681,11 +681,11 @@ static int mana_set_coalesce(struct net_device *ndev,
return 0;
}
-/* A reduction leaves every surviving queue configured as it was, so it retires
- * the tail rather than rebuilding; an increase still builds a new set. On
- * failure the existing queues keep running and the requested value is never
- * replaced by a fallback. The vport is never torn down, so RDMA cannot take it
- * mid-reconfiguration.
+/* A count change leaves every surviving queue configured as it was, so
+ * neither direction rebuilds: a reduction retires the tail, an increase
+ * builds only the queues added. On failure the existing queues keep running
+ * and the requested value is never replaced by a fallback. The vport is never
+ * torn down, so RDMA cannot take it mid-reconfiguration.
*/
static int mana_set_channels(struct net_device *ndev,
struct ethtool_channels *channels)
@@ -693,7 +693,7 @@ static int mana_set_channels(struct net_device *ndev,
struct mana_port_context *apc = netdev_priv(ndev);
unsigned int new_count = channels->combined_count;
struct mana_port_context *scratch;
- struct mana_qset newq, oldq;
+ struct mana_qset newq, oldq, freshq;
int err;
if (new_count < 1 || new_count > apc->max_queues) {
@@ -788,19 +788,41 @@ static int mana_set_channels(struct net_device *ndev,
goto free_scratch;
}
- err = mana_alloc_qset(apc, scratch, new_count, apc->rx_queue_size,
- apc->tx_queue_size, apc->priv_flags,
- apc->configured_mtu, apc->bpf_prog, &newq);
+ /* An increase does not change the queues that already exist either, so
+ * carry them over as well and build only the queues being added. The
+ * peak stays at the new count instead of old + new.
+ */
+ err = mana_grow_qset(apc, scratch, new_count, &newq, &freshq);
if (err)
goto free_scratch; /* current qset untouched, nothing to undo */
err = mana_publish_qset(apc, &newq, &oldq);
if (err) {
- mana_free_qset(apc, scratch, &newq);
+ /* The old set is live again. Retire the queues that were just
+ * built - @freshq names exactly those - and then drop the
+ * merged containers without touching the carried-over queues.
+ */
+ mana_free_qset(apc, scratch, &freshq);
+ mana_discard_grow(&newq);
goto free_scratch;
}
- mana_free_qset(apc, scratch, &oldq);
+ /* Nothing is retired by a grow: every queue @oldq referenced is now
+ * part of the published set, and so is every queue in @freshq. Only
+ * the containers of both are released here.
+ */
+ kfree(oldq.tx_qp);
+ kfree(oldq.rxqs);
+ kfree(oldq.indir_table);
+ kfree(oldq.rxobj_table);
+ kfree(freshq.tx_qp);
+ kfree(freshq.rxqs);
+
+ /* A grow retires nothing, so mana_free_qset() never runs to hand out
+ * the debugfs names. The queues that were just added are the only
+ * ones missing a node, and no retiring set is holding their names.
+ */
+ mana_qset_debugfs_publish(apc);
free_scratch:
mana_publish_close_if_needed(apc);
@@ -880,7 +902,7 @@ static int mana_set_ringparam(struct net_device *ndev,
goto clear_flag;
}
- err = mana_alloc_qset(apc, scratch, apc->num_queues, new_rx, new_tx,
+ err = mana_alloc_qset(apc, scratch, new_rx, new_tx,
apc->priv_flags, apc->configured_mtu,
apc->bpf_prog, &newq);
if (err) {
@@ -900,9 +922,6 @@ static int mana_set_ringparam(struct net_device *ndev,
mana_free_qset(apc, scratch, &oldq);
free_scratch:
- /* After the caller-side cleanup above, so the EQ pool outlives the
- * CQs that reference it.
- */
mana_publish_close_if_needed(apc);
mana_qset_scratch_free(scratch);
clear_flag:
@@ -979,7 +998,7 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
goto clear_flag;
}
- err = mana_alloc_qset(apc, scratch, apc->num_queues, apc->rx_queue_size,
+ err = mana_alloc_qset(apc, scratch, apc->rx_queue_size,
apc->tx_queue_size, priv_flags,
apc->configured_mtu, apc->bpf_prog, &newq);
if (err)
diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h
index 308950f9b54b0485bac66b80d63e257eaf5f787e..bab3f4b2efee0bb8e062093b9922b5b4066a9be5 100644
--- a/include/net/mana/gdma.h
+++ b/include/net/mana/gdma.h
@@ -686,6 +686,14 @@ enum {
/* Driver supports non-contiguous queue buffers */
#define GDMA_DRV_CAP_FLAG_1_NON_CONTIGUOUS_BUFFERS BIT(30)
+/* Driver recovers by itself when a queue resize fails: a failed resize leaves
+ * the queues that were already serving traffic in place, so the host does not
+ * have to bring the port back. This covers the resize itself failing. It does
+ * not promise recovery when restoring the previous queue set fails too, which
+ * leaves the port administratively down for the admin to bring back up.
+ */
+#define GDMA_DRV_CAP_FLAG_1_SELF_RECOVERY_ON_QUEUE_RESIZE_FAILURE BIT(31)
+
#define GDMA_DRV_CAP_FLAGS1 \
(GDMA_DRV_CAP_FLAG_1_EQ_SHARING_MULTI_VPORT | \
GDMA_DRV_CAP_FLAG_1_NAPI_WKDONE_FIX | \
@@ -703,7 +711,8 @@ enum {
GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECOVERY | \
GDMA_DRV_CAP_FLAG_1_EQ_MSI_UNSHARE_MULTI_VPORT | \
GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION | \
- GDMA_DRV_CAP_FLAG_1_NON_CONTIGUOUS_BUFFERS)
+ GDMA_DRV_CAP_FLAG_1_NON_CONTIGUOUS_BUFFERS | \
+ GDMA_DRV_CAP_FLAG_1_SELF_RECOVERY_ON_QUEUE_RESIZE_FAILURE)
#define GDMA_DRV_CAP_FLAGS2 0
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index 9fd555164df0de9ecd00fb840891025d566c0143..81708c157b69102740ea395458fbcade675bf72e 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -796,7 +796,7 @@ static inline struct mana_stats_rx *mana_rxq_stats(struct mana_rxq *rxq)
}
int mana_alloc_qset(struct mana_port_context *apc,
- struct mana_port_context *scratch, unsigned int num_queues,
+ struct mana_port_context *scratch,
unsigned int rx_queue_size, unsigned int tx_queue_size,
u32 priv_flags, int mtu, struct bpf_prog *bpf_prog,
struct mana_qset *out);
@@ -804,11 +804,16 @@ int mana_split_qset(struct mana_port_context *apc,
struct mana_port_context *scratch, unsigned int new_count,
struct mana_qset *out_new, struct mana_qset *out_tail);
void mana_discard_split(struct mana_qset *newq, struct mana_qset *tailq);
+int mana_grow_qset(struct mana_port_context *apc,
+ struct mana_port_context *scratch, unsigned int new_count,
+ struct mana_qset *out_new, struct mana_qset *out_fresh);
+void mana_discard_grow(struct mana_qset *newq);
int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
struct mana_qset *out_old);
void mana_publish_close_if_needed(struct mana_port_context *apc);
void mana_free_qset(struct mana_port_context *apc,
struct mana_port_context *scratch, struct mana_qset *qset);
+void mana_qset_debugfs_publish(struct mana_port_context *apc);
void mana_dim_change(struct mana_cq *cq, bool enable);
--
2.43.0
^ permalink raw reply related [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 04/13] net: mana: swap queue sets in mana_set_ringparam
2026-09-01 1:44 ` [PATCH net-next v3 04/13] net: mana: swap queue sets in mana_set_ringparam Long Li
@ 2026-09-02 1:45 ` sashiko-bot
2026-09-02 19:25 ` [EXTERNAL] " Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
1 sibling, 1 reply; 33+ messages in thread
From: sashiko-bot @ 2026-09-02 1:45 UTC (permalink / raw)
To: Long Li; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The patch introduces a boolean flag (`channel_changing`) into `mana_set_ringparam` to act as an ad-hoc lock against concurrent RDMA operations. The subsystem guidelines explicitly prohibit using a boolean flag set on entry and cleared on exit of a code region to prevent concurrent access, requiring a proper lock instead. Furthermore, this ad-hoc lock is entirely redundant and constitutes unnecessary code complexity: the RDMA path (`mana_cfg_vport`) already returns `-EBUSY` when `apc->vport_use_count > 0`. Because `mana_set_ringparam` returns early if the port is down, `vport_use_count` is guaranteed to be `> 0` when the flag is set (as the author notes in the code comment). This means the RDMA path will always short-circuit and fail before it even evaluates `apc->channel_changing`, making the flag updates dead code.
--
commit ea4e4054a6be8a40258c13cd5a0aa9f21fa11e29
Author: Long Li <longli@microsoft.com>
net: mana: swap queue sets in mana_set_ringparam
Convert the ring size path to pre-allocate and swap so that allocation failures
return an error without touching the running configuration. This drops the
fallback on failure and returns early when the requested sizes round to the
values already in use.
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
[ ... ]
> + /* 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);
[Severity: Low]
Does this introduce a boolean flag as an ad-hoc lock? Subsystem guidelines
prohibit using a boolean flag set on entry and cleared on exit of a code
region to prevent concurrent access, requiring a proper lock instead.
Additionally, could this flag be entirely redundant? Since the RDMA path
returns -EBUSY when apc->vport_use_count > 0, and the port is up here (meaning
vport_use_count > 0, as noted in the comment), the RDMA path will likely
short-circuit and fail before it even evaluates apc->channel_changing.
Does this make the flag updates dead code?
[ ... ]
> +clear_flag:
> + mutex_lock(&apc->vport_mutex);
> + apc->channel_changing = false;
> + mutex_unlock(&apc->vport_mutex);
> return err;
> }
[Severity: Low]
This clears the ad-hoc lock mentioned above. Can we remove this flag-based
synchronization entirely if the RDMA path is already blocked by
vport_use_count?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901014442.2945689-1-longli@microsoft.com?part=4
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 09/13] net: mana: keep per-queue statistics in the port context
2026-09-01 1:44 ` [PATCH net-next v3 09/13] net: mana: keep per-queue statistics in the port context Long Li
@ 2026-09-02 1:45 ` sashiko-bot
2026-09-02 19:28 ` [EXTERNAL] " Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
1 sibling, 1 reply; 33+ messages in thread
From: sashiko-bot @ 2026-09-02 1:45 UTC (permalink / raw)
To: Long Li; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] False-positive ASSERT_RTNL() warning during probe failure.
--
commit b6c8581e036e2f21afeed4eb0d2e9961d1a0d2f5
Author: Long Li <longli@microsoft.com>
Subject: net: mana: keep per-queue statistics in the port context
This patch moves rx and tx statistics from the individual queue structures
into port-context arrays. This ensures that the interface counters are not
lost across reconfigurations such as ethtool channel count or MTU changes.
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
[ ... ]
> +static void mana_fold_rxq_stats(struct mana_port_context *apc,
> + struct mana_rxq *rxq)
> +{
> + struct mana_stats_rx *src = &rxq->drain_stats;
> + struct mana_stats_rx *dst;
> + unsigned int i;
> +
> + ASSERT_RTNL();
[ ... ]
> @@ -3017,6 +3163,11 @@ static void mana_destroy_rxq(struct mana_port_context *apc,
> netif_napi_del_locked(napi);
> }
>
> + /* No poller left, so this is the last chance to keep what the queue
> + * counted after it stopped being the live one.
> + */
> + mana_fold_rxq_stats(apc, rxq);
> +
[Severity: Medium]
Will this trigger a false-positive ASSERT_RTNL() warning if the driver
fails to probe?
If mana_init_port() fails during device probe (e.g., due to a memory
allocation failure or hardware timeout), the error recovery path calls
mana_destroy_rxqs() and then mana_destroy_rxq(), which now unconditionally
calls mana_fold_rxq_stats().
Since the probe path in mana_probe_port() invokes mana_init_port()
before calling register_netdev(), the RTNL lock is not held yet. This
seems like it would trigger the new ASSERT_RTNL() inside
mana_fold_rxq_stats() during a valid error recovery path.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901014442.2945689-1-longli@microsoft.com?part=9
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 10/13] net: mana: release EQs left idle by a channel-count reduction
2026-09-01 1:44 ` [PATCH net-next v3 10/13] net: mana: release EQs left idle by a channel-count reduction Long Li
@ 2026-09-02 1:45 ` sashiko-bot
2026-09-02 19:30 ` [EXTERNAL] " Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
1 sibling, 1 reply; 33+ messages in thread
From: sashiko-bot @ 2026-09-02 1:45 UTC (permalink / raw)
To: Long Li; +Cc: linux-hyperv
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Use-After-Free of CQ in EQ event processing due to lack of synchronization during CQ destruction
--
commit 3d0d6fcf405f01d902d109c4441be576c316986f
Author: Long Li <longli@microsoft.com>
net: mana: release EQs left idle by a channel-count reduction
The shared EQ pool only grows, so it sits at the high-water mark of every
channel count the port has ever used. This commit releases the EQs above the
live queue count once a retiring set has been torn down. It also ensures the
dentry is properly recorded for debugfs.
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
> index 69e34fab8fe8e..2295769da0306 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
[ ... ]
> @@ -4745,12 +4785,19 @@ void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
>
> memset(qset, 0, sizeof(*qset));
>
> + /* This set is gone, so any EQ above the live queue count is now
> + * unreferenced. Release those vectors instead of holding them at the
> + * high-water mark. Safe here and only here: the retiring set's CQs
> + * have just been destroyed.
> + */
[Severity: High]
This is a pre-existing issue, but since this teardown path is being modified,
can the destruction of the retiring set's CQs race with the EQ interrupt
handler?
An admin reconfiguring the network interface (e.g., changing MTU, ring sizes,
or channels) will tear down the old queue set. The hardware queues are fenced,
generating a final CQE in the CQ.
If the EQ interrupt fires, CPU A could process the EQE in
mana_gd_process_eqe(). It reads the CQ pointer but might be delayed (e.g., by
an NMI) before invoking the callback:
drivers/net/ethernet/microsoft/mana/gdma_main.c:mana_gd_process_eqe() {
...
cq = gc->cq_table[cq_id];
if (WARN_ON_ONCE(!cq || cq->type != GDMA_CQ || cq->id != cq_id))
break;
if (cq->cq.callback)
cq->cq.callback(cq->cq.context, cq);
...
}
Concurrently, NAPI on CPU C could process the fence CQE and signal the
completion event. CPU B, waiting in mana_fence_rqs(), wakes up and tears down
the old CQ via mana_destroy_rxq() -> mana_gd_destroy_queue().
It clears gc->cq_table[cq_id] = NULL in mana_gd_destroy_cq() and frees the CQ
memory immediately in mana_gd_destroy_queue():
drivers/net/ethernet/microsoft/mana/gdma_main.c:mana_gd_destroy_queue() {
...
mana_gd_destroy_cq(gc, queue);
...
kfree(queue);
}
When CPU A resumes and executes cq->cq.callback(cq->cq.context, cq), this
dereferences the freed cq and mana_rxq structures to call napi_schedule(),
causing a UAF write to napi.state.
Is there any synchronization, such as RCU or IRQ synchronization, to prevent
this use-after-free during CQ destruction?
> + mana_shrink_eqs(apc, apc->num_queues);
> +
> /* Queues built through a scratch context carry no debugfs nodes,
> * because both sets are alive during the swap and would collide on
> * the same names. The retiring set's nodes are gone now, so the
> * published queues can finally take those names.
> */
> - mana_qset_debugfs_publish(netdev_priv(scratch->ndev));
> + mana_qset_debugfs_publish(apc);
> }
>
> /* --- end of pre-allocate + swap reconfiguration path ---------------------- */
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260901014442.2945689-1-longli@microsoft.com?part=10
^ permalink raw reply [flat|nested] 33+ messages in thread
* RE: [EXTERNAL] Re: [PATCH net-next v3 04/13] net: mana: swap queue sets in mana_set_ringparam
2026-09-02 1:45 ` sashiko-bot
@ 2026-09-02 19:25 ` Long Li
0 siblings, 0 replies; 33+ messages in thread
From: Long Li @ 2026-09-02 19:25 UTC (permalink / raw)
To: sashiko-reviews@lists.linux.dev; +Cc: linux-hyperv@vger.kernel.org
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s)
> to consider:
> - [Low] The patch introduces a boolean flag (`channel_changing`) into
> `mana_set_ringparam` to act as an ad-hoc lock against concurrent RDMA
> operations. The subsystem guidelines explicitly prohibit using a boolean flag
> set on entry and cleared on exit of a code region to prevent concurrent
> access, requiring a proper lock instead. Furthermore, this ad-hoc lock is
> entirely redundant and constitutes unnecessary code complexity: the RDMA
> path (`mana_cfg_vport`) already returns `-EBUSY` when `apc-
> >vport_use_count > 0`. Because `mana_set_ringparam` returns early if the
> port is down, `vport_use_count` is guaranteed to be `> 0` when the flag is set
> (as the author notes in the code comment). This means the RDMA path will
> always short-circuit and fail before it even evaluates `apc-
> >channel_changing`, making the flag updates dead code.
> --
>
> commit ea4e4054a6be8a40258c13cd5a0aa9f21fa11e29
> Author: Long Li <longli@microsoft.com>
>
> net: mana: swap queue sets in mana_set_ringparam
>
> Convert the ring size path to pre-allocate and swap so that allocation failures
> return an error without touching the running configuration. This drops the
> fallback on failure and returns early when the requested sizes round to the
> values already in use.
>
> > diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> > b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> > --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> > +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> [ ... ]
> > + /* 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);
>
> [Severity: Low]
> Does this introduce a boolean flag as an ad-hoc lock? Subsystem guidelines
> prohibit using a boolean flag set on entry and cleared on exit of a code
> region to prevent concurrent access, requiring a proper lock instead.
>
> Additionally, could this flag be entirely redundant? Since the RDMA path
> returns -EBUSY when apc->vport_use_count > 0, and the port is up here
> (meaning vport_use_count > 0, as noted in the comment), the RDMA path
> will likely short-circuit and fail before it even evaluates apc-
> >channel_changing.
> Does this make the flag updates dead code?
>
> [ ... ]
> > +clear_flag:
> > + mutex_lock(&apc->vport_mutex);
> > + apc->channel_changing = false;
> > + mutex_unlock(&apc->vport_mutex);
> > return err;
> > }
>
> [Severity: Low]
> This clears the ad-hoc lock mentioned above. Can we remove this flag-based
> synchronization entirely if the RDMA path is already blocked by
> vport_use_count?
The flag is not introduced by this patch. channel_changing is already in
mainline: declared in struct mana_port_context, checked inside
mana_cfg_vport() via its check_channel_changing argument, and
set/cleared by mana_set_channels(), mana_set_priv_flags() and the
per-port reset work handler. This patch converts mana_set_ringparam() to
the swap model and follows the convention the neighbouring ethtool
setters already use.
It is also not dead code. The reasoning holds for the success path:
while the swap succeeds the vport is never released, vport_use_count
stays at 1, and mana_cfg_vport() short-circuits on the first disjunct
exactly as you describe. The failure path releases it:
free_scratch:
mana_publish_close_if_needed(apc); /* -> mana_dealloc_queues()
-> mana_destroy_vport()
-> mana_uncfg_vport(): 1 -> 0 */
mana_qset_scratch_free(scratch);
clear_flag:
apc->channel_changing = false;
When mana_publish_qset() hits a dead end the port is closed, which drops
vport_use_count to 0, and the flag is only cleared after that. In that
window channel_changing is the only thing still blocking RDMA, which
calls mana_cfg_vport(..., true) from drivers/infiniband/hw/mana/main.c
without RTNL and can therefore land in it. The same applies to the reset
work handler, which this series does not convert and which still does a
full detach/attach.
Thanks,
Long
^ permalink raw reply [flat|nested] 33+ messages in thread
* RE: [EXTERNAL] Re: [PATCH net-next v3 09/13] net: mana: keep per-queue statistics in the port context
2026-09-02 1:45 ` sashiko-bot
@ 2026-09-02 19:28 ` Long Li
0 siblings, 0 replies; 33+ messages in thread
From: Long Li @ 2026-09-02 19:28 UTC (permalink / raw)
To: sashiko-reviews@lists.linux.dev; +Cc: linux-hyperv@vger.kernel.org
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s)
> to consider:
> - [Medium] False-positive ASSERT_RTNL() warning during probe failure.
> --
>
> commit b6c8581e036e2f21afeed4eb0d2e9961d1a0d2f5
> Author: Long Li <longli@microsoft.com>
> Subject: net: mana: keep per-queue statistics in the port context
>
> This patch moves rx and tx statistics from the individual queue structures into
> port-context arrays. This ensures that the interface counters are not lost
> across reconfigurations such as ethtool channel count or MTU changes.
>
> > diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c
> > b/drivers/net/ethernet/microsoft/mana/mana_en.c
> > --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> > +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
> [ ... ]
> > +static void mana_fold_rxq_stats(struct mana_port_context *apc,
> > + struct mana_rxq *rxq)
> > +{
> > + struct mana_stats_rx *src = &rxq->drain_stats;
> > + struct mana_stats_rx *dst;
> > + unsigned int i;
> > +
> > + ASSERT_RTNL();
> [ ... ]
> > @@ -3017,6 +3163,11 @@ static void mana_destroy_rxq(struct
> mana_port_context *apc,
> > netif_napi_del_locked(napi);
> > }
> >
> > + /* No poller left, so this is the last chance to keep what the queue
> > + * counted after it stopped being the live one.
> > + */
> > + mana_fold_rxq_stats(apc, rxq);
> > +
>
> [Severity: Medium]
> Will this trigger a false-positive ASSERT_RTNL() warning if the driver fails to
> probe?
>
> If mana_init_port() fails during device probe (e.g., due to a memory
> allocation failure or hardware timeout), the error recovery path calls
> mana_destroy_rxqs() and then mana_destroy_rxq(), which now
> unconditionally calls mana_fold_rxq_stats().
>
> Since the probe path in mana_probe_port() invokes mana_init_port() before
> calling register_netdev(), the RTNL lock is not held yet. This seems like it
> would trigger the new ASSERT_RTNL() inside
> mana_fold_rxq_stats() during a valid error recovery path.
>
The false-positive warning will not cause any functional degradation and only show up when probe failed.
IMHO this is benign, I can move it to a cleanup patch if this is needed.
^ permalink raw reply [flat|nested] 33+ messages in thread
* RE: [EXTERNAL] Re: [PATCH net-next v3 10/13] net: mana: release EQs left idle by a channel-count reduction
2026-09-02 1:45 ` sashiko-bot
@ 2026-09-02 19:30 ` Long Li
0 siblings, 0 replies; 33+ messages in thread
From: Long Li @ 2026-09-02 19:30 UTC (permalink / raw)
To: sashiko-reviews@lists.linux.dev; +Cc: linux-hyperv@vger.kernel.org
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s)
> to consider:
>
> Pre-existing issues:
> - [High] Use-After-Free of CQ in EQ event processing due to lack of
> synchronization during CQ destruction
> --
>
> commit 3d0d6fcf405f01d902d109c4441be576c316986f
> Author: Long Li <longli@microsoft.com>
> net: mana: release EQs left idle by a channel-count reduction
>
> The shared EQ pool only grows, so it sits at the high-water mark of every
> channel count the port has ever used. This commit releases the EQs above
> the live queue count once a retiring set has been torn down. It also ensures
> the dentry is properly recorded for debugfs.
>
> > diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c
> > b/drivers/net/ethernet/microsoft/mana/mana_en.c
> > index 69e34fab8fe8e..2295769da0306 100644
> > --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> > +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
> [ ... ]
> > @@ -4745,12 +4785,19 @@ void mana_free_qset(struct
> mana_port_context
> > *scratch, struct mana_qset *qset)
> >
> > memset(qset, 0, sizeof(*qset));
> >
> > + /* This set is gone, so any EQ above the live queue count is now
> > + * unreferenced. Release those vectors instead of holding them at
> the
> > + * high-water mark. Safe here and only here: the retiring set's CQs
> > + * have just been destroyed.
> > + */
>
> [Severity: High]
> This is a pre-existing issue, but since this teardown path is being modified,
> can the destruction of the retiring set's CQs race with the EQ interrupt
> handler?
>
> An admin reconfiguring the network interface (e.g., changing MTU, ring sizes,
> or channels) will tear down the old queue set. The hardware queues are
> fenced, generating a final CQE in the CQ.
>
> If the EQ interrupt fires, CPU A could process the EQE in
> mana_gd_process_eqe(). It reads the CQ pointer but might be delayed (e.g.,
> by an NMI) before invoking the callback:
>
> drivers/net/ethernet/microsoft/mana/gdma_main.c:mana_gd_process_eqe()
> {
> ...
> cq = gc->cq_table[cq_id];
> if (WARN_ON_ONCE(!cq || cq->type != GDMA_CQ || cq->id != cq_id))
> break;
>
> if (cq->cq.callback)
> cq->cq.callback(cq->cq.context, cq);
> ...
> }
>
> Concurrently, NAPI on CPU C could process the fence CQE and signal the
> completion event. CPU B, waiting in mana_fence_rqs(), wakes up and tears
> down the old CQ via mana_destroy_rxq() -> mana_gd_destroy_queue().
>
> It clears gc->cq_table[cq_id] = NULL in mana_gd_destroy_cq() and frees the
> CQ memory immediately in mana_gd_destroy_queue():
>
> drivers/net/ethernet/microsoft/mana/gdma_main.c:mana_gd_destroy_queu
> e() {
> ...
> mana_gd_destroy_cq(gc, queue);
> ...
> kfree(queue);
> }
>
> When CPU A resumes and executes cq->cq.callback(cq->cq.context, cq), this
> dereferences the freed cq and mana_rxq structures to call napi_schedule(),
> causing a UAF write to napi.state.
>
> Is there any synchronization, such as RCU or IRQ synchronization, to prevent
> this use-after-free during CQ destruction?
>
> > + mana_shrink_eqs(apc, apc->num_queues);
> > +
> > /* Queues built through a scratch context carry no debugfs nodes,
> > * because both sets are alive during the swap and would collide on
> > * the same names. The retiring set's nodes are gone now, so the
> > * published queues can finally take those names.
> > */
> > - mana_qset_debugfs_publish(netdev_priv(scratch->ndev));
> > + mana_qset_debugfs_publish(apc);
> > }
> >
> > /* --- end of pre-allocate + swap reconfiguration path
> > ---------------------- */
>
> --
This is an existing issue with the original code; this patch set doesn't touch it and will leave it as is.
If needed, I will submit another net patch for fixing the existing issue.
Long
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 01/13] net: mana: add queue-set allocation and teardown helpers
2026-09-01 1:44 ` [PATCH net-next v3 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
@ 2026-09-04 4:46 ` netdev-bot+sashiko
0 siblings, 0 replies; 33+ messages in thread
From: netdev-bot+sashiko @ 2026-09-04 4:46 UTC (permalink / raw)
To: longli
Cc: kotaranov, kuba, davem, pabeni, edumazet, andrew+netdev, jgg,
leon, haiyangz, kys, wei.liu, decui, shradhagupta, horms, ernis,
stephen, shirazsaleem, netdev, linux-rdma, linux-hyperv,
linux-kernel
Thank you for your contribution! Sashiko AI review found 8 potential
issue(s) to consider:
Critical: 0 · High: 2 · Medium: 3 · Low: 3
- [High] mana_drain_txqs() refuses to unmap/free pending TX SKBs when the
fallback pci_try_reset_function() fails ("leaking pending TX buffers…
- [High] The drain's fallback reset was changed from an unconditional
pcie_flr() to pci_try_reset_function(), which starts with…
- [Medium] The commit message claims "No functional change otherwise:
nothing calls the new helpers yet", but the TX-drain rewrite changes…
- [Medium] After a successful function reset invalidates every port's
queues, the sibling recovery this patch newly relies on can exit…
- [Medium] The drain fallback issues a PCI function-level reset and, on
success, treats the function as usable: it bumps reset_gen, continues…
- [Low] include/net/mana/mana.h documents mana_context.reset_gen as
"Bumped on every PCI function reset", and mana_drain_txqs() uses a…
- [Low] The commit message presents "That tears the vport down, so RDMA
can claim it while released" as applying to all of the named…
- [Low] The live-path change from a bare pcie_flr() to a PCI-core-managed
function reset is a user-visible correctness fix (the bare FLR wipes…
--
net: mana: add queue-set allocation and teardown helpers
This adds a mana_qset data model plus mana_qset_scratch_alloc(),
mana_alloc_qset() and mana_free_qset() so a queue set can be built and
retired against a scratch port context rather than by tearing the vport
down. It also moves the TX drain out of mana_dealloc_queues() into a new
mana_drain_txqs() and replaces the fallback pcie_flr() with
pci_try_reset_function().
No Link: tags are present in the commit message.
A few questions on the commit message first, then some on the diff.
> The ethtool reconfiguration paths (channel count, ring size, private
> flags), mana_change_mtu() and mana_xdp_set() rebuild the queues with
> mana_detach() then mana_attach(). That tears the vport down, so RDMA can
> claim it while released, and a failed mana_attach() leaves the port down
> with no way back but manual intervention.
[Severity: Low]
This isn't a bug, but does the RDMA part of that sentence hold for all of
the listed paths?
mana_set_channels() and mana_set_priv_flags() in mana_ethtool.c already
set apc->channel_changing = true across their detach/attach window:
/* Block RDMA from grabbing the vport during detach/attach */
apc->channel_changing = true;
and mana_cfg_vport() returns -EBUSY while that flag is set. The RDMA
exposure argument seems to apply to mana_set_ringparam(),
mana_change_mtu() and mana_xdp_set() only, while the failed-attach
argument applies to all of them. Could the wording be narrowed?
> The TX drain moves out of mana_dealloc_queues() so the new teardown path
> gets it too, and its fallback reset becomes pci_try_reset_function()
> rather than an open-coded pcie_flr(), which does not save and restore
> config space. Trylock because this runs under RTNL while removal takes
> the device lock first.
[Severity: Low]
This part reads as a correctness fix to the pre-existing fallback reset in
mana_dealloc_queues(), but the patch carries no Fixes: tag and declares no
functional change. Would it be better as a separate commit so it can be
considered for stable on its own?
> No functional change otherwise: nothing calls the new helpers yet.
[Severity: Medium]
Is the "no functional change otherwise" claim accurate for the live
teardown path (mana_close()/mana_detach()/mana_remove())? Three behaviour
changes seem to ride along:
- On a failed fallback reset, mana_drain_txqs() returns before the
skb_dequeue() loop, so pending TX SKBs and their DMA mappings are now
deliberately leaked. The baseline code ran that loop unconditionally
after the pcie_flr() attempt.
- On a successful reset, mana_dealloc_queues() now walks ac->ports[] and
queues sib->queue_reset_work for every other port, so unrelated netdevs
get their queues torn down and rebuilt.
- mana_remove() now clears ac->ports[i], which is only justified by a code
comment.
Could these be called out in the changelog?
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
> index 7a1ac853e3abc..b9d9543d1a6dc 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
[ ... ]
> @@ -3679,15 +3684,115 @@ int mana_attach(struct net_device *ndev)
> return 0;
> }
>
> -static int mana_dealloc_queues(struct net_device *ndev)
> +/* Drain a set about to be destroyed: nothing new can reach it, so wait for the
> + * hardware to finish what it owns, then release every mapped SKB.
> + *
> + * The 120s budget is shared across all queues. On timeout the device is reset,
> + * since its buffers are about to be freed while it may still DMA into them; if
> + * that fails too they are leaked.
> + *
> + * Returns true only if a reset happened, taking every queue on the function
> + * down with it.
> + */
> +static bool mana_drain_txqs(struct mana_port_context *apc)
> {
> - struct mana_port_context *apc = netdev_priv(ndev);
> unsigned long timeout = jiffies + 120 * HZ;
> - struct gdma_dev *gd = apc->ac->gdma_dev;
> + struct gdma_context *gc = apc->ac->gdma_dev->gdma_context;
> + bool quiesced = true;
> + bool reset = false;
> struct mana_txq *txq;
> struct sk_buff *skb;
> - int i, err;
> u32 tsleep;
> + int i, err;
> +
> + if (!apc->tx_qp)
> + return false;
> +
> + for (i = 0; i < apc->num_queues; i++) {
> + if (!apc->tx_qp[i])
> + continue;
> +
> + txq = &apc->tx_qp[i]->txq;
> +
> + /* The function was reset after this queue was created, so the
> + * device has stopped touching its buffers and the completions
> + * waited for below can never arrive. Without this the port
> + * would burn the full timeout under RTNL, then reset the
> + * function again on the way out.
> + */
> + if (READ_ONCE(apc->ac->reset_gen) != txq->reset_gen)
> + continue;
> +
> + tsleep = 1000;
> + while (atomic_read(&txq->pending_sends) > 0 &&
> + time_before(jiffies, timeout)) {
> + usleep_range(tsleep, tsleep + 1000);
> + tsleep <<= 1;
> + }
> + if (atomic_read(&txq->pending_sends)) {
> + /* The device still owns these buffers, so reset it
> + * before they are freed. pci_try_reset_function()
> + * rather than pcie_flr(): it saves and restores config
> + * space, which a bare FLR wipes behind the PCI core's
> + * back. Trylock because RTNL is held here while the
> + * remove path takes the device lock first.
> + */
> + err = pci_try_reset_function(to_pci_dev(gc->dev));
[Severity: High]
Can this reset ever happen on the remove path? mana_gd_remove() is the PCI
.remove callback, and the driver core invokes it with device_lock held:
mana_gd_remove() -> mana_remove() -> mana_detach() -> mana_dealloc_queues()
-> mana_drain_txqs()
and pci_try_reset_function() begins with:
if (!pci_dev_trylock(dev))
return -EAGAIN;
on a mutex the calling thread already owns, so it looks like it can only
return -EAGAIN there. The old pcie_flr() had no lock dependency and did
stop the device, so on unbind with un-drained TX the device now appears to
never be reset while its queues are still destroyed below. Would
pci_reset_function_locked() (or __pci_reset_function_locked()) be the right
call for a caller that is already under device_lock?
> + if (err) {
> + netdev_err(apc->ndev,
> + "function reset failed: %d, %d pkts pending in txq %u\n",
> + err,
> + atomic_read(&txq->pending_sends),
> + txq->gdma_txq_id);
> + quiesced = false;
> + } else {
> + /* Every queue on the function is dead now,
> + * including the ones this loop has not reached
> + * and those of the other ports.
> + */
> + WRITE_ONCE(apc->ac->reset_gen,
> + apc->ac->reset_gen + 1);
> +
> + /* Only a reset that actually happened takes the
> + * other ports down with it; reporting a failed
> + * one would rebuild them for nothing.
> + */
> + reset = true;
> + }
> + break;
> + }
> + }
> +
> + /* Only a reset that actually happened makes freeing these safe; without
> + * one the device still owns them. Leak instead, bounded at one SQ ring
> + * of skbs per queue.
> + */
> + if (!quiesced) {
> + netdev_err(apc->ndev,
> + "device not quiesced, leaking pending TX buffers instead of unmapping memory it can still DMA from\n");
> + return reset;
> + }
[Severity: High]
Does this leak-instead-of-unmap decision reach the callers?
The return value is reset, which is false both for a healthy drain and for
a failed reset, so neither mana_dealloc_queues() nor mana_free_qset() can
tell the two apart. Both then run unconditionally:
mana_fence_rqs(apc);
mana_destroy_rxqs(apc);
mana_destroy_txq(apc);
mana_destroy_eq(apc);
and mana_gd_destroy_queue() in gdma_main.c releases the SQ/CQ/EQ ring
memory regardless of whether the firmware-side destroy succeeded:
mana_gd_destroy_dma_region(gc, gmi->dma_region_handle);
mana_gd_free_memory(gmi);
kfree(queue);
while mana_destroy_rxq() unmaps and frees the RX pages. So the pending SKB
mappings are kept, but the descriptor rings and RX buffers of the same
un-quiesced device go back to the page allocator. Can the device DMA into
that memory after it is reused?
> +
> + 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;
> +}
[ ... ]
> @@ -3705,41 +3810,27 @@ static int mana_dealloc_queues(struct net_device *ndev)
> * new packets due to apc->port_is_up being false.
> *
> * Drain all the in-flight TX packets.
> - * A timeout of 120 seconds for all the queues is used.
> - * This will break the while loop when h/w is not responding.
> - * This value of 120 has been decided here considering max
> - * number of queues.
> + *
> + * If the drain had to reset the function to get there, every other
> + * port on the adapter lost its queues too, so schedule them for a
> + * rebuild. This port is being torn down here and needs no such
> + * treatment, and a down port stays down: with port_st_save false,
> + * detach and attach both skip the queue work.
> */
> + if (mana_drain_txqs(apc)) {
> + struct mana_context *ac = apc->ac;
> + unsigned int i;
[ ... ]
> + 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);
> }
> }
[Severity: Medium]
Can this rebuild silently not happen? mana_per_port_queue_reset_work_handler()
gives up before detach/attach when the RX pre-allocation fails:
err = mana_pre_alloc_rxbufs(apc, ndev->mtu, apc->num_queues);
if (err) {
netdev_err(ndev, "Insufficient memory for reset post tx stall detection\n");
goto clear_flag;
}
There is no retry and no state change on that branch, so under memory
pressure the sibling netdev stays with port_is_up true and
apc->tx_qp[]/apc->rxqs[] pointing at queues the function reset destroyed,
until someone does a manual ifdown/ifup. Should this branch requeue the
work or mark the port down?
> @@ -3763,6 +3854,278 @@ static int mana_dealloc_queues(struct net_device *ndev)
> return 0;
> }
>
[ ... ]
> +void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
> +{
[ ... ]
> + if (mana_drain_txqs(scratch)) {
> + /* The drain had to reset the function to stop the device
> + * touching those buffers. A function reset takes down every
> + * port on the adapter, not just this one, so rebuild them all
> + * - the same recovery mana_tx_timeout() relies on. A port that
> + * is already down has nothing to rebuild and its handler
> + * leaves it down.
> + */
[Severity: Medium]
After the function reset, what re-establishes the GDMA/HWC state that the
scheduled rebuilds need?
The driver registers no pci_error_handlers, so there is no reset_prepare or
reset_done callback, and pci_try_reset_function() only saves and restores
config space. The only path that re-bootstraps the device is:
mana_serv_reset() -> mana_gd_suspend()/mana_gd_resume()
-> mana_gd_cleanup_device()/mana_gd_setup()
and that is not invoked here. Each queued queue_reset_work then re-enters
mana_attach() and issues HWC requests over a channel the reset destroyed,
while gc->cq_table[], the EQ/MSI-X bookkeeping and the cached vport and
queue handles still describe objects that no longer exist. The mana_ib
auxiliary device on the same function is also not told.
Is the comment's "the same recovery mana_tx_timeout() relies on" accurate?
That path never resets the function, so its HWC is still alive when the
rebuild runs.
> + struct mana_port_context *apc = netdev_priv(scratch->ndev);
> + struct mana_context *ac = apc->ac;
> + struct mana_port_context *sib;
> + 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;
> + sib = netdev_priv(ac->ports[i]);
> + queue_work(ac->per_port_queue_reset_wq,
> + &sib->queue_reset_work);
> + }
> + }
[ ... ]
> diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
> index 83b7eff4646ea..356aaa652fa6f 100644
> --- a/include/net/mana/mana.h
> +++ b/include/net/mana/mana.h
[ ... ]
> @@ -537,6 +547,14 @@ struct mana_context {
> u8 bm_hostmode;
>
> struct mana_ethtool_hc_stats hc_stats;
> +
> + /* Bumped on every PCI function reset. A queue created before the
> + * current value can no longer be reached by the device, so its buffers
> + * need no drain. All access is under RTNL: the bump is a non-atomic
> + * read-modify-write, so a second writer would lose an increment.
> + */
> + u32 reset_gen;
[Severity: Low]
Is "bumped on every PCI function reset" true? The only writer is
mana_drain_txqs(), right after its own successful pci_try_reset_function().
Resets initiated elsewhere leave it unchanged, for example:
echo 1 > /sys/bus/pci/devices/<bdf>/reset
as well as secondary-bus/slot resets, EEH/AER recovery, and the driver's
own mana_serv_reset() -> mana_gd_suspend()/mana_gd_resume() cycle. There
is no reset_done callback anywhere under drivers/net/ethernet/microsoft/mana/
that would observe them, so mana_drain_txqs() still burns the full 120s
under RTNL and then resets the function again.
The effect is conservative rather than unsafe, so would either adding a
reset_done handler or narrowing the comment to resets issued by
mana_drain_txqs() be preferable?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901014442.2945689-1-longli%40microsoft.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 02/13] net: mana: share the EQ pool across a queue-set swap
2026-09-01 1:44 ` [PATCH net-next v3 02/13] net: mana: share the EQ pool across a queue-set swap Long Li
@ 2026-09-04 4:46 ` netdev-bot+sashiko
0 siblings, 0 replies; 33+ messages in thread
From: netdev-bot+sashiko @ 2026-09-04 4:46 UTC (permalink / raw)
To: longli
Cc: kotaranov, kuba, davem, pabeni, edumazet, andrew+netdev, jgg,
leon, haiyangz, kys, wei.liu, decui, shradhagupta, horms, ernis,
stephen, shirazsaleem, netdev, linux-rdma, linux-hyperv,
linux-kernel
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 2 · Low: 1
- [Medium] The patch makes the EQ pool port-owned and grows it against
the *live* port context from inside the allocation phase, while the…
- [Medium] mana_grow_eqs()
(drivers/net/ethernet/microsoft/mana/mana_en.c:1835) is the first NIC…
- [Low] mana_qset_scratch_alloc()'s kernel-doc
(drivers/net/ethernet/microsoft/mana/mana_en.c:3983-3985) still…
--
net: mana: share the EQ pool across a queue-set swap
The EQ array moves from the queue set to the port, so a queue-set swap no
longer needs old + new MSI-X vectors. mana_create_eq() now sizes the array
to apc->max_queues and tracks how many EQs exist in apc->num_eqs, and a new
mana_grow_eqs() adds EQs on demand from mana_alloc_qset().
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
> index b9d9543d1a6dc..bd80658cf3efc 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
[ ... ]
> @@ -1814,6 +1821,72 @@ int mana_create_eq(struct mana_port_context *apc)
> }
> EXPORT_SYMBOL_NS(mana_create_eq, "NET_MANA");
>
> +/**
> + * mana_grow_eqs - make sure the port has at least @need EQs
> + * @apc: port context
> + * @need: number of EQs the new queue set requires
> + *
> + * EQs are bound to MSI-X vectors, so the pool is port-owned and shared across
> + * a swap: peak usage is max(old, new), not the sum. Grow-only, up to
> + * apc->max_queues.
> + *
> + * Return: 0 on success, negative error code on failure.
> + */
> +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);
[Severity: Medium]
This looks like the first NIC path that creates an EQ while the port is up
and its MSI-X vectors are already delivering interrupts. Can a vector
interrupt land on the EQ before it is initialized?
mana_gd_create_mana_eq() -> mana_gd_create_eq() publishes the queue to the
handler list first:
gdma_main.c:mana_gd_create_eq() {
...
queue->id = INVALID_QUEUE_ID;
...
err = mana_gd_register_irq(queue, spec);
...
queue->eq.callback = spec->eq.callback;
queue->eq.context = spec->eq.context;
queue->head |= INITIALIZED_OWNER_BIT(log2_num_entries);
...
}
gdma_main.c:mana_gd_register_irq() {
...
spin_lock_irqsave(&gic->lock, flags);
list_add_rcu(&queue->entry, &gic->eq_list);
spin_unlock_irqrestore(&gic->lock, flags);
}
and the hardirq handler walks that list under RCU only:
gdma_main.c:mana_gd_intr() {
rcu_read_lock();
list_for_each_entry_rcu(eq, eq_list, entry) {
gic->handler(eq);
}
rcu_read_unlock();
}
With head still 0 and a zeroed ring, does mana_gd_process_eq_events() miss
its early return?
gdma_main.c:mana_gd_process_eq_events() {
...
old_bits = (eq->head / num_eqe - 1) & GDMA_EQE_OWNER_MASK;
/* No more entries */
if (owner_bits == old_bits) {
if (i == 0)
return;
...
}
old_bits computes as 7 while owner_bits reads 0, so the handler would
consume zeroed EQEs, advance eq->head and ring the doorbell while
queue->id is still INVALID_QUEUE_ID. The window spans the
mana_gd_create_hw_eq() and mana_gd_test_eq() HWC round trip.
The sharing precondition holds when gc->msi_sharing is set, and also when
an RDMA EQ already sits on the same index, since that path takes the vector
without setting the NIC bitmap bit:
drivers/infiniband/hw/mana/main.c:mana_ib_create_eqs() {
...
gic = mana_gd_get_gic(gc, false, &msi);
...
}
Would this show up as an intermittent EQ creation failure in
mana_gd_test_eq(), or as missed events on the new queue set? gdma_main.c is
unchanged across this series, and mana_grow_eqs() is still called on a live
port by mana_grow_qset() at the end of it.
> + 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;
> +}
> +
[ ... ]
> @@ -3926,14 +3996,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;
[Severity: Low]
With these two assignments gone, the kernel-doc just above
mana_qset_scratch_alloc() no longer matches the code:
* Builds a heap copy of @apc that shares its vport identity but owns no
* queues, so the existing allocators and destroyers can run against it
* without touching the live context.
After *scratch = *apc the scratch aliases apc->eqs, apc->num_eqs and the
live apc->mana_eqs_debugfs dentry. mana_destroy_eq() is an exported
NET_MANA symbol taking any struct mana_port_context *, and it does
debugfs_remove_recursive(apc->mana_eqs_debugfs) plus destroy and
kfree(apc->eqs), so running it against a scratch as the doc invites would
tear down the live port's EQs and EQ debugfs tree.
No caller does that today, either here or at the end of the series, so this
is a stale comment rather than a live bug. Should the wording call out
that EQs and the EQ debugfs dentry are the exception?
>
> /* Never consume the live set's pre-allocated RX buffers; the swap path
> * has no post-teardown allocation to de-risk.
> @@ -3959,7 +4029,8 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
> * installed set keeps serving traffic meanwhile. On error nothing is left
> * allocated.
> */
> -int mana_alloc_qset(struct mana_port_context *scratch, unsigned int num_queues,
> +int mana_alloc_qset(struct mana_port_context *apc,
> + struct mana_port_context *scratch, unsigned int num_queues,
> unsigned int rx_queue_size, unsigned int tx_queue_size,
> u32 priv_flags, struct mana_qset *out)
> {
> @@ -3981,13 +4052,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;
> +
[Severity: Medium]
Two documented contracts appear to be contradicted by this call.
The comment retained right above mana_alloc_qset() still says:
* installed set keeps serving traffic meanwhile. On error nothing is left
* allocated.
but the cleanup_eq label is deleted here and mana_grow_eqs() ends with:
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;
so a failed rebuild permanently raises apc->num_eqs and the port's MSI-X
footprint. Should the "on error nothing is left allocated" wording be
adjusted, or the growth unwound?
The second one is the header comment this patch rewrites (see the
include/net/mana/mana.h hunk below): mana_grow_eqs(apc, num_queues) writes
apc->eqs[i].eq and apc->num_eqs and creates children under the live
apc->mana_eqs_debugfs while the old set is installed and TX is enabled, not
inside mana_publish_qset().
For what it is worth, the "nothing is left allocated" half is repaired
later in this series: mana_alloc_qset() stops growing EQs and
mana_grow_qset() unwinds with mana_shrink_eqs(apc, apc->num_queues) on
failure. The header claim about where the live context is mutated still
reads the same at the end of the series, while mana_grow_qset() keeps
calling mana_grow_eqs() during allocation.
> err = mana_create_txq(scratch, ndev);
> if (err)
> - goto cleanup_eq;
> + goto cleanup_rss;
>
> err = mana_add_rx_queues(scratch, ndev);
> if (err)
> @@ -4005,8 +4083,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:
[ ... ]
> diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
> index 356aaa652fa6f..2117a30116c26 100644
> --- a/include/net/mana/mana.h
> +++ b/include/net/mana/mana.h
[ ... ]
> @@ -710,13 +714,14 @@ int mana_attach(struct net_device *ndev);
> int mana_detach(struct net_device *ndev, bool from_close);
>
> /* Pre-allocate + swap reconfiguration. Allocation and teardown run against a
> - * scratch context, so the live port context is only ever mutated with TX
> - * disabled.
> + * scratch context, so the live port context is mutated only inside
> + * mana_publish_qset() with TX disabled. Both sets share a port-owned EQ pool.
> */
Is this claim accurate once mana_grow_eqs() exists? The allocation phase
mutates the live @apc: apc->eqs[i].eq, apc->num_eqs and new debugfs
children under apc->mana_eqs_debugfs are all written from
mana_alloc_qset() -> mana_grow_eqs(), with the old set still installed and
TX enabled, so the mutation is not confined to mana_publish_qset().
> 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, struct mana_qset *out);
> void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset);
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901014442.2945689-1-longli%40microsoft.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 03/13] net: mana: swap queue sets in mana_set_channels
2026-09-01 1:44 ` [PATCH net-next v3 03/13] net: mana: swap queue sets in mana_set_channels Long Li
@ 2026-09-04 4:46 ` netdev-bot+sashiko
0 siblings, 0 replies; 33+ messages in thread
From: netdev-bot+sashiko @ 2026-09-04 4:46 UTC (permalink / raw)
To: longli
Cc: kotaranov, kuba, davem, pabeni, edumazet, andrew+netdev, jgg,
leon, haiyangz, kys, wei.liu, decui, shradhagupta, horms, ernis,
stephen, shirazsaleem, netdev, linux-rdma, linux-hyperv,
linux-kernel
Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 3 · Low: 3
- [Medium] The commit message ("If the swap itself fails the previous set
is put back and the port keeps running on it") and the…
- [Medium] mana_publish_qset() lowers ndev->real_num_rx_queues to the new
(smaller) count before the retiring RX queues stop delivering.
- [Medium] mana_publish_qset() reopens the datapath with 'smp_wmb();
WRITE_ONCE(apc->port_is_up, true); mana_start_txqs(apc);' and…
- [Low] mana_set_channels() builds the complete replacement queue set
with mana_alloc_qset() while the current set is still instantiated on…
- [Low] The comment on struct mana_port_context::channel_changing in
include/net/mana/mana.h still describes the flag as covering…
- [Low] The new inline port-down branch of mana_set_channels() writes
apc->num_queues = new_count and resizes apc->rxqs, but unlike every…
--
net: mana: swap queue sets in mana_set_channels
The channel count path stops using detach/attach: mana_set_channels() now
builds a replacement queue set, hands it to a new mana_publish_qset() that
swaps it in under RTNL, and retires the old set afterwards. Two new port
context fields (publish_dead_end, carrier_forced_off) record the case where
neither the new nor the old set could be installed.
A few questions about the new paths below.
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
> index bd80658cf3efc..880a3ba37fd3e 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
[ ... ]
> @@ -4094,6 +4145,289 @@ int mana_alloc_qset(struct mana_port_context *apc,
> return err;
> }
>
> +/* Close a port mana_publish_qset() gave up on; does nothing otherwise. Under
> + * RTNL.
> + *
> + * The caller releases the unpublished set first: closing destroys the shared
> + * EQ pool its CQs attach to, and only the caller knows whether it owns its
> + * queues or shares them with the live set. RX is already off.
> + *
> + * Merely stopping the port would leave port_is_up false with queues still
> + * allocated, so mana_detach() skips teardown and the next open trips
> + * WARN_ON(apc->eqs).
> + */
> +void mana_publish_close_if_needed(struct mana_port_context *apc)
> +{
> + ASSERT_RTNL();
> +
> + if (!apc->publish_dead_end)
> + return;
> +
> + apc->publish_dead_end = false;
> +
> + /* mana_dealloc_queues() requires the port already marked down, which
> + * mana_publish_qset() did before the swap it is unwinding.
> + */
> + if (mana_dealloc_queues(apc->ndev))
> + netdev_err(apc->ndev,
> + "failed to close the port after a failed rollback\n");
> +}
> +
> +/* Start only the netdev queues that can take work. A carried-over queue may
> + * still have a full ring, and restarting it would just make mana_start_xmit()
> + * drop; leave it for mana_poll_tx_cq() to wake. Must run after port_is_up is
> + * set, or that wakeup is gated off.
> + */
> +static void mana_start_txqs(struct mana_port_context *apc)
> +{
> + struct net_device *ndev = apc->ndev;
> + unsigned int i;
> +
> + if (!apc->tx_qp)
> + return;
> +
> + for (i = 0; i < apc->num_queues; i++) {
> + if (!apc->tx_qp[i])
> + continue;
> +
> + if (mana_can_tx(apc->tx_qp[i]->txq.gdma_sq))
> + netif_tx_wake_queue(netdev_get_tx_queue(ndev, i));
> + }
> +}
[ ... ]
> +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->carrier_forced_off = netif_carrier_ok(apc->ndev);
> + netif_carrier_off(apc->ndev);
> + apc->publish_dead_end = true;
> +}
[ ... ]
> + 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;
[Severity: Medium]
When the channel count is reduced, does lowering ndev->real_num_rx_queues
here happen before the retiring RX queues stop delivering?
At this point mana_publish_qset() has only installed the new (smaller) set.
The old RQs with rxq_idx >= the new count still have buffers posted and
their NAPIs enabled; they are only disabled much later, in
mana_free_qset() -> mana_destroy_rxqs() -> mana_destroy_rxq(), after
mana_config_rss() (an HWC round trip plus mana_fence_rqs(), up to 10s per
queue), a synchronize_net() and mana_drain_txqs() (bounded at 120s).
Everything those queues complete in that window is stamped with their own
index:
mana_rx_skb()
...
skb_record_rx_queue(skb, rxq_idx);
and that index then reaches the core:
net/core/dev.c:netif_get_rxqueue() {
...
if (unlikely(index >= dev->real_num_rx_queues)) {
WARN_ONCE(dev->real_num_rx_queues > 1,
"%s received packet on queue %u, but number "
"of RX queues is %u\n",
dev->name, index, dev->real_num_rx_queues);
...
}
netif_get_rxqueue() is reached from get_rps_cpu() whenever RPS/RFS is
configured and from netif_receive_generic_xdp(), so an 8 -> 4 reduction
under RX load looks like it can produce that warning plus a fallback to
rxqueue 0's RPS state. With the old detach/attach flow every RQ was
destroyed by mana_dealloc_queues() before the counts changed, so this
window did not exist.
Would it work to postpone netif_set_real_num_rx_queues() until after the
retiring queues have been quiesced?
> +
> + /* Carry the XDP program over before steering can reach the new RXQs:
> + * they were created with bpf_prog == NULL, so a packet arriving first
> + * would bypass an attached program. This also takes the per-queue
> + * references that mana_free_qset() drops for the old set.
> + */
> + mana_chn_setxdp(apc, mana_xdp_get(apc));
> +
> + err = mana_config_rss(apc, TRI_STATE_TRUE, true, true);
> + if (err)
> + goto rollback;
> +
> + /* Pair with the queue-state stores above: a datapath reader that sees
> + * the gate open must also see the queue set it is about to index.
> + */
> + smp_wmb();
> +
> + WRITE_ONCE(apc->port_is_up, true);
> + mana_start_txqs(apc);
[Severity: Medium]
Is a store-store barrier enough here, given that mana_start_txqs() then
loads queue state?
smp_wmb() does not order the store to apc->port_is_up against the following
load of the SQ head/tail inside mana_can_tx(), so this is the store-buffer
pattern:
CPU0 (ethtool, RTNL) CPU1 (NAPI)
WRITE_ONCE(apc->port_is_up, true); mana_move_wq_tail(); /* frees room */
mana_start_txqs() smp_mb(); smp_rmb();
mana_can_tx() -> ring still full reads apc->port_is_up == false
no netif_tx_wake_queue() no netif_tx_wake_queue()
The completion side is gated on a plain read:
mana_poll_tx_cq() {
...
/* Ensure checking txq_stopped before apc->port_is_up. */
smp_rmb();
if (txq_stopped && !READ_ONCE(txq->retiring) && apc->port_is_up &&
avail_space >= MAX_TX_WQE_SIZE) {
netif_tx_wake_queue(net_txq);
...
}
A fence on only one side of the store-buffer test still allows both loads
to return stale values, since CPU0's store to port_is_up can sit in its
store buffer while CPU0's load of the ring executes.
All queues are XOFF at that point because of the netif_tx_disable() earlier
in this function, and mana_start_txqs() is the only thing that can restart
them; once pending_sends is drained no further completion retries. Does
that leave the netdev TX queue stopped until the 15s watchdog fires
ndo_tx_timeout and resets the port?
The same pattern is repeated on the rollback path below, where the restored
old queues keep their rings and can legitimately be full. Would an smp_mb()
after the port_is_up store (or before reading it in mana_poll_tx_cq()) close
this?
> +
> + return 0;
> +
> +rollback:
> + netdev_err(ndev, "%s failed: %d, restoring previous queue set\n",
> + __func__, err);
[ ... ]
> + if (netif_set_real_num_tx_queues(ndev, apc->num_queues) ||
> + netif_set_real_num_rx_queues(ndev, apc->num_queues)) {
[ ... ]
> + 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)) {
[ ... ]
> + netdev_err(ndev, "failed to restore RSS steering, closing the port\n");
> + mana_publish_give_up(apc);
> + return err;
> + }
[Severity: Medium]
The commit message says:
"If the swap itself fails the previous set is put back and the port keeps
running on it."
Do these two branches match that description? Both call
mana_publish_give_up(), which disables vport RX, forces the carrier down and
sets apc->publish_dead_end, and the caller then reaches
mana_publish_close_if_needed() -> mana_dealloc_queues().
The result is a third outcome: the interface stays administratively up but
carries no traffic until an admin does a down/up. netif_set_real_num_rx_queues()
can return -ENOMEM from the sysfs/kobject update, and mana_config_rss() can
fail on an ordinary HWC error or timeout, so it is reachable.
Could the changelog describe that outcome, along with the two new fields
(publish_dead_end, carrier_forced_off) and the mana_open() /
mana_link_state_handle() carrier arbitration that exist only to make it
recoverable?
The same applies to the function comment added in mana_ethtool.c below,
which promises "On failure the existing queues keep running".
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> index ece7ff9cc409a..2ef3d461e7072 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> @@ -648,52 +648,98 @@ static int mana_set_coalesce(struct net_device *ndev,
[ ... ]
> +/* Build the new queue set while the current one keeps serving traffic, then
> + * swap. On failure the existing queues keep running and the requested value is
> + * never replaced by a fallback. The vport is never torn down, so RDMA cannot
> + * take it mid-reconfiguration.
> */
> static int mana_set_channels(struct net_device *ndev,
> struct ethtool_channels *channels)
> {
[ ... ]
> + 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;
> + }
[Severity: Low]
Should this port-down branch also update the netdev core counters and the
RSS table?
Every other writer of apc->num_queues pairs it with them:
mana_alloc_queues() {
...
err = netif_set_real_num_tx_queues(ndev, apc->num_queues);
...
err = netif_set_real_num_rx_queues(ndev, apc->num_queues);
...
mana_rss_table_init(apc);
}
and mana_publish_qset() added in this patch does the same. Here only
apc->num_queues and apc->rxqs change, yet mana_get_channels() reports the
new value immediately:
channels->combined_count = apc->num_queues;
So after "ip link set eth0 down; ethtool -L eth0 combined 4", does
"ethtool -l" advertise 4 while real_num_tx_queues / real_num_rx_queues and
the sysfs queue listing still describe the old count, and can "ethtool -x"
return indirection entries >= 4, until the next mana_open() quietly fixes
both?
> + 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(apc, 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 */
[Severity: Low]
Building the whole replacement set here means old_count + new_count SQ and
RQ work-queue objects have to coexist on the same vport, but the only
validation is new_count <= apc->max_queues, and apc->max_queues is itself
clamped to the firmware-reported per-vport limits:
mana_query_vport_cfg() {
...
*max_sq = resp.max_num_sq;
*max_rq = resp.max_num_rq;
}
Can "ethtool -L ethX combined N" now be rejected by the device at
MANA_CREATE_WQ_OBJ time for values that the previous detach/attach
implementation accepted, because old + new exceeds max_num_sq /
max_num_rq?
Later patches in this series appear to remove this condition:
mana_split_qset() re-partitions the existing queues and mana_grow_qset()
only fills the [old_count, new_count) slots, so the peak returns to
max(old_count, new_count). Is it worth folding that reduction in earlier, or
noting the temporary doubled peak in the changelog?
> diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
> index 2117a30116c26..dd767ab623912 100644
> --- a/include/net/mana/mana.h
> +++ b/include/net/mana/mana.h
> @@ -639,6 +639,21 @@ struct mana_port_context {
> */
> bool channel_changing;
[Severity: Low]
Could the comment above this field be refreshed? It currently reads:
/* Set by mana_set_channels() under vport_mutex to block RDMA
* from grabbing the vport during the detach/attach window.
* Checked by mana_cfg_vport() when called from the RDMA path.
*/
After this patch mana_set_channels() has no detach/attach window (the vport
is never released), and mana_cfg_vport() is no longer the only reader:
mana_set_channels() itself now tests the flag and returns a new -EBUSY.
The other writer also still claims parity that no longer holds:
mana_per_port_queue_reset_work_handler() {
...
/* Block RDMA from grabbing the vport during the detach/attach
* window, same as mana_set_channels().
*/
mutex_lock(&apc->vport_mutex);
apc->channel_changing = true;
It sets the flag unconditionally rather than following the new
test-then-set protocol. Both writers run under RTNL today, so nothing
misbehaves, but should they use the same protocol so a future caller that
does not hold RTNL cannot lose the -EBUSY or clear the flag early?
> + /* mana_publish_qset() could neither publish the new set nor restore the
> + * old one. Vport RX is already off; the port still has to be closed,
> + * which mana_publish_close_if_needed() does once the caller has
> + * released the set that failed.
> + */
> + bool publish_dead_end;
> +
> + /* The dead end above took the carrier down for a software reason, not
> + * a link event, so nothing else will ever put it back: the link
> + * handler only runs on a real HWC_DATA_HW_LINK_* transition. Remember
> + * it so the next successful open restores it, which is what makes the
> + * documented "recoverable with a down/up" actually true.
> + */
> + bool carrier_forced_off;
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901014442.2945689-1-longli%40microsoft.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 04/13] net: mana: swap queue sets in mana_set_ringparam
2026-09-01 1:44 ` [PATCH net-next v3 04/13] net: mana: swap queue sets in mana_set_ringparam Long Li
2026-09-02 1:45 ` sashiko-bot
@ 2026-09-04 4:46 ` netdev-bot+sashiko
1 sibling, 0 replies; 33+ messages in thread
From: netdev-bot+sashiko @ 2026-09-04 4:46 UTC (permalink / raw)
To: longli
Cc: kotaranov, kuba, davem, pabeni, edumazet, andrew+netdev, jgg,
leon, haiyangz, kys, wei.liu, decui, shradhagupta, horms, ernis,
stephen, shirazsaleem, netdev, linux-rdma, linux-hyperv,
linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 · Low: 0
- [Medium] The changelog of "net: mana: swap queue sets in
mana_set_ringparam" states: "This drops the fallback on failure.
--
net: mana: swap queue sets in mana_set_ringparam
This converts the ethtool ring size path to build a new queue set,
publish it with mana_publish_qset() and retire the old one, instead of
running mana_detach()/mana_attach() over the live configuration. It also
returns early when the rounded sizes already match the current ones.
> This drops the fallback on failure. Previously a failed mana_attach() was
> retried with the previous values, or the defaults, or the minimums, so a
> user who asked for a specific size could end up with a different one with
> no indication beyond dmesg. There is nothing to recover from now, so the
> error is returned.
[Severity: Medium]
Does the retry chain described here actually exist in the code being
replaced? In the pre-image of mana_set_ringparam() a failed mana_attach()
only writes the old sizes back into apc and returns the error:
err = mana_attach(ndev);
if (err) {
netdev_err(ndev, "mana_attach failed: %d\n", err);
apc->tx_queue_size = old_tx;
apc->rx_queue_size = old_rx;
}
out:
mana_pre_dealloc_rxbufs(apc);
return err;
mana_attach() in mana_en.c makes a single attempt:
err = mana_init_port(ndev);
if (err)
return err;
if (apc->port_st_save) {
err = mana_alloc_queues(ndev);
if (err) {
mana_cleanup_port_context(apc);
return err;
}
}
There is no second attach at the previous values, the defaults or the
minimums; the only other writers of apc->tx_queue_size /
apc->rx_queue_size in the driver are the one-time defaults set in
mana_probe_port().
Also, the restored size fields do not describe a working port after that
failure: mana_detach(ndev, false) already ran, so
mana_dealloc_queues() -> mana_destroy_vport() -> mana_uncfg_vport() has
released the vport and the interface stays detached until an explicit
down/up, with the vport claimable by RDMA in the meantime.
Could the changelog be reworded to describe what the old path really did,
so the connectivity aspect of this change is not presented as a cosmetic
"user may end up with a different size" cleanup?
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> index 2ef3d461e7072..24353dfddde3c 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
[ ... ]
> @@ -790,32 +788,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;
[ ... ]
> - 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);
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901014442.2945689-1-longli%40microsoft.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 05/13] net: mana: swap queue sets in mana_set_priv_flags
2026-09-01 1:44 ` [PATCH net-next v3 05/13] net: mana: swap queue sets in mana_set_priv_flags Long Li
@ 2026-09-04 4:46 ` netdev-bot+sashiko
0 siblings, 0 replies; 33+ messages in thread
From: netdev-bot+sashiko @ 2026-09-04 4:46 UTC (permalink / raw)
To: longli
Cc: kotaranov, kuba, davem, pabeni, edumazet, andrew+netdev, jgg,
leon, haiyangz, kys, wei.liu, decui, shradhagupta, horms, ernis,
stephen, shirazsaleem, netdev, linux-rdma, linux-hyperv,
linux-kernel
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 · Low: 1 (1)
- [Medium] Commit message describes a change the patch does not make:
"Scheduling queue_reset_work() on failure goes with it, leaving the TX…
- [Low] Understated failure impact in the commit message: "a failure
returns the error with both the queues and the flag word untouched"…
Pre-existing issues:
- [Low] Claimed comment-vs-code mismatch: the kernel-doc comment on
struct mana_port_context::channel_changing states it is "Set by…
--
net: mana: swap queue sets in mana_set_priv_flags
This converts the MANA_PRIV_FLAG_USE_FULL_PAGE_RXBUF toggle from an
in-place detach/attach cycle into a pre-allocate-then-swap using
mana_qset_scratch_alloc(), mana_alloc_qset(), mana_publish_qset() and
mana_free_qset(). The flag value is passed into the new queue set instead
of being written to apc->priv_flags before the rebuild, and the existing
shortcuts (down port, jumbo MTU, attached XDP program) are folded into one
condition.
No Link: tags are present in the commit header.
A couple of questions about the commit message and one note on an existing
comment follow.
The commit message says:
Scheduling queue_reset_work() on failure goes with it, leaving the TX
timeout handler as its only user.
[Severity: Medium]
Is this description accurate for this tree?
The pre-patch mana_set_priv_flags() in mana_ethtool.c does not schedule
queue_reset_work() at all - the removed code only calls
mana_pre_alloc_rxbufs(), mana_detach(), mana_attach() and
mana_pre_dealloc_rxbufs(). A grep for queue_reset_work at the parent
revision matches only mana_en.c and mana.h, never mana_ethtool.c, so there
appears to be nothing of that kind to remove here.
After the patch, queue_reset_work() still has three queue_work() sites in
mana_en.c:
mana_tx_timeout()
queue_work(ac->per_port_queue_reset_wq, &apc->queue_reset_work);
mana_dealloc_queues()
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);
mana_free_qset()
netdev_err(scratch->ndev,
"device reset while retiring a queue set, scheduling port reset\n");
queue_work(ac->per_port_queue_reset_wq,
&sib->queue_reset_work);
The last one is reached from the new success and publish-failure paths added
by this patch, so is the TX timeout handler really the only user now?
Could this paragraph be dropped or reworded?
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> index 24353dfddde3c..eab7df3fb888b 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
[ ... ]
> @@ -882,54 +886,53 @@ static int mana_set_priv_flags(struct net_device *ndev, u32 priv_flags)
[ ... ]
> - /* 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);
[Severity: Low]
This isn't a bug introduced by this patch, but the kernel-doc comment on
channel_changing in struct mana_port_context (include/net/mana/mana.h) is
getting further out of date:
/* Set by mana_set_channels() under vport_mutex to block RDMA
* from grabbing the vport during the detach/attach window.
* Checked by mana_cfg_vport() when called from the RDMA path.
*/
bool channel_changing;
It already named only mana_set_channels() at the baseline while
mana_set_priv_flags() and mana_per_port_queue_reset_work_handler() also set
the flag, and the region guarded here is now a pre-allocate/swap rather than
a detach/attach window. Would it make sense to refresh that comment along
with this conversion?
> - 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(apc, scratch, apc->num_queues, apc->rx_queue_size,
> + apc->tx_queue_size, priv_flags, &newq);
> + if (err)
> + goto free_scratch; /* current qset and priv_flags untouched */
>
> - err = mana_attach(ndev);
> - if (err) {
> - netdev_err(ndev, "mana_attach failed: %d\n", err);
> - apc->priv_flags = old_priv_flags;
> - }
> + err = mana_publish_qset(apc, &newq, &oldq);
> + if (err) {
> + mana_free_qset(scratch, &newq);
> + goto free_scratch;
> }
>
> -out:
> - mana_pre_dealloc_rxbufs(apc);
> + mana_free_qset(scratch, &oldq);
> +
> +free_scratch:
> + mana_publish_close_if_needed(apc);
> + mana_qset_scratch_free(scratch);
[Severity: Low]
The commit message states:
a failure returns the error with both the queues and the flag word
untouched
Does that hold for a failure inside mana_publish_qset()?
mana_publish_qset() can fail on mana_config_rss(), which is an HWC command
and can fail on an unhealthy device. Its rollback re-runs
mana_config_rss(), and if that fails too it calls mana_publish_give_up():
apc->carrier_forced_off = netif_carrier_ok(apc->ndev);
netif_carrier_off(apc->ndev);
apc->publish_dead_end = true;
mana_set_priv_flags() then reaches free_scratch and calls
mana_publish_close_if_needed(apc) unconditionally, which does:
if (mana_dealloc_queues(apc->ndev))
netdev_err(apc->ndev,
"failed to close the port after a failed rollback\n");
So ethtool --set-priv-flags can return an error while the port is left
closed with the carrier down until a manual down/up. The flag word does
look preserved (mana_qset_install() restores qset->priv_flags), so only the
queues half of the claim seems affected. Could the guarantee in the commit
message be qualified for the dead-end rollback case?
> clear_flag:
> mutex_lock(&apc->vport_mutex);
> apc->channel_changing = false;
> mutex_unlock(&apc->vport_mutex);
> -
> return err;
> }
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901014442.2945689-1-longli%40microsoft.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 06/13] net: mana: swap queue sets in mana_change_mtu
2026-09-01 1:44 ` [PATCH net-next v3 06/13] net: mana: swap queue sets in mana_change_mtu Long Li
@ 2026-09-04 4:46 ` netdev-bot+sashiko
0 siblings, 0 replies; 33+ messages in thread
From: netdev-bot+sashiko @ 2026-09-04 4:46 UTC (permalink / raw)
To: longli
Cc: kotaranov, kuba, davem, pabeni, edumazet, andrew+netdev, jgg,
leon, haiyangz, kys, wei.liu, decui, shradhagupta, horms, ernis,
stephen, shirazsaleem, netdev, linux-rdma, linux-hyperv,
linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 · Low: 0
- [Medium] `mana_change_mtu()` now builds a complete second queue set —
`mana_alloc_qset()` → `mana_create_txq()` + `mana_add_rx_queues()` on…
--
net: mana: swap queue sets in mana_change_mtu
The MTU becomes part of the queue-set configuration, so mana_change_mtu()
now pre-allocates a new queue set for the new MTU while the running set
still serves traffic. ndev->mtu is published in mana_publish_qset() once
the new set is live, instead of being written before mana_attach() and
rolled back on failure.
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
> index 880a3ba37fd3e..2c5aa5e5d1a11 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
> @@ -917,35 +917,49 @@ int mana_pre_alloc_rxbufs(struct mana_port_context *mpc, int new_mtu, int num_qu
> return -ENOMEM;
> }
>
> +/* ndev->mtu is updated only once the new set is live (mana_publish_qset), so
> + * a failed allocation leaves the queues and the advertised MTU untouched.
> + */
> static int mana_change_mtu(struct net_device *ndev, int new_mtu)
> {
> struct mana_port_context *mpc = netdev_priv(ndev);
> - unsigned int old_mtu = ndev->mtu;
> + struct mana_port_context *scratch;
> + struct mana_qset newq, oldq;
> int err;
>
> - /* Pre-allocate buffers to prevent failure in mana_attach later */
> - err = mana_pre_alloc_rxbufs(mpc, new_mtu, mpc->num_queues);
> - if (err) {
> - netdev_err(ndev, "Insufficient memory for new MTU\n");
> - return err;
> + /* Port is down: no queues to rebuild, just record the new MTU.
> + * mana_open() will size the RX buffers accordingly.
> + */
> + if (!mpc->port_is_up) {
> + mpc->configured_mtu = new_mtu;
> + WRITE_ONCE(ndev->mtu, new_mtu);
> + return 0;
> }
>
> - err = mana_detach(ndev, false);
> - if (err) {
> - netdev_err(ndev, "mana_detach failed: %d\n", err);
> - goto out;
> - }
> + scratch = mana_qset_scratch_alloc(mpc);
> + if (!scratch)
> + return -ENOMEM;
>
> - WRITE_ONCE(ndev->mtu, new_mtu);
> + err = mana_alloc_qset(mpc, 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 */
[Severity: Medium]
Can an MTU change now be refused at high channel counts where the old
detach-then-attach sequence succeeded?
mana_alloc_qset() here builds a complete second set on the scratch
context, mana_alloc_qset()->mana_create_txq() plus
mana_alloc_qset()->mana_add_rx_queues(), so num_queues firmware SQ/RQ WQ
objects and their CQs come into existence while apc->tx_qp[] and
apc->rxqs[] still own theirs on the same vport. The transient peak is
2 * num_queues SQ/RQ objects, and twice the CQ count against
gc->max_num_cqs / gc->cq_table.
mana_init_port() clamps the ceiling to what the vport actually offers:
max_queues = min_t(u32, max_txq, max_rxq);
if (apc->max_queues > max_queues)
apc->max_queues = max_queues;
if (apc->max_queues > gc->max_num_queues_vport)
apc->max_queues = gc->max_num_queues_vport;
and mana_set_channels() permits num_queues to reach that ceiling:
if (new_count < 1 || new_count > apc->max_queues) {
So after "ethtool -L ens1 combined <max_queues>", does
"ip link set dev ens1 mtu 9000" ask the firmware for one more SQ/RQ than
the vport advertises? The RX-side DMA footprint doubles for the same
window as well.
The design comment this series adds ahead of mana_qset_snapshot() says
exactly that:
* and the vport is never torn down, so RDMA cannot take it mid-swap. The cost
* is room for both sets at once, so a rebuild at the vport's maximum queue
* count can be refused; EQs are shared from a port-owned pool, not doubled.
The later patch "net: mana: keep the existing queues when the channel
count is raised" avoids that peak for the channel-count path by carrying
the existing queues over, but the MTU path still allocates a full second
set, both here and at the end of the series (bf2a8b43). Would it be worth
either applying a similar mitigation here or noting the new peak in the
changelog?
Failure itself looks graceful: mana_alloc_qset() tears down its partial
set and the live queues and ndev->mtu are untouched, so the visible
effect is the MTU change returning an error.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901014442.2945689-1-longli%40microsoft.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 07/13] net: mana: swap queue sets in mana_xdp_set
2026-09-01 1:44 ` [PATCH net-next v3 07/13] net: mana: swap queue sets in mana_xdp_set Long Li
@ 2026-09-04 4:46 ` netdev-bot+sashiko
0 siblings, 0 replies; 33+ messages in thread
From: netdev-bot+sashiko @ 2026-09-04 4:46 UTC (permalink / raw)
To: longli
Cc: kotaranov, kuba, davem, pabeni, edumazet, andrew+netdev, jgg,
leon, haiyangz, kys, wei.liu, decui, shradhagupta, horms, ernis,
stephen, shirazsaleem, netdev, linux-rdma, linux-hyperv,
linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 0 · Low: 1
- [Low] The rewrite silently repairs a real, user-triggerable failure-
path bug in the code it replaces, without documenting it or carrying a…
--
net: mana: swap queue sets in mana_xdp_set
The patch converts mana_xdp_set() from the detach/attach sequence to the
pre-allocate-and-swap scheme, so the RX queues are rebuilt for the new
buffer layout before anything live is touched. The XDP program becomes
part of struct mana_qset so it is installed and rolled back together with
the queues it was sized for.
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
> index e16ce2a071583..d2093625956bb 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
[ ... ]
> @@ -198,46 +205,46 @@ static int mana_xdp_set(struct net_device *ndev, struct bpf_prog *prog,
> return -EOPNOTSUPP;
> }
>
> - /* One refcnt of the prog is hold by the caller already, so
> - * don't increase refcnt for this one.
> - */
> - apc->bpf_prog = prog;
> -
> if (apc->port_is_up) {
> - /* Re-create rxq's after xdp prog was loaded or unloaded.
> - * Ex: re create rxq's to switch from full pages to smaller
> - * size page fragments when xdp prog is unloaded and
> - * vice-versa.
> - */
> -
> - /* Pre-allocate buffers to prevent failure in mana_attach */
> - err = mana_pre_alloc_rxbufs(apc, ndev->mtu, apc->num_queues);
> - if (err) {
> + scratch = mana_qset_scratch_alloc(apc);
> + if (!scratch) {
> NL_SET_ERR_MSG_MOD(extack,
> - "XDP: Insufficient memory for tx/rx re-config");
> - return err;
> + "XDP: Insufficient memory for re-config");
> + return -ENOMEM;
> }
>
> - err = mana_detach(ndev, false);
> + err = mana_alloc_qset(apc, 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;
> }
[Severity: Low]
Does this hunk quietly fix a failure-path bug in the code it replaces?
In the removed version, apc->bpf_prog was assigned before any allocation:
apc->bpf_prog = prog;
if (apc->port_is_up) {
err = mana_pre_alloc_rxbufs(apc, ndev->mtu, apc->num_queues);
if (err) {
NL_SET_ERR_MSG_MOD(extack,
"XDP: Insufficient memory for tx/rx re-config");
return err;
}
That early return skips the err_dealloc_rxbuffs label, which is the only
place that restored the old pointer:
err_dealloc_rxbuffs:
apc->bpf_prog = old_prog;
So on -ENOMEM from mana_pre_alloc_rxbufs() the attach is reported as failed
to userspace, the BPF core drops the caller's reference, and apc->bpf_prog
keeps pointing at the released program. A later mana_xdp_get() consumer such
as mana_chn_setxdp() from mana_alloc_queues() (next ifup, or after a reset)
would then bpf_prog_add() on that pointer.
The new code leaves the live configuration alone until mana_alloc_qset() and
mana_publish_qset() succeed, so the stale pointer can no longer happen.
The commit message only mentions the queue swap and the window where queues
ran without a program:
The program becomes part of the queue-set configuration, so it is
swapped atomically with the queues it was built for and restored by the
rollback path. Program references follow the same swap, so attaching no
longer leaves the queues briefly running without one.
Could it also note the corrected failure behaviour? The defect appears to
date back to 730ff06d3f5c ("net: mana: Use page pool fragments for RX
buffers instead of full pages to improve memory efficiency."). Since this
refactor is not backportable, would a separate minimal fix for net, or a
Fixes tag, help stable triage here?
>
> - err = mana_attach(ndev);
> + err = mana_publish_qset(apc, &newq, &oldq);
> if (err) {
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901014442.2945689-1-longli%40microsoft.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 08/13] net: mana: do not bail out of mana_detach on dealloc failure
2026-09-01 1:44 ` [PATCH net-next v3 08/13] net: mana: do not bail out of mana_detach on dealloc failure Long Li
@ 2026-09-04 4:46 ` netdev-bot+sashiko
0 siblings, 0 replies; 33+ messages in thread
From: netdev-bot+sashiko @ 2026-09-04 4:46 UTC (permalink / raw)
To: longli
Cc: kotaranov, kuba, davem, pabeni, edumazet, andrew+netdev, jgg,
leon, haiyangz, kys, wei.liu, decui, shradhagupta, horms, ernis,
stephen, shirazsaleem, netdev, linux-rdma, linux-hyperv,
linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 0 · Low: 1
- [Low] Commit-message framing concern: the reviewed patch removes the
`return err` after `mana_dealloc_queues()` in `mana_detach()`…
--
net: mana: do not bail out of mana_detach on dealloc failure
This removes the early return that mana_detach() took when
mana_dealloc_queues() returned an error, so that netif_device_detach()
and mana_cleanup_port_context() always run. The error is now only
logged.
> 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.
[Severity: Low]
This isn't a bug in the code, but could the description of the reset
handler case be toned down, since the "permanently dead" port does not
look reachable?
mana_dealloc_queues() has exactly one non-zero return:
drivers/net/ethernet/microsoft/mana/mana_en.c:mana_dealloc_queues() {
if (apc->port_is_up)
return -EINVAL;
...
}
and mana_detach() clears that flag two statements before the call:
drivers/net/ethernet/microsoft/mana/mana_en.c:mana_detach() {
apc->port_st_save = apc->port_is_up;
apc->port_is_up = false;
...
if (apc->port_st_save) {
err = mana_dealloc_queues(ndev);
...
}
Every other error inside mana_dealloc_queues(), such as the
mana_disable_vport_rx() failure, is only logged and the function ends with
an unconditional return 0.
The last paragraph does say "in practice cannot fail here since port_is_up
is already false", and the text uses "were to fail" / "would overwrite",
so the conditional framing is already there. Would it read better to state
up front that this is a robustness cleanup for an unreachable branch,
rather than describing a fatal reset-handler outcome first?
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
> index af2fdd5e876eb..5c5d1edd20f84 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
> @@ -4594,10 +4594,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) {
The code change itself looks fine; with port_is_up already false the
deleted return was dead code, so no live caller changes behaviour here.
Consistent with that, there is no Fixes: tag on the patch.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901014442.2945689-1-longli%40microsoft.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 09/13] net: mana: keep per-queue statistics in the port context
2026-09-01 1:44 ` [PATCH net-next v3 09/13] net: mana: keep per-queue statistics in the port context Long Li
2026-09-02 1:45 ` sashiko-bot
@ 2026-09-04 4:46 ` netdev-bot+sashiko
1 sibling, 0 replies; 33+ messages in thread
From: netdev-bot+sashiko @ 2026-09-04 4:46 UTC (permalink / raw)
To: longli
Cc: kotaranov, kuba, davem, pabeni, edumazet, andrew+netdev, jgg,
leon, haiyangz, kys, wei.liu, decui, shradhagupta, horms, ernis,
stephen, shirazsaleem, netdev, linux-rdma, linux-hyperv,
linux-kernel
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 · Low: 2
- [Medium] mana_get_stats64() keeps the `if (!apc->port_is_up) return;`
early return above the new port-lifetime accumulation, so the counters…
- [Low] mana_update_rx_dim() now feeds net_dim() from
rxq->stats->packets/bytes, which after this patch is…
- [Low] The writer invariant documented for the new statistics arrays is
contradicted by the code added in the same patch.
--
net: mana: keep per-queue statistics in the port context
This moves the per-queue RX/TX statistics out of struct mana_rxq and
struct mana_txq into arrays owned by the port context, sized to
max_queues and allocated at probe time. A retiring RX queue counts into
a private drain_stats which is folded into a per-index "retired" array,
and ndo_get_stats64() adds the live and retired slots for every index.
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
> index 5c5d1edd20f84..69e34fab8fe8e 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
[ ... ]
> @@ -639,9 +639,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;
> @@ -649,6 +649,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;
> +
[Severity: Medium]
Does the retained port_is_up gate above this still let rx_bytes be
observed going backwards through the very operation the commit message
describes?
mana_publish_qset() clears the flag on entry and only sets it again at the
very end:
WRITE_ONCE(apc->port_is_up, false);
...
netif_tx_disable(ndev);
mana_qset_set_retiring(out_old, newq, true);
synchronize_net();
...
netif_set_real_num_tx_queues() / netif_set_real_num_rx_queues()
mana_chn_setxdp()
mana_config_rss()
...
WRITE_ONCE(apc->port_is_up, true);
so the flag is false across the whole ethtool channel/ring/priv-flag, MTU
and XDP-attach window, and stays false indefinitely on the
mana_publish_give_up() dead ends.
Readers are not serialized against it. netstat_show() (sysfs
statistics/rx_bytes) and dev_seq_show() (/proc/net/dev) reach
dev_get_stats() under rcu_read_lock() only, never RTNL, and dev_get_stats()
zeroes the storage before calling the driver:
net/core/dev.c:dev_get_stats() {
...
if (ops->ndo_get_stats64) {
memset(storage, 0, sizeof(*storage));
ops->ndo_get_stats64(dev, storage);
...
}
Returning early therefore publishes zeros rather than the previous values,
so a reader polling during "ethtool -L" would still see
4475831638 -> 0 -> N.
Since the arrays are now port-owned and valid whether or not a queue
exists at that index, could they be reported unconditionally?
mana_get_ethtool_stats() carries the same "if (!apc->port_is_up) return;"
gate, which also makes the newly persistent per-queue counters unreadable
while the port is administratively down.
[ ... ]
> @@ -1092,6 +1112,122 @@ static void mana_cleanup_port_context(struct mana_port_context *apc)
> apc->rxqs = NULL;
> }
>
> +/* Counters belong to the port, not the queues, so a queue-set replacement
> + * does not reset them. Sized to max_queues, allocated once.
> + *
> + * A swap adds no writer to a TX slot. RX slots do overlap briefly, since a
> + * retiring rxq keeps its NAPI until mana_free_qset() destroys it. MANA is
> + * 64-bit only, so u64_stats_sync has no seqcount and at worst a few
> + * increments are lost; the alternatives are a lock in the receive path or
> + * per-set slots that make ndo_get_stats64() dip during a swap.
> + */
> +static int mana_alloc_queue_stats(struct mana_port_context *apc)
[Severity: Low]
This isn't a bug, but does this comment contradict the one added to struct
mana_port_context in the same patch?
Here the RX live slots are described as having overlapping writers:
"RX slots do overlap briefly, since a retiring rxq keeps its NAPI
until mana_free_qset() destroys it ... at worst a few increments
are lost"
while the header says:
"rxq_stats[] is written by the live RX queue at that index and
rxq_stats_ret[] only under RTNL, by mana_destroy_rxq() folding in
what a retiring queue counted while it drained. One writer each;
readers add the two."
The header is the text a later auditor is likely to consult when deciding
whether these bare u64_stats_sync updates need a lock.
[ ... ]
> @@ -2672,8 +2811,15 @@ 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);
> + /* A retiring queue counts elsewhere and is about to be destroyed, so
> + * there is no moderation left to tune and its samples would step off
> + * the shared slot onto a counter that restarts at zero.
> + */
> + if (READ_ONCE(rxq->retiring))
> + return;
> +
> + dim_update_sample(READ_ONCE(cq->dim_event_ctr), rxq->stats->packets,
> + rxq->stats->bytes, &dim_sample);
> net_dim(&cq->dim, &dim_sample);
> }
[Severity: Low]
After this change the DIM input is apc->rxq_stats[rxq_idx], a per-index
counter shared by every queue generation that served that index, while
cq->dim.start_sample belongs to one queue instance. When the retiring flag
is cleared again on the rollback path, is anything re-basing cq->dim?
mana_publish_qset() rollback:
mana_qset_set_retiring(newq, out_old, true);
synchronize_net();
mana_qset_set_retiring(out_old, NULL, false);
The restored @out_old queues resume sampling with the start_sample they
captured before the swap, at which point the shared slot held X. The
@newq queues were unmarked and installed earlier and may have advanced the
same slot to Y > X (steering can already point at them when
mana_config_rss() fails part-way), so the first dim_update_sample() after
the rollback attributes another queue's packets and bytes to this queue.
mana_dim_change() memsets cq->dim on enable for what looks like the same
reason:
if (enable) {
memset(&cq->dim, 0, sizeof(cq->dim));
Also, the commit message describes the change as moving statistics storage
only, and does not mention that the RX interrupt-moderation input source
and its new skip condition changed. Should that be called out?
[ ... ]
> @@ -4230,16 +4382,33 @@ static void mana_start_txqs(struct mana_port_context *apc)
> * A queue both sets own must end up unmarked, so callers mark the leaving set
> * first and unmark the incoming one second.
> */
> -static void mana_qset_set_retiring(struct mana_qset *qset, bool retiring)
> +static void mana_qset_set_retiring(struct mana_qset *qset,
> + const struct mana_qset *keep, bool retiring)
> {
> unsigned int q;
>
> - if (!qset->tx_qp)
> - return;
> -
> for (q = 0; q < qset->num_queues; q++) {
> - if (qset->tx_qp[q])
> + if (qset->tx_qp && qset->tx_qp[q])
> WRITE_ONCE(qset->tx_qp[q]->txq.retiring, retiring);
> +
> + if (!qset->rxqs || !qset->rxqs[q])
> + continue;
> +
> + /* A queue @keep carries over serves the same index before and
> + * after, so it stays the live writer of that index. Marking it
> + * would strand the counts it takes during the swap in
> + * drain_stats, which only mana_destroy_rxq() drains.
> + */
[Severity: Low]
Same comment inconsistency as above: "which only mana_destroy_rxq()
drains" is no longer true once the rollback path below calls
mana_fold_qset_rx_stats().
[ ... ]
> @@ -4384,8 +4553,23 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
> /* 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_set_retiring(newq, out_old, true);
> +
> + /* Same grace period as the forward path: a poll that sampled the flag
> + * before the line above must finish before @out_old is unmarked, or
> + * both sets would briefly count into apc->rxq_stats[].
> + */
> + synchronize_net();
> +
> + mana_qset_set_retiring(out_old, NULL, false);
> +
> + /* @out_old counted into drain_stats while it was marked, and it is
> + * about to serve again rather than be destroyed, so nothing else
> + * would ever publish those packets. Fold them now, once the polls
> + * that still saw the flag above have finished writing.
> + */
> + synchronize_net();
> + mana_fold_qset_rx_stats(apc, out_old);
[Severity: Low]
This is the second writer of apc->rxq_stats_ret[] that the header comment
and mana_qset_set_retiring() both say does not exist. Both writers do run
under RTNL, so the serialization holds; only the documentation is stale.
[ ... ]
> diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
> index 61d136736cf6c..9a43856760776 100644
> --- a/include/net/mana/mana.h
> +++ b/include/net/mana/mana.h
[ ... ]
> @@ -415,7 +419,23 @@ struct mana_rxq {
>
> u32 buf_index;
>
> - struct mana_stats_rx stats;
> + /* Points into apc->rxq_stats[], which outlives the queue. Only the
> + * live queue at this index writes there; once retiring is set this
> + * queue counts into drain_stats instead, so the slot has one writer.
> + * Use mana_rxq_stats() rather than either directly.
> + */
> + struct mana_stats_rx *stats;
> +
> + /* Set under RTNL before a different queue takes over this index. A
> + * queue carried across a swap keeps serving its index and is never
> + * marked.
> + */
> + bool retiring;
> +
> + /* What this queue counted after it stopped being the live one.
> + * Folded into apc->rxq_stats_ret[] when the queue is destroyed.
> + */
> + struct mana_stats_rx drain_stats;
[Severity: Low]
"Folded into apc->rxq_stats_ret[] when the queue is destroyed" - the
rollback path folds drain_stats for queues that are not destroyed but
resume service. Could this be reworded to cover both cases?
> @@ -623,6 +643,19 @@ struct mana_port_context {
> unsigned int max_queues;
> unsigned int num_queues;
>
> + /* Per-queue counters, max_queues entries each. Allocated at probe and
> + * freed at remove, never on queue teardown, so a reconfiguration does
> + * not reset them.
> + *
> + * rxq_stats[] is written by the live RX queue at that index and
> + * rxq_stats_ret[] only under RTNL, by mana_destroy_rxq() folding in
> + * what a retiring queue counted while it drained. One writer each;
> + * readers add the two.
> + */
[Severity: Low]
"only under RTNL, by mana_destroy_rxq()" and "One writer each" are the two
claims that the mana_alloc_queue_stats() comment and the
mana_fold_qset_rx_stats() call in mana_publish_qset()'s rollback path
contradict. Should this describe both rxq_stats_ret[] writers, and drop
the "One writer each" statement for the live slots?
> + struct mana_stats_rx *rxq_stats;
> + struct mana_stats_rx *rxq_stats_ret;
> + struct mana_stats_tx *txq_stats;
> +
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901014442.2945689-1-longli%40microsoft.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 10/13] net: mana: release EQs left idle by a channel-count reduction
2026-09-01 1:44 ` [PATCH net-next v3 10/13] net: mana: release EQs left idle by a channel-count reduction Long Li
2026-09-02 1:45 ` sashiko-bot
@ 2026-09-04 4:46 ` netdev-bot+sashiko
1 sibling, 0 replies; 33+ messages in thread
From: netdev-bot+sashiko @ 2026-09-04 4:46 UTC (permalink / raw)
To: longli
Cc: kotaranov, kuba, davem, pabeni, edumazet, andrew+netdev, jgg,
leon, haiyangz, kys, wei.liu, decui, shradhagupta, horms, ernis,
stephen, shirazsaleem, netdev, linux-rdma, linux-hyperv,
linux-kernel
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 0 · Low: 3
- [Low] Three comments (and the commit message) state a shrink
precondition that this patch itself violates, and one pre-existing…
- [Low] mana_free_qset() gains a leading `struct mana_port_context *apc`
parameter (exported prototype in include/net/mana/mana.h plus eight…
- [Low] Concern (assessed as a false positive): the commit lacks a Fixes:
trailer for the commit that made the grow-only EQ pool reachable.
--
net: mana: release EQs left idle by a channel-count reduction
The shared EQ pool previously only grew, so a port kept the high-water mark
of every channel count it had ever used. This adds mana_shrink_eqs() to
release the EQs above the live queue count once a retiring set is gone, and
fixes mana_create_eq_debugfs() to record the dentry in apc->eqs[i] so a
single EQ's debugfs directory can be removed.
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
> index 69e34fab8fe8e..2295769da0306 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
[ ... ]
> @@ -2084,6 +2085,37 @@ static int mana_grow_eqs(struct mana_port_context *apc, unsigned int need)
> return err;
> }
>
> +/* Release EQs above @keep, returning the MSI-X vectors freed. Only safe once
> + * no set references them, i.e. after mana_free_qset(), or a live CQ would
> + * point at a destroyed EQ.
> + */
[Severity: Low]
This comment says a shrink is only safe after mana_free_qset(), and the
commit message says the same:
"Release the EQs above the live queue count once a retiring set has been
torn down. That is the only safe point"
The same patch also calls mana_shrink_eqs() from mana_alloc_qset()'s
out_err path, which is not after mana_free_qset(). Can the two be
reconciled?
The rule that appears to hold at both sites is "no CQ of any set
references eqs[keep..num_eqs-1]": mana_alloc_qset()'s out_err is reached
either before any queue was built, or after mana_destroy_rxqs(scratch) and
mana_destroy_txq(scratch) destroyed the CQs that attempt created. Would
stating that condition instead be more accurate, here and in
mana_free_qset()'s "Safe here and only here" comment below?
Separately, the error exit of mana_grow_eqs() just above this new helper
still reads:
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;
Since its only caller, mana_alloc_qset(), now runs
mana_shrink_eqs(apc, apc->num_queues) on that same error return, is
anything still kept for the next attempt?
> +static void mana_shrink_eqs(struct mana_port_context *apc, unsigned int keep)
> +{
> + struct gdma_context *gc = apc->ac->gdma_dev->gdma_context;
> + struct gdma_queue *eq;
> + unsigned int msi;
> + unsigned int i;
> +
> + if (!apc->eqs || keep >= apc->num_eqs)
> + return;
> +
> + for (i = keep; i < apc->num_eqs; i++) {
> + eq = apc->eqs[i].eq;
> + if (!eq)
> + continue;
> +
> + debugfs_remove_recursive(apc->eqs[i].mana_eq_debugfs);
> + apc->eqs[i].mana_eq_debugfs = NULL;
> +
> + msi = eq->eq.msix_index;
> + mana_gd_destroy_queue(gc, eq);
> + mana_gd_put_gic(gc, !gc->msi_sharing, msi);
> + apc->eqs[i].eq = NULL;
> + }
> +
> + apc->num_eqs = keep;
> +}
> +
> static int mana_fence_rq(struct mana_port_context *apc, struct mana_rxq *rxq)
> {
> struct mana_fence_rq_resp resp = {};
> @@ -4319,6 +4351,13 @@ int mana_alloc_qset(struct mana_port_context *apc,
> kfree(scratch->rxqs);
> scratch->rxqs = NULL;
> out_err:
> + /* Give back any EQ this attempt added to the shared pool rather than
> + * holding its MSI-X vectors until some later teardown: the live set
> + * still needs only apc->num_queues of them. Safe here because this
> + * set's CQs have already been destroyed above.
> + */
> + mana_shrink_eqs(apc, apc->num_queues);
> +
> netdev_err(ndev, "%s(num_queues=%u) failed: %d\n", __func__,
> num_queues, err);
> return err;
> @@ -4649,7 +4688,8 @@ static void mana_qset_debugfs_publish(struct mana_port_context *apc)
> /* Tear down @qset, no longer installed on @apc, against @scratch so the live
> * context never points at queues being freed.
> */
> -void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
> +void mana_free_qset(struct mana_port_context *apc,
> + struct mana_port_context *scratch, struct mana_qset *qset)
> {
> struct bpf_prog *retiring_prog;
> unsigned int retiring_queues;
[Severity: Low]
Does the new apc parameter carry anything the function cannot already
derive? mana_qset_scratch_alloc() does *scratch = *apc, so
netdev_priv(scratch->ndev) == apc for every one of the eight call sites,
which is exactly what this patch relies on when it replaces
mana_qset_debugfs_publish(netdev_priv(scratch->ndev)) with
mana_qset_debugfs_publish(apc).
The TX-drain-failure block inside the same function still has:
if (mana_drain_txqs(scratch)) {
...
struct mana_port_context *apc = netdev_priv(scratch->ndev);
so that local now shadows the new parameter, which -Wshadow / W=2 builds
will flag.
Nothing verifies that a caller passes a matching (apc, scratch) pair, and a
mismatched pair would let the mana_shrink_eqs() call below destroy another
port's EQs. Would dropping either the parameter or the inner declaration
be preferable?
The exported prototype in include/net/mana/mana.h changes with it - is that
signature change worth a line in the commit message?
> @@ -4745,12 +4785,19 @@ void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset)
>
> memset(qset, 0, sizeof(*qset));
>
> + /* This set is gone, so any EQ above the live queue count is now
> + * unreferenced. Release those vectors instead of holding them at the
> + * high-water mark. Safe here and only here: the retiring set's CQs
> + * have just been destroyed.
> + */
> + mana_shrink_eqs(apc, apc->num_queues);
> +
> /* Queues built through a scratch context carry no debugfs nodes,
> * because both sets are alive during the swap and would collide on
> * the same names. The retiring set's nodes are gone now, so the
> * published queues can finally take those names.
> */
> - mana_qset_debugfs_publish(netdev_priv(scratch->ndev));
> + mana_qset_debugfs_publish(apc);
> }
>
> /* --- end of pre-allocate + swap reconfiguration path ---------------------- */
> diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
> index 9a43856760776..140948b2b45d5 100644
> --- a/include/net/mana/mana.h
> +++ b/include/net/mana/mana.h
> @@ -797,7 +797,8 @@ int mana_alloc_qset(struct mana_port_context *apc,
> int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
> struct mana_qset *out_old);
> void mana_publish_close_if_needed(struct mana_port_context *apc);
> -void mana_free_qset(struct mana_port_context *scratch, struct mana_qset *qset);
> +void mana_free_qset(struct mana_port_context *apc,
> + struct mana_port_context *scratch, struct mana_qset *qset);
Both observations above are documentation and readability points; I did not
find a path where either shrink call site touches an EQ that a live CQ
still references.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901014442.2945689-1-longli%40microsoft.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 11/13] net: mana: keep a user-configured RSS table across a queue rebuild
2026-09-01 1:44 ` [PATCH net-next v3 11/13] net: mana: keep a user-configured RSS table across a queue rebuild Long Li
@ 2026-09-04 4:46 ` netdev-bot+sashiko
0 siblings, 0 replies; 33+ messages in thread
From: netdev-bot+sashiko @ 2026-09-04 4:46 UTC (permalink / raw)
To: longli
Cc: kotaranov, kuba, davem, pabeni, edumazet, andrew+netdev, jgg,
leon, haiyangz, kys, wei.liu, decui, shradhagupta, horms, ernis,
stephen, shirazsaleem, netdev, linux-rdma, linux-hyperv,
linux-kernel
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 · Low: 2
- [Medium] In mana_alloc_queues() the loss is reported before the driver
state it describes is updated: ethtool_rxfh_indir_lost(ndev) is called…
- [Low] Commit message and the in-code comment describe a narrower rule
than the code implements.
- [Low] The patch is a user-visible bug fix (a user-installed `ethtool
-X` indirection table was silently replaced by the driver default on…
--
net: mana: keep a user-configured RSS table across a queue rebuild
The patch stops mana from unconditionally regenerating the RSS indirection
table when the queues are rebuilt, so a table installed with "ethtool -X"
survives a reconfiguration. A table that cannot be applied to the new
queue count is reported to the core with ethtool_rxfh_indir_lost(), and for
the swap path that report is deferred until the new set is live.
A couple of questions below about the described condition, the missing
Fixes: tag, and the ordering of the loss report in mana_alloc_queues().
> Carry it over instead when the queue count is unchanged: the entries are
> queue indices, so they stay meaningful.
[Severity: Low]
Is "when the queue count is unchanged" the condition the code actually
implements? mana_rss_table_keep() tests netif_is_rxfh_configured() plus a
per-entry bounds check against the requested count:
if (!netif_is_rxfh_configured(apc->ndev))
return false;
for (i = 0; i < apc->indir_table_sz; i++) {
if (apc->indir_table[i] >= num_queues) {
and mana_alloc_qset() passes the new count, which mana_set_channels() only
reaches when new_count differs from apc->num_queues.
So "ethtool -L" also carries a user table over now instead of rebuilding
it. Growing 4 -> 8 combined channels leaves every old entry in range, the
table is copied verbatim, and the four new RX queues get no steered
traffic. mana_get_tx_queue() indexes apc->indir_table[] too, so the new TX
queues stay unused as well until the user re-runs "ethtool -X".
The semantics match what other drivers do (ice, mlx5, bnxt, idpf and others
skip default regeneration when netif_is_rxfh_configured() is set), so could
the changelog state the real predicate (entries still in range for the new
count) and mention the "ethtool -L" effect?
[Severity: Low]
Should this carry a Fixes: tag? In the baseline tree mana_alloc_queues()
called mana_rss_table_init(apc) unconditionally, so every detach/attach
cycle (reset recovery, resume, MTU/ringparam/channel/priv-flag/XDP
reconfiguration) overwrote apc->indir_table with the driver default while
the core kept IFF_RXFH_CONFIGURED set, since netif_is_rxfh_configured()
reads dev->ethtool->rss_indir_user_size and the driver never cleared it.
That is a user-visible loss of configuration, which makes this part a fix.
The unconditional initialization came in with ca9c54d2d6a5 ("net: mana: Add
a driver for Microsoft Azure Network Adapter (MANA)"), which also added
mana_set_rxfh(). Would a Fixes: trailer, or splitting the
mana_alloc_queues() change out as a standalone fix, be appropriate here?
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
> index 2295769da0306..6769faada11bc 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
[ ... ]
> @@ -3949,7 +3982,18 @@ int mana_alloc_queues(struct net_device *ndev)
> goto destroy_rxq;
> }
>
> - mana_rss_table_init(apc);
> + /* Keep a user-configured RSS table across a rebuild; the entries are
> + * queue indices, so they stay meaningful as long as the queue count
> + * is unchanged. Only a driver-generated table is regenerated here.
> + *
> + * Nothing to roll back to here, so report the loss as soon as it is
> + * decided and keep the table and the core's view of it in step.
> + */
> + if (!mana_rss_table_keep(apc, apc->num_queues, &indir_lost)) {
> + if (indir_lost)
> + ethtool_rxfh_indir_lost(ndev);
> + mana_rss_table_init(apc);
> + }
[Severity: Medium]
Can this report the wrong table to userspace? ethtool_rxfh_indir_lost() is
not just a flag clear:
net/ethtool/common.c:ethtool_rxfh_indir_lost() {
...
dev->ethtool->rss_indir_user_size = 0;
ethtool_rss_notify(dev, ETHTOOL_MSG_RSS_NTF, 0);
...
}
That notification re-enters the driver synchronously:
ethtool_rss_notify()->ethnl_notify()->ethnl_default_notify()->
rss_prepare_data()->rss_prepare()->rss_prepare_get()
net/ethtool/rss.c:rss_prepare_get() {
...
ret = ops->get_rxfh(dev, &rxfh);
...
}
and mana_get_rxfh() copies apc->indir_table[] verbatim. At that point
mana_rss_table_init(apc) has not run yet, so the broadcast RSS_NTF carries
the table that mana_rss_table_keep() just rejected (it has at least one
entry >= apc->num_queues), and mana_config_rss() has not programmed
anything. No further notification is emitted once the default table is
installed, so a listener caching notification state keeps the discarded
table.
Reachable in the documented case: mana_init_port() clamps apc->num_queues
down to a smaller device-reported max after a reset, and a user table with
a higher entry then makes mana_rss_table_keep() set *lost.
Would swapping the two statements, so the loss is reported after
mana_rss_table_init(apc), be better? That matches the ordering of the
other new call site, which reports only once the new set is live:
if (newq->rxfh_indir_lost)
ethtool_rxfh_indir_lost(ndev);
>
> err = mana_config_rss(apc, TRI_STATE_TRUE, true, true);
> if (err) {
[ ... ]
> @@ -4333,9 +4382,19 @@ int mana_alloc_qset(struct mana_port_context *apc,
> if (err)
> goto cleanup_rxq;
>
> - mana_rss_table_init(scratch);
> + /* Carry a user-configured RSS table over to the new set. The entries
> + * are queue indices, so mana_config_rss() in mana_publish_qset() maps
> + * them onto the new set's RX objects. A driver-generated table is
> + * rebuilt instead, so it covers every queue of the new set.
> + */
> + if (mana_rss_table_keep(apc, num_queues, &indir_lost))
> + memcpy(scratch->indir_table, apc->indir_table,
> + apc->indir_table_sz * sizeof(*apc->indir_table));
> + else
> + mana_rss_table_init(scratch);
>
> mana_qset_snapshot(scratch, out);
> + out->rxfh_indir_lost = indir_lost;
> return 0;
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901014442.2945689-1-longli%40microsoft.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 12/13] net: mana: keep the surviving queues when the channel count is reduced
2026-09-01 1:44 ` [PATCH net-next v3 12/13] net: mana: keep the surviving queues when the channel count is reduced Long Li
@ 2026-09-04 4:46 ` netdev-bot+sashiko
0 siblings, 0 replies; 33+ messages in thread
From: netdev-bot+sashiko @ 2026-09-04 4:46 UTC (permalink / raw)
To: longli
Cc: kotaranov, kuba, davem, pabeni, edumazet, andrew+netdev, jgg,
leon, haiyangz, kys, wei.liu, decui, shradhagupta, horms, ernis,
stephen, shirazsaleem, netdev, linux-rdma, linux-hyperv,
linux-kernel
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 · Low: 2
- [Medium] Use-after-free read of the RSS indirection table in the new
channel-reduction branch of mana_set_channels().
- [Low] Stale/contradictory in-tree contract comments in
mana_publish_qset() for the new channel-reduction caller.
- [Low] mana_split_qset()'s header comment at
drivers/net/ethernet/microsoft/mana/mana_en.c:4335 says "Allocates two…
--
net: mana: keep the surviving queues when the channel count is reduced
A channel-count reduction no longer rebuilds the queues it keeps. The live
set is carved into a kept prefix and a tail by the new mana_split_qset(),
the prefix is published and only the tail is retired. Increases and
per-queue property changes still rebuild a full set.
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
> index 6769faada11bc..ae7c664bf76ef 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
> @@ -4327,7 +4327,135 @@ void mana_qset_scratch_free(struct mana_port_context *scratch)
> kvfree(scratch);
> }
>
> -/* Build a queue set in @scratch, sized and configured from the arguments. The
> +/* Carve the live set into a kept prefix [0, @new_count) in @out_new and a
> + * tail to retire in @out_tail. @apc is untouched, on failure too.
> + *
> + * Queue i is built from the ring sizes and the buffer layout and keeps EQ i
> + * at any count, so a reduction carries the survivors over and destroys only
> + * the tail. Allocates two pointer arrays and a steering table, nothing else:
> + * the queues that stay keep their page pools, posted buffers and NAPI.
> + */
[Severity: Low]
This isn't a bug, but does the allocation inventory in this comment match
what the body does?
Four pointer arrays are allocated, not two, and mana_rss_table_alloc()
allocates two tables (indir_table and rxobj_table), both of which
mana_discard_split() and mana_set_channels() free separately.
The commit message carries the same count:
"a reduction allocates two pointer arrays and a steering table
instead of a full queue set"
Should both read four pointer arrays and two tables?
> +int mana_split_qset(struct mana_port_context *apc,
> + struct mana_port_context *scratch, unsigned int new_count,
> + struct mana_qset *out_new, struct mana_qset *out_tail)
> +{
[ ... ]
> + scratch->num_queues = new_count;
> + err = mana_rss_table_alloc(scratch);
> + if (err)
> + return err;
> +
> + if (mana_rss_table_keep(apc, new_count, &indir_lost))
> + memcpy(scratch->indir_table, apc->indir_table,
> + apc->indir_table_sz * sizeof(*apc->indir_table));
> + else
> + mana_rss_table_init(scratch);
> +
> + new_tx = kzalloc_objs(struct mana_tx_qp *, new_count);
> + new_rx = kzalloc_objs(struct mana_rxq *, new_count);
> + tail_tx = kzalloc_objs(struct mana_tx_qp *, tail_count);
> + tail_rx = kzalloc_objs(struct mana_rxq *, tail_count);
[ ... ]
> + /* The tail. It owns no steering table; bpf_prog is carried so that
> + * retiring it drops exactly the tail's per-queue program references
> + * and leaves the kept ones alone.
> + */
[Severity: Low]
Where does the number of program references dropped for the tail come
from? mana_free_qset() installs the tail into @scratch and then calls:
retiring_prog = mana_chn_xdp_peek(scratch);
retiring_queues = scratch->num_queues;
...
mana_chn_xdp_release(retiring_prog, retiring_queues);
so the count comes from the tail's queue count, not from
out_tail->bpf_prog.
Related to that, is the comment in mana_publish_qset() still accurate for
the set this helper builds?
/* Carry the XDP program over before steering can reach the new RXQs:
* they were created with bpf_prog == NULL, so a packet arriving first
* would bypass an attached program. This also takes the per-queue
* references that mana_free_qset() drops for the old set.
*/
mana_chn_setxdp(apc, mana_xdp_get(apc));
For a reduction, newq->rxqs[] are the live queues, so they already carry
apc->bpf_prog and mana_chn_setxdp() returns immediately:
drivers/net/ethernet/microsoft/mana/mana_bpf.c:mana_chn_setxdp() {
struct bpf_prog *old_prog = mana_chn_xdp_get(apc);
...
if (old_prog == prog)
return;
if (prog)
bpf_prog_add(prog, num_queues);
...
}
The refcount does balance today: old_count references are held going in,
mana_free_qset() puts tail_count, and new_count are left for the new_count
live queues. Read together though, the two comments describe old_count +
new_count taken against tail_count released, and the balance rests on that
undocumented early return in another file. Could the publish comment
mention the carried-over case?
The same function's header contract ("On failure the old set is reinstalled
and the caller frees only @newq") also reads oddly for this caller, which
must free only @newq's containers via mana_discard_split() because @newq's
queues are the live ones.
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> index c7ddd26cada25..b5587764333f1 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
> @@ -754,6 +755,39 @@ static int mana_set_coalesce(struct net_device *ndev,
> goto clear_flag;
> }
>
> + /* A reduction keeps its queues configured identically, so carry them
> + * over and retire only the tail: no DMA ring, no hardware WQ object,
> + * and no old+new peak.
> + */
> + if (new_count < apc->num_queues) {
> + struct mana_qset tailq;
> +
> + err = mana_split_qset(apc, scratch, new_count, &newq, &tailq);
> + if (err)
> + goto free_scratch; /* current qset untouched */
> +
> + err = mana_publish_qset(apc, &newq, &oldq);
> + if (err) {
> + /* The old set is live again; drop only the containers
> + * built above, never the queues they point at.
> + */
> + mana_discard_split(&newq, &tailq);
> + goto free_scratch;
> + }
> +
> + /* @oldq holds the original arrays and steering table. Every
> + * queue they referenced is now owned by either the published
> + * set or the tail, so only the containers are freed here.
> + */
> + kfree(oldq.tx_qp);
> + kfree(oldq.rxqs);
> + kfree(oldq.indir_table);
> + kfree(oldq.rxobj_table);
[Severity: Medium]
Can this kfree() release the indirection table while the transmit path is
still reading it?
mana_get_tx_queue() dereferences apc->indir_table with no lock and no
port_is_up gate:
drivers/net/ethernet/microsoft/mana/mana_en.c:mana_get_tx_queue() {
txq = apc->indir_table[hash & (apc->indir_table_sz - 1)];
...
}
It is reached from ndo_select_queue, netdev_core_pick_tx() ->
mana_select_queue() -> mana_get_tx_queue(), inside __dev_queue_xmit()'s
rcu_read_lock_bh() section.
The only synchronize_net() on the publish success path runs before the
pointer is replaced, and transmits are running again by the time publish
returns:
mana_publish_qset()
synchronize_net(); /* before the swap */
mana_qset_set_retiring(newq, NULL, false);
mana_qset_install(apc, newq); /* apc->indir_table replaced here */
...
WRITE_ONCE(apc->port_is_up, true);
mana_start_txqs(apc);
mana_set_channels()
kfree(oldq.indir_table); /* no grace period since the swap */
So a CPU that loaded the old apc->indir_table before mana_qset_install()
and has not yet done the array load can read freed memory.
mana_config_rss() only waits on an HWC completion, which says nothing about
other CPUs' read-side sections.
Before this patch the reduction freed the old table inside mana_free_qset()
-> mana_cleanup_indir_table(scratch), which happens after that function's
synchronize_net() and therefore after the new set is installed.
Would a synchronize_net() before these frees, or moving them after
mana_free_qset(), close that window? The same pattern appears in the grow
branch added later in the series ("net: mana: keep the existing queues when
the channel count is raised"), where oldq.indir_table is freed the same way,
so both branches look affected at the end of the series.
> +
> + mana_free_qset(apc, scratch, &tailq);
> + goto free_scratch;
> + }
> +
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901014442.2945689-1-longli%40microsoft.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PATCH net-next v3 13/13] net: mana: keep the existing queues when the channel count is raised
2026-09-01 1:44 ` [PATCH net-next v3 13/13] net: mana: keep the existing queues when the channel count is raised Long Li
@ 2026-09-04 4:46 ` netdev-bot+sashiko
0 siblings, 0 replies; 33+ messages in thread
From: netdev-bot+sashiko @ 2026-09-04 4:46 UTC (permalink / raw)
To: longli
Cc: kotaranov, kuba, davem, pabeni, edumazet, andrew+netdev, jgg,
leon, haiyangz, kys, wei.liu, decui, shradhagupta, horms, ernis,
stephen, shirazsaleem, netdev, linux-rdma, linux-hyperv,
linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 0 · Low: 1
- [Low] Claim of latent double-free/use-after-free: the new helper
mana_destroy_txq_from()…
--
net: mana: keep the existing queues when the channel count is raised
Growing the channel count now carries the running queues over and builds
only the [old, new) tail, instead of building a full second queue set and
discarding the old one. mana_alloc_qset() loses its num_queues argument
since a count change goes through mana_split_qset() or the new
mana_grow_qset(), and the self-recovery-on-resize-failure capability bit is
advertised to the firmware.
> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
> index ae7c664bf76ef..67bfea57e6ed2 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
[ ... ]
> @@ -2958,7 +2958,11 @@ static void mana_deinit_txq(struct mana_port_context *apc, struct mana_txq *txq)
> mana_gd_destroy_queue(gd->gdma_context, txq->gdma_sq);
> }
>
> -static void mana_destroy_txq(struct mana_port_context *apc)
> +/* The array itself is left in place: the grow path tears down only a range,
> + * and the queues below @first are still live and still referenced by it.
> + */
> +static void mana_destroy_txq_from(struct mana_port_context *apc,
> + unsigned int first)
> {
> struct napi_struct *napi;
> int i;
> @@ -2966,7 +2970,7 @@ static void mana_destroy_txq(struct mana_port_context *apc)
> if (!apc->tx_qp)
> return;
>
> - for (i = 0; i < apc->num_queues; i++) {
> + for (i = first; i < apc->num_queues; i++) {
> if (!apc->tx_qp[i])
> continue;
>
> @@ -2991,6 +2995,14 @@ static void mana_destroy_txq(struct mana_port_context *apc)
>
> kvfree(apc->tx_qp[i]);
> }
> +}
[Severity: Low]
This isn't a bug today, but should this loop clear the slot after the
kvfree(), the way the RX sibling added in this same patch does?
The comment above mana_destroy_txq_from() says the array is deliberately
left in place, and the loop guard is:
if (!apc->tx_qp[i])
continue;
but nothing sets apc->tx_qp[i] = NULL after kvfree(), so for the
[first, num_queues) range that guard can no longer tell "already
destroyed" from "live".
mana_destroy_rxqs_from() does clear its slot:
mana_destroy_rxq(apc, rxq, true);
apc->rxqs[rxq_idx] = NULL;
I traced the three call sites and none of them re-reads a stale slot at the
moment:
mana_destroy_txq() -> first == 0, immediately followed by
kfree(apc->tx_qp); apc->tx_qp = NULL;
mana_create_txq() out: (first != 0) -> caller mana_grow_qset() jumps to
cleanup_rss, skipping cleanup_rxq
mana_grow_qset() cleanup_rxq -> falls into free_arrays, which just
kfree()s new_tx without walking it
So the only thing keeping the tail from being torn down twice is the label
choice in mana_grow_qset() below. Would a single store keep the two helpers
symmetric and make that independent of the caller?
>
> +static void mana_destroy_txq(struct mana_port_context *apc)
> +{
> + if (!apc->tx_qp)
> + return;
> +
> + mana_destroy_txq_from(apc, 0);
>
> kfree(apc->tx_qp);
> apc->tx_qp = NULL;
[ ... ]
> @@ -3164,7 +3185,10 @@ static int mana_create_txq(struct mana_port_context *apc,
> out:
> netdev_err(net, "Failed to create %d TX queues, %d\n",
> apc->num_queues, err);
> - mana_destroy_txq(apc);
> + if (first)
> + mana_destroy_txq_from(apc, first);
> + else
> + mana_destroy_txq(apc);
> return err;
> }
[Severity: Low]
This is the first standalone caller of mana_destroy_txq_from(): it tears
down [first, num_queues) and returns without freeing the container, so the
freed-but-non-NULL pointers survive back into mana_grow_qset().
[ ... ]
> @@ -3569,6 +3599,11 @@ static void mana_destroy_rxqs(struct mana_port_context *apc)
> }
> }
>
> +static void mana_destroy_rxqs(struct mana_port_context *apc)
> +{
> + mana_destroy_rxqs_from(apc, 0);
> +}
> +
[ ... ]
> @@ -4454,13 +4489,161 @@ void mana_discard_split(struct mana_qset *newq, struct mana_qset *tailq)
[ ... ]
> + err = mana_create_txq(scratch, ndev, old_count);
> + if (err)
> + goto cleanup_rss; /* create_txq already undid its own work */
> +
> + err = mana_add_rx_queues(scratch, ndev, old_count);
> + if (err)
> + goto cleanup_rxq;
[Severity: Low]
Related to the comment on mana_destroy_txq_from(): correctness here rests
entirely on this goto targeting cleanup_rss rather than cleanup_rxq.
If a later change routed the mana_create_txq() failure to cleanup_rxq
instead, mana_destroy_txq_from(scratch, old_count) would run a second time
over the tail slots that mana_create_txq()'s out: path already kvfree()d,
re-running napi_disable_locked() / cancel_work_sync() on freed memory.
Is that invariant worth making explicit, either with the NULL store in the
helper or a note at this label?
[ ... ]
> +cleanup_rxq:
> + mana_destroy_rxqs_from(scratch, old_count);
> + mana_destroy_txq_from(scratch, old_count);
> +cleanup_rss:
> + mana_cleanup_indir_table(scratch);
> +free_arrays:
> + /* Only the containers: every queue they name is still live on @apc. */
> + scratch->tx_qp = NULL;
> + scratch->rxqs = NULL;
> + kfree(new_tx);
> + kfree(new_rx);
> + kfree(fresh_tx);
> + kfree(fresh_rx);
[ ... ]
> @@ -4861,7 +5037,7 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
> * Idempotent: a carried-over queue keeps its node; suppressed creation leaves
> * an error pointer, not NULL, so both read as "no node". Under RTNL.
> */
> -static void mana_qset_debugfs_publish(struct mana_port_context *apc)
> +void mana_qset_debugfs_publish(struct mana_port_context *apc)
> {
> unsigned int i;
>
[Severity: Low]
One more consumer of the same convention, now that this is no longer
static:
if (apc->tx_qp && apc->tx_qp[i] &&
IS_ERR_OR_NULL(apc->tx_qp[i]->mana_tx_debugfs))
It treats any non-NULL tx_qp[i] as live and dereferences it. I confirmed it
cannot be reached with a stale array today, because every grow-failure path
frees the container without publishing it, and after a failed
mana_publish_qset() the rollback reinstalls the original apc->tx_qp.
Does relying on that reachability argument, rather than on the slot being
cleared, seem like the right long-term contract for the helper?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260901014442.2945689-1-longli%40microsoft.com
^ permalink raw reply [flat|nested] 33+ messages in thread
end of thread, other threads:[~2026-09-04 4:47 UTC | newest]
Thread overview: 33+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
2026-09-01 1:44 ` [PATCH net-next v3 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 02/13] net: mana: share the EQ pool across a queue-set swap Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 03/13] net: mana: swap queue sets in mana_set_channels Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 04/13] net: mana: swap queue sets in mana_set_ringparam Long Li
2026-09-02 1:45 ` sashiko-bot
2026-09-02 19:25 ` [EXTERNAL] " Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 05/13] net: mana: swap queue sets in mana_set_priv_flags Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 06/13] net: mana: swap queue sets in mana_change_mtu Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 07/13] net: mana: swap queue sets in mana_xdp_set Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 08/13] net: mana: do not bail out of mana_detach on dealloc failure Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 09/13] net: mana: keep per-queue statistics in the port context Long Li
2026-09-02 1:45 ` sashiko-bot
2026-09-02 19:28 ` [EXTERNAL] " Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 10/13] net: mana: release EQs left idle by a channel-count reduction Long Li
2026-09-02 1:45 ` sashiko-bot
2026-09-02 19:30 ` [EXTERNAL] " Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 11/13] net: mana: keep a user-configured RSS table across a queue rebuild Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 12/13] net: mana: keep the surviving queues when the channel count is reduced Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 13/13] net: mana: keep the existing queues when the channel count is raised Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox