* [PATCH net-next v5 0/4] net: mana: concurrent HWC requests and dynamic queue depth
@ 2026-09-08 3:51 Long Li
2026-09-08 3:51 ` [PATCH net-next v5 1/4] net: mana: track when the HWC has been handed to the PF Long Li
` (3 more replies)
0 siblings, 4 replies; 9+ messages in thread
From: Long Li @ 2026-09-08 3:51 UTC (permalink / raw)
To: Long Li, 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
This series enables concurrent MANA Hardware Channel (HWC) requests and
rebuilds the depth-one bootstrap queues at the device-reported maximum,
capped at 128. The first two patches provide handover and per-slot
ownership state; the last two add concurrency and dynamic depth.
Version history follows the original combined submission through its
split: v3 was a fixes-only net posting, while v4 and v5 here are the
standalone net-next feature series. The separate net v4-v8 iterations
are not revisions of this four-patch series and are not prerequisites.
Patch numbers in each entry refer to that version.
Changes in v5 (v4 -> v5):
- Patches 1-2: shorten comments and describe the existing error-path
changes, including teardown polling and query-only zero-timeout
filtering. No executable changes from v4 in these patches.
- Patch 3: reject apparent success from a response accepted before the
request was submitted, preserving an existing cancellation error.
- Patch 4: guard CQ unpublishing when establishment failed before
allocating cq_table.
- Patch 4: after an initial destroy failure, require successful retry
teardown before creating fresh bootstrap queues. Do not test the old
queues with uncertain mappings.
- Clarify publication and locking scope, dimension acceptance, and the
missing-doorbell guard's limits. Shorten all commit messages.
Changes in v4 (return to net-next after the v3 split):
- Rework the feature series into four standalone patches: handover
tracking, per-slot completion state, concurrent requests and dynamic
queue depth. No dependency on the separate net fixes.
- Patch 1: record submission in mana_smc_setup_hwc() itself, including
failure after handover, rather than using max_num_cqs as the gate.
- Patch 2: separate per-slot ownership from concurrency; make error
signed, handle a response racing timeout, and ignore zero timeout-
query replies while leaving asynchronous updates unchanged.
- Patch 3: use bounded semaphore admission, guarded sender accounting,
and per-request timeout quarantine rather than a channel-wide latch.
Retain quarantine for zero-timeout cleanup.
- Patch 4: cap depth at 128, round message buffers to a power of two,
and require bootstrap message sizes before growing the queues.
Retain the full 24-bit depth in u32 and check allocation bounds.
- Patch 4: reset dimensions/routing IDs for each establish; check for a
missing doorbell after the handshake; validate rebuilt dimensions.
Retry teardown before bootstrap recovery after failed re-establishment.
- Base the four-patch series on net-next 1bb784eb6e38.
Changes in v3 (v2 -> net v3, historical fixes-only posting):
- Split the combined series after maintainer feedback. Post six fixes
to net and defer the concurrent-request and dynamic-depth features.
- Add a separate stale-response patch with per-slot ownership,
buffer withdrawal, a channel timeout latch and zero-event filtering.
- Remove the pcie_flr()-based teardown fallback; retain HWC resources
when teardown cannot be confirmed.
- Patch 2: publish queue dimensions before creating the CQ so the RX
path has its stride and slot bound.
- Rebase on net af39eb111ce6 and clarify messages/comments.
These describe the historical net submission, not additional fixes
carried by the current net-next series.
Changes in v2 (v1 -> v2):
- Patch 4: bounds-check the SGE pointer derived from inline OOB size
before dereferencing it.
- Patch 6: protect active-sender accounting with hwc_lock and drain via
wait_event_lock_irq(), so the last wakeup cannot access freed gc.
- Patch 6: force-complete and drain senders before teardown/FLR handling,
including its failure exits.
- Rebase on net-next e354f7d60f14 and keep code within 80 columns.
v1 (initial submission):
- Seven patches combined CQ-table lifetime, RQ/SQ sizing, completion-
buffer teardown, RX validation and HWC teardown fixes with concurrent
requests and dynamic queue depth.
- Base: net-next f6f3b36c15ed.
The v4 admission and quarantine policies are unchanged: FIFO admission has
its own timeout and may trigger recovery on contention; zero-timeout
cleanup still retains slots for outstanding requests. Existing IRQ/DMA
teardown and service-lifetime issues remain outside this revision.
The series remains based on net-next commit 1bb784eb6e38. It does not
require the abandoned net v8 series.
Tested on Azure: each commit's MANA Ethernet/RDMA build; three PCI cycles;
queue-count, MTU and link changes; and bidirectional 16-stream, 60-second
TCP traffic. Synthetic timeout and bootstrap-fallback recovery were also
exercised. RDMA coverage was enumeration, not verbs traffic.
v4:
https://lore.kernel.org/all/20260901200018.3194525-1-longli@microsoft.com/
v3 (historical net split):
https://lore.kernel.org/all/20260803234355.636038-1-longli@microsoft.com/
v2:
https://lore.kernel.org/all/20260721234339.1476932-1-longli@microsoft.com/
v1:
https://lore.kernel.org/all/20260715032942.3945317-1-longli@microsoft.com/
Long Li (4):
net: mana: track when the HWC has been handed to the PF
net: mana: give each HWC message slot its own completion state
net: mana: support concurrent HWC requests
net: mana: add dynamic HWC queue depth with reinit path
.../net/ethernet/microsoft/mana/gdma_main.c | 52 +-
.../net/ethernet/microsoft/mana/hw_channel.c | 535 ++++++++++++++++--
.../net/ethernet/microsoft/mana/shm_channel.c | 8 +-
include/net/mana/gdma.h | 13 +
include/net/mana/hw_channel.h | 46 +-
include/net/mana/shm_channel.h | 2 +-
6 files changed, 593 insertions(+), 63 deletions(-)
base-commit: 1bb784eb6e38fd73143f021608e4ef3095d0c0d7
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v5 1/4] net: mana: track when the HWC has been handed to the PF
2026-09-08 3:51 [PATCH net-next v5 0/4] net: mana: concurrent HWC requests and dynamic queue depth Long Li
@ 2026-09-08 3:51 ` Long Li
2026-09-09 3:52 ` sashiko-bot
2026-09-08 3:51 ` [PATCH net-next v5 2/4] net: mana: give each HWC message slot its own completion state Long Li
` (2 subsequent siblings)
3 siblings, 1 reply; 9+ messages in thread
From: Long Li @ 2026-09-08 3:51 UTC (permalink / raw)
To: Long Li, 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
Track possible PF queue ownership with setup_active for HWC reinit.
Set it before submitting ESTABLISH_HWC. Cleanup now attempts DESTROY_HWC
after submitted setup failures even before MAX_NUM_CQS arrives, adding
potentially tens of seconds of polling. Resources are still freed if
teardown fails.
Signed-off-by: Long Li <longli@microsoft.com>
---
Changes in v5 (v4 -> v5):
- Describe additional teardown attempts and polling after setup failure.
- Clarify the flag's context lifetime and setup precondition.
- Shorten comments; no executable changes from v4.
Changes in v4 (standalone net-next rework after the v3 split):
- Introduce this dedicated handover-tracking preparation patch.
- Set the submission flag inside mana_smc_setup_hwc() and use it as the
cleanup gate, including setup failures before MAX_NUM_CQS arrives.
- Do not carry the separate net series' teardown-failure resource retention.
Changes in v3 (historical net fixes-only posting):
- Handover tracking remained in the teardown-safety patch (5/6).
- That patch dropped pcie_flr() recovery and retained resources after
failed teardown. Those changes are not part of this preparation patch.
Changes in v2 (v1 -> v2):
- Handover tracking remained part of teardown-safety patch 5/7.
- No separate preparation patch was posted.
v1:
- The combined series introduced setup_active in teardown-safety patch 5/7.
drivers/net/ethernet/microsoft/mana/hw_channel.c | 14 +++++++-------
drivers/net/ethernet/microsoft/mana/shm_channel.c | 8 +++++++-
include/net/mana/hw_channel.h | 3 +++
include/net/mana/shm_channel.h | 2 +-
4 files changed, 18 insertions(+), 9 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c
index 263e7c4e2934186af037be4c80350a6e322b6771..88e92e94e2e90ff31ca6710a7e9b8e34b5fa191c 100644
--- a/drivers/net/ethernet/microsoft/mana/hw_channel.c
+++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c
@@ -683,7 +683,7 @@ static int mana_hwc_establish_channel(struct gdma_context *gc, u16 *q_depth,
cq->mem_info.dma_handle,
rq->mem_info.dma_handle,
sq->mem_info.dma_handle,
- eq->eq.msix_index);
+ eq->eq.msix_index, &hwc->setup_active);
if (err)
return err;
@@ -815,13 +815,13 @@ void mana_hwc_destroy_channel(struct gdma_context *gc)
if (!hwc)
return;
- /* gc->max_num_cqs is set in mana_hwc_init_event_handler(). If it's
- * non-zero, the HWC worked and we should tear down the HWC here.
- */
- if (gc->max_num_cqs > 0) {
- mana_smc_teardown_hwc(&gc->shm_channel, false);
- gc->max_num_cqs = 0;
+ if (hwc->setup_active) {
+ if (!mana_smc_teardown_hwc(&gc->shm_channel, false))
+ hwc->setup_active = false;
+ else
+ dev_err(hwc->dev, "Failed to tear down HWC\n");
}
+ gc->max_num_cqs = 0;
if (hwc->txq)
mana_hwc_destroy_wq(hwc, hwc->txq);
diff --git a/drivers/net/ethernet/microsoft/mana/shm_channel.c b/drivers/net/ethernet/microsoft/mana/shm_channel.c
index d21b5db06e5092d82249fb1053d07f65aa38490c..3cf6a9f8e32c4ff5e5423fc950e88082941e21a8 100644
--- a/drivers/net/ethernet/microsoft/mana/shm_channel.c
+++ b/drivers/net/ethernet/microsoft/mana/shm_channel.c
@@ -129,9 +129,12 @@ void mana_smc_init(struct shm_channel *sc, struct device *dev,
sc->base = base;
}
+/* Requires no outstanding HWC handover. *submitted records possible PF
+ * ownership, including when setup fails after submission.
+ */
int mana_smc_setup_hwc(struct shm_channel *sc, bool reset_vf, u64 eq_addr,
u64 cq_addr, u64 rq_addr, u64 sq_addr,
- u32 eq_msix_index)
+ u32 eq_msix_index, bool *submitted)
{
union smc_proto_hdr *hdr;
u16 all_addr_h4bits = 0;
@@ -144,6 +147,8 @@ int mana_smc_setup_hwc(struct shm_channel *sc, bool reset_vf, u64 eq_addr,
int err;
int i;
+ *submitted = false;
+
/* Ensure VF already has possession of shared memory */
err = mana_smc_poll_register(sc->base, false);
if (err) {
@@ -229,6 +234,7 @@ int mana_smc_setup_hwc(struct shm_channel *sc, bool reset_vf, u64 eq_addr,
/* Write 256-message buffer to shared memory (final 32-bit write
* triggers HW to set possession bit to PF).
*/
+ *submitted = true;
dword = (u32 *)shm_buf;
for (i = 0; i < SMC_APERTURE_DWORDS; i++)
writel(*dword++, sc->base + i * SMC_BASIC_UNIT);
diff --git a/include/net/mana/hw_channel.h b/include/net/mana/hw_channel.h
index 16feb39616c1bead1a043b3fadc2e18a90651516..befa09674ce5614a955441e75e480a21aa8695fb 100644
--- a/include/net/mana/hw_channel.h
+++ b/include/net/mana/hw_channel.h
@@ -199,6 +199,9 @@ struct hw_channel_context {
u32 pf_dest_vrcq_id;
u32 hwc_timeout;
+ /* PF may own the queue mappings; state lasts only for this context. */
+ bool setup_active;
+
struct hwc_caller_ctx *caller_ctx;
};
diff --git a/include/net/mana/shm_channel.h b/include/net/mana/shm_channel.h
index dbabcfb95daf3e87b39a657e3a5f23a1508d4f31..e96387d795259d52aeec7b3fe0981825d90a3822 100644
--- a/include/net/mana/shm_channel.h
+++ b/include/net/mana/shm_channel.h
@@ -20,7 +20,7 @@ void mana_smc_init(struct shm_channel *sc, struct device *dev,
int mana_smc_setup_hwc(struct shm_channel *sc, bool reset_vf, u64 eq_addr,
u64 cq_addr, u64 rq_addr, u64 sq_addr,
- u32 eq_msix_index);
+ u32 eq_msix_index, bool *submitted);
int mana_smc_teardown_hwc(struct shm_channel *sc, bool reset_vf);
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH net-next v5 2/4] net: mana: give each HWC message slot its own completion state
2026-09-08 3:51 [PATCH net-next v5 0/4] net: mana: concurrent HWC requests and dynamic queue depth Long Li
2026-09-08 3:51 ` [PATCH net-next v5 1/4] net: mana: track when the HWC has been handed to the PF Long Li
@ 2026-09-08 3:51 ` Long Li
2026-09-09 3:52 ` sashiko-bot
2026-09-08 3:51 ` [PATCH net-next v5 3/4] net: mana: support concurrent HWC requests Long Li
2026-09-08 3:51 ` [PATCH net-next v5 4/4] net: mana: add dynamic HWC queue depth with reinit path Long Li
3 siblings, 1 reply; 9+ messages in thread
From: Long Li @ 2026-09-08 3:51 UTC (permalink / raw)
To: Long Li, 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
Add per-slot locking, sender/response references and a responded flag;
make ctx->error signed. Stop copying after buffer withdrawal or completion.
Return a response recorded at the timeout check instead of -ETIMEDOUT,
without shortening later waits.
Ignore zero timeout-query replies; asynchronous updates remain unfiltered.
Timed-out slots remain reusable; the next patch adds quarantine.
Signed-off-by: Long Li <longli@microsoft.com>
---
Changes in v5 (v4 -> v5):
- Describe buffer withdrawal and response precedence at the timeout check.
- State that zero filtering applies only to timeout-query replies.
- Shorten the message and comment; no executable changes from v4.
Changes in v4 (standalone net-next rework after the v3 split):
- Extract per-slot ownership into this preparation patch: lock,
sender/response references, responded flag and signed error.
- Initialize the slot before publishing its bitmap bit.
- Return a recorded response when it races timeout; ignore zero timeout-
query replies. Keep timed-out slot reuse here for the next patch to change.
Changes in v3 (historical net fixes-only posting):
- A separate stale-response fix (6/6) supplied per-slot locking/references
and buffer withdrawal, together with a channel timeout latch and
asynchronous zero-timeout filtering. The latter policy is not carried here.
Changes in v2 (v1 -> v2):
- Per-slot state remained within concurrency patch 6/7.
- Its channel-lifetime accounting and teardown drain were revised; see
the concurrency patch's history.
v1:
- Per-slot locking and references were introduced in concurrency patch 6/7.
.../net/ethernet/microsoft/mana/gdma_main.c | 6 +-
.../net/ethernet/microsoft/mana/hw_channel.c | 167 +++++++++++++++---
include/net/mana/hw_channel.h | 18 +-
3 files changed, 163 insertions(+), 28 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c
index f92b2d0bf926e1b715ff665d37f8173a2103e6fe..8d86de0a334b21d77ab6bfb578917c56404bc856 100644
--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
+++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
@@ -331,7 +331,11 @@ static int mana_gd_query_hwc_timeout(struct pci_dev *pdev, u32 *timeout_val)
if (err || resp.hdr.status)
return err ? err : -EPROTO;
- *timeout_val = resp.timeout_ms;
+ /* Keep the current timeout on a zero query reply. Asynchronous
+ * HWC_DATA_CFG_HWC_TIMEOUT updates remain unfiltered.
+ */
+ if (resp.timeout_ms)
+ *timeout_val = resp.timeout_ms;
return 0;
}
diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c
index 88e92e94e2e90ff31ca6710a7e9b8e34b5fa191c..6605e7a9c481bcb11c95f90f627b7c422b62cc28 100644
--- a/drivers/net/ethernet/microsoft/mana/hw_channel.c
+++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c
@@ -6,9 +6,11 @@
#include <net/mana/hw_channel.h>
#include <linux/vmalloc.h>
+/* Acquire a free inflight message slot, waiting for one if all are in use. */
static int mana_hwc_get_msg_index(struct hw_channel_context *hwc, u16 *msg_id)
{
struct gdma_resource *r = &hwc->inflight_msg_res;
+ struct hwc_caller_ctx *ctx;
unsigned long flags;
u32 index;
@@ -19,6 +21,17 @@ static int mana_hwc_get_msg_index(struct hw_channel_context *hwc, u16 *msg_id)
index = find_first_zero_bit(hwc->inflight_msg_res.map,
hwc->inflight_msg_res.size);
+ ctx = &hwc->caller_ctx[index];
+ reinit_completion(&ctx->comp_event);
+ /* Take both references (sender + handle_resp) before publishing the
+ * slot, so an early response cannot free it under the sender.
+ */
+ refcount_set(&ctx->refcnt, 2);
+ ctx->responded = false;
+ ctx->msg_id = index;
+ ctx->error = -EINPROGRESS;
+
+ /* Publish the slot last, after it is fully initialised. */
bitmap_set(hwc->inflight_msg_res.map, index, 1);
spin_unlock_irqrestore(&r->lock, flags);
@@ -40,6 +53,13 @@ static void mana_hwc_put_msg_index(struct hw_channel_context *hwc, u16 msg_id)
up(&hwc->sema);
}
+static void hwc_ctx_put(struct hw_channel_context *hwc,
+ struct hwc_caller_ctx *ctx)
+{
+ if (refcount_dec_and_test(&ctx->refcnt))
+ mana_hwc_put_msg_index(hwc, ctx->msg_id);
+}
+
static int mana_hwc_verify_resp_msg(const struct hwc_caller_ctx *caller_ctx,
const struct gdma_resp_hdr *resp_msg,
u32 resp_len)
@@ -90,22 +110,35 @@ static void mana_hwc_handle_resp(struct hw_channel_context *hwc, u32 resp_len,
}
ctx = hwc->caller_ctx + msg_id;
- err = mana_hwc_verify_resp_msg(ctx, resp_msg, resp_len);
- if (err)
- goto out;
- ctx->status_code = resp_msg->status;
+ spin_lock(&ctx->lock);
- memcpy(ctx->output_buf, resp_msg, resp_len);
-out:
+ /* Honour a response only while the sender owns the slot (output_buf
+ * published) and has not already been answered; otherwise drop it as
+ * premature, stale or duplicate without touching the refcount.
+ */
+ if (!ctx->output_buf || ctx->responded) {
+ spin_unlock(&ctx->lock);
+ mana_hwc_post_rx_wqe(hwc->rxq, rx_req);
+ return;
+ }
+ ctx->responded = true;
+
+ err = mana_hwc_verify_resp_msg(ctx, resp_msg, resp_len);
+ if (!err) {
+ ctx->status_code = resp_msg->status;
+ memcpy(ctx->output_buf, resp_msg, resp_len);
+ }
ctx->error = err;
- /* Must post rx wqe before complete(), otherwise the next rx may
- * hit no_wqe error.
+ /* Post RX WQE before completing — the next response may arrive
+ * immediately and needs a posted buffer.
*/
mana_hwc_post_rx_wqe(hwc->rxq, rx_req);
-
complete(&ctx->comp_event);
+ spin_unlock(&ctx->lock);
+
+ hwc_ctx_put(hwc, ctx);
}
static void mana_hwc_init_event_handler(void *ctx, struct gdma_queue *q_self,
@@ -657,8 +690,10 @@ static int mana_hwc_test_channel(struct hw_channel_context *hwc, u16 q_depth,
if (!ctx)
return -ENOMEM;
- for (i = 0; i < q_depth; ++i)
+ for (i = 0; i < q_depth; ++i) {
+ spin_lock_init(&ctx[i].lock);
init_completion(&ctx[i].comp_event);
+ }
hwc->caller_ctx = ctx;
@@ -669,6 +704,12 @@ static int mana_hwc_establish_channel(struct gdma_context *gc, u16 *q_depth,
u32 *max_req_msg_size,
u32 *max_resp_msg_size)
{
+ /* mana_hwc_init_event_handler() fills the bootstrap fields from hard
+ * IRQ on GDMA_EQE_HWC_INIT_DATA and then signals hwc_init_eqe_comp on
+ * GDMA_EQE_HWC_INIT_DONE. The wait_for_completion() below pairs with
+ * that complete(), so every value stored before INIT_DONE is ordered
+ * against the reads that follow it here.
+ */
struct hw_channel_context *hwc = gc->hwc.driver_data;
struct gdma_queue *rq = hwc->rxq->gdma_wq;
struct gdma_queue *sq = hwc->txq->gdma_wq;
@@ -860,13 +901,19 @@ int mana_hwc_send_request(struct hw_channel_context *hwc, u32 req_len,
struct hwc_wq *txq = hwc->txq;
struct gdma_req_hdr *req_msg;
struct hwc_caller_ctx *ctx;
+ unsigned long flags;
+ bool drop_resp_ref;
u32 dest_vrcq = 0;
u32 dest_vrq = 0;
u32 command;
+ u32 status;
+ u32 wait_ms;
u16 msg_id;
int err;
- mana_hwc_get_msg_index(hwc, &msg_id);
+ err = mana_hwc_get_msg_index(hwc, &msg_id);
+ if (err)
+ return err;
tx_wr = &txq->msg_buf->reqs[msg_id];
@@ -878,8 +925,11 @@ int mana_hwc_send_request(struct hw_channel_context *hwc, u32 req_len,
}
ctx = hwc->caller_ctx + msg_id;
+
+ spin_lock_irqsave(&ctx->lock, flags);
ctx->output_buf = resp;
ctx->output_buflen = resp_len;
+ spin_unlock_irqrestore(&ctx->lock, flags);
req_msg = (struct gdma_req_hdr *)tx_wr->buf_va;
if (req)
@@ -895,43 +945,108 @@ int mana_hwc_send_request(struct hw_channel_context *hwc, u32 req_len,
dest_vrcq = hwc->pf_dest_vrcq_id;
}
+ /* The response-side reference (from get_msg_index) keeps the slot
+ * alive if hardware responds right after the doorbell.
+ */
err = mana_hwc_post_tx_wqe(txq, tx_wr, dest_vrq, dest_vrcq, false);
if (err) {
dev_err(hwc->dev, "HWC: Failed to post send WQE: %d\n", err);
goto out;
}
+ wait_ms = hwc->hwc_timeout;
if (!wait_for_completion_timeout(&ctx->comp_event,
- (msecs_to_jiffies(hwc->hwc_timeout)))) {
- if (hwc->hwc_timeout != 0)
+ msecs_to_jiffies(wait_ms))) {
+ /* Clear output_buf so a late response cannot write the caller's
+ * buffer, then check whether one already arrived
+ * (error != -EINPROGRESS).
+ */
+ spin_lock_irqsave(&ctx->lock, flags);
+ ctx->output_buf = NULL;
+ err = ctx->error;
+ status = ctx->status_code;
+ spin_unlock_irqrestore(&ctx->lock, flags);
+
+ if (err != -EINPROGRESS) {
+ /* A response raced in just after the timeout, so the
+ * hardware is alive: keep the channel and report what
+ * that response said rather than a timeout. It may
+ * itself be an error -- a malformed response leaves
+ * -EPROTO here -- which is still the answer to this
+ * command.
+ */
+ hwc_ctx_put(hwc, ctx);
+ goto check_status;
+ }
+
+ if (wait_ms != 0)
dev_err(hwc->dev, "Command 0x%x timed out: %u ms\n",
- command, hwc->hwc_timeout);
+ command, wait_ms);
- /* Reduce further waiting if HWC no response */
+ err = -ETIMEDOUT;
+
+ /* No-wait teardown (hwc_timeout == 0) is expected to expire;
+ * just release the slot so the next teardown command can reuse
+ * it.
+ */
+ if (wait_ms == 0)
+ goto out;
+
+ /* Genuine timeout: shorten later waits so subsequent commands
+ * fail fast instead of each draining the full timeout.
+ */
if (hwc->hwc_timeout > 1)
hwc->hwc_timeout = 1;
- err = -ETIMEDOUT;
+ /* Release the slot via out:; a late response no longer touches
+ * it, so the sender must drop the reference here.
+ */
goto out;
}
- if (ctx->error) {
- err = ctx->error;
- goto out;
- }
+ /* Clear output_buf and read the result under the lock; the slot may
+ * be reused after hwc_ctx_put().
+ */
+ spin_lock_irqsave(&ctx->lock, flags);
+ ctx->output_buf = NULL;
+ err = ctx->error;
+ status = ctx->status_code;
+ spin_unlock_irqrestore(&ctx->lock, flags);
+ hwc_ctx_put(hwc, ctx);
+
+check_status:
+ if (err)
+ goto done;
- if (ctx->status_code && ctx->status_code != GDMA_STATUS_MORE_ENTRIES) {
- if (ctx->status_code == GDMA_STATUS_CMD_UNSUPPORTED) {
+ if (status && status != GDMA_STATUS_MORE_ENTRIES) {
+ if (status == GDMA_STATUS_CMD_UNSUPPORTED) {
err = -EOPNOTSUPP;
- goto out;
+ goto done;
}
+
if (command != MANA_QUERY_PHY_STAT)
dev_err(hwc->dev, "Command 0x%x failed with status: 0x%x\n",
- command, ctx->status_code);
+ command, status);
err = -EPROTO;
- goto out;
+ goto done;
}
+
+ err = 0;
+ goto done;
out:
- mana_hwc_put_msg_index(hwc, msg_id);
+ /* Error, no-wait teardown, or timeout: drop the sender's and the
+ * response-side references. Latch ->responded so a racing response
+ * is a no-op, and only drop the response-side ref if it has not.
+ */
+ ctx = hwc->caller_ctx + msg_id;
+ spin_lock_irqsave(&ctx->lock, flags);
+ ctx->output_buf = NULL;
+ drop_resp_ref = !ctx->responded;
+ ctx->responded = true;
+ spin_unlock_irqrestore(&ctx->lock, flags);
+ if (drop_resp_ref)
+ refcount_dec(&ctx->refcnt);
+ hwc_ctx_put(hwc, ctx);
+done:
return err;
}
diff --git a/include/net/mana/hw_channel.h b/include/net/mana/hw_channel.h
index befa09674ce5614a955441e75e480a21aa8695fb..b377e221aa5c8183825e65ac0972c6b9f959004c 100644
--- a/include/net/mana/hw_channel.h
+++ b/include/net/mana/hw_channel.h
@@ -171,8 +171,24 @@ struct hwc_caller_ctx {
void *output_buf;
u32 output_buflen;
- u32 error; /* Linux error code */
+ int error; /* Linux error code (negative errno or 0) */
u32 status_code;
+
+ /* Protects output_buf against concurrent access from
+ * handle_resp() (CQ interrupt) and the sender timeout path.
+ */
+ spinlock_t lock;
+
+ /* Tracks sender + handle_resp ownership. The last put
+ * (refcount reaches 0) releases the bitmap slot.
+ */
+ refcount_t refcnt;
+ u16 msg_id;
+
+ /* Set by the first handle_resp(), or by the sender's timeout path,
+ * so a later or duplicate response is dropped.
+ */
+ bool responded;
};
struct hw_channel_context {
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH net-next v5 3/4] net: mana: support concurrent HWC requests
2026-09-08 3:51 [PATCH net-next v5 0/4] net: mana: concurrent HWC requests and dynamic queue depth Long Li
2026-09-08 3:51 ` [PATCH net-next v5 1/4] net: mana: track when the HWC has been handed to the PF Long Li
2026-09-08 3:51 ` [PATCH net-next v5 2/4] net: mana: give each HWC message slot its own completion state Long Li
@ 2026-09-08 3:51 ` Long Li
2026-09-09 3:52 ` sashiko-bot
2026-09-08 3:51 ` [PATCH net-next v5 4/4] net: mana: add dynamic HWC queue depth with reinit path Long Li
3 siblings, 1 reply; 9+ messages in thread
From: Long Li @ 2026-09-08 3:51 UTC (permalink / raw)
To: Long Li, 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
Serialize SQ posting and protect HWC lookup and sender accounting with
hwc_lock. Teardown stops admission, force-completes requests and drains
senders before destroying CQ, TXQ and RXQ. Preserve cancellation errors;
return -EPROTO instead of success for a response accepted before posting.
Bound FIFO slot admission with down_timeout(), independently of the
response wait. Admission expiry returns -ETIMEDOUT even for contention,
so existing callers may reset a responsive channel.
Quarantine posted requests on timeout, including zero-timeout cleanup,
until a response or teardown releases their slot. This closes the
late-response reuse window present at depth one. Keep depth one here.
Existing lifecycle and timeout-field races are not resolved here.
Signed-off-by: Long Li <longli@microsoft.com>
---
Changes in v5 (v4 -> v5):
- Return -EPROTO rather than success if a response precedes submission;
preserve nonzero cancellation errors and existing reference cleanup.
- Document separate FIFO admission and response budgets, including
contention-triggered recovery and zero-timeout quarantine.
- Correct and shorten locking, publication and teardown comments.
Changes in v4 (standalone net-next rework after the v3 split):
- Rework former patch 6/7 as patch 3/4 on the new ownership preparation.
- Use down_timeout() for semaphore admission and keep the slot until a
timed-out request's response arrives or teardown releases it.
- Retain quarantine for zero-timeout cleanup; do not use the former
channel-wide timeout latch.
- Serialize cancellation with SQ posting, preserve already recorded
results during teardown, and retain guarded sender draining.
Changes in v3 (historical net fixes-only posting):
- Defer the concurrency feature from the net submission.
- Related slot-timeout ownership work was posted separately as fix 6/6.
Changes in v2 (v1 -> v2, former patch 6/7):
- Replace atomic sender accounting with an hwc_lock-protected count and
wait_event_lock_irq() drain to fence the final sender's wakeup.
- Move force-completion and draining before teardown/FLR failure exits.
v1:
- Introduce waitqueue/bitmap admission, per-slot synchronization,
posting serialization and channel teardown gating in patch 6/7.
.../net/ethernet/microsoft/mana/gdma_main.c | 38 ++-
.../net/ethernet/microsoft/mana/hw_channel.c | 229 ++++++++++++++----
include/net/mana/gdma.h | 9 +
include/net/mana/hw_channel.h | 16 ++
4 files changed, 239 insertions(+), 53 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c
index 8d86de0a334b21d77ab6bfb578917c56404bc856..eb88bae2b14d86de33e79eb597a076a7d6e54436 100644
--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
+++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
@@ -162,6 +162,8 @@ static int mana_gd_init_registers(struct pci_dev *pdev)
bool mana_need_log(struct gdma_context *gc, int err)
{
struct hw_channel_context *hwc;
+ bool need_log = true;
+ unsigned long flags;
if (err != -ETIMEDOUT)
return true;
@@ -169,11 +171,13 @@ bool mana_need_log(struct gdma_context *gc, int err)
if (!gc)
return true;
+ spin_lock_irqsave(&gc->hwc_lock, flags);
hwc = gc->hwc.driver_data;
if (hwc && hwc->hwc_timeout == 0)
- return false;
+ need_log = false;
+ spin_unlock_irqrestore(&gc->hwc_lock, flags);
- return true;
+ return need_log;
}
static int mana_gd_query_max_resources(struct pci_dev *pdev)
@@ -391,9 +395,27 @@ static int mana_gd_detect_devices(struct pci_dev *pdev)
int mana_gd_send_request(struct gdma_context *gc, u32 req_len, const void *req,
u32 resp_len, void *resp)
{
- struct hw_channel_context *hwc = gc->hwc.driver_data;
+ struct hw_channel_context *hwc;
+ unsigned long flags;
+ int err;
+
+ spin_lock_irqsave(&gc->hwc_lock, flags);
+ hwc = gc->hwc.driver_data;
+ if (!hwc) {
+ spin_unlock_irqrestore(&gc->hwc_lock, flags);
+ return -ENODEV;
+ }
+ hwc->active_senders++;
+ spin_unlock_irqrestore(&gc->hwc_lock, flags);
+
+ err = mana_hwc_send_request(hwc, req_len, req, resp_len, resp);
+
+ spin_lock_irqsave(&gc->hwc_lock, flags);
+ if (--hwc->active_senders == 0)
+ wake_up(&gc->hwc_drain_waitq);
+ spin_unlock_irqrestore(&gc->hwc_lock, flags);
- return mana_hwc_send_request(hwc, req_len, req, resp_len, resp);
+ return err;
}
EXPORT_SYMBOL_NS(mana_gd_send_request, "NET_MANA");
@@ -714,6 +736,7 @@ static void mana_serv_reset(struct pci_dev *pdev)
{
struct gdma_context *gc = pci_get_drvdata(pdev);
struct hw_channel_context *hwc;
+ unsigned long flags;
int ret;
if (!gc) {
@@ -723,14 +746,17 @@ static void mana_serv_reset(struct pci_dev *pdev)
return;
}
+ spin_lock_irqsave(&gc->hwc_lock, flags);
hwc = gc->hwc.driver_data;
if (!hwc) {
+ spin_unlock_irqrestore(&gc->hwc_lock, flags);
dev_err(&pdev->dev, "MANA service: no HWC\n");
goto out;
}
/* HWC is not responding in this case, so don't wait */
hwc->hwc_timeout = 0;
+ spin_unlock_irqrestore(&gc->hwc_lock, flags);
dev_info(&pdev->dev, "MANA reset cycle start\n");
@@ -1337,6 +1363,7 @@ static int mana_gd_create_dma_region(struct gdma_dev *gd,
if (gmi->nr_pages == 0 && !MANA_PAGE_ALIGNED(gmi->virt_addr))
return -EINVAL;
+ /* The caller must keep the HWC alive throughout queue creation. */
hwc = gc->hwc.driver_data;
req_msg_size = struct_size(req, page_addr_list, num_page);
if (req_msg_size > hwc->max_req_msg_size)
@@ -1542,7 +1569,9 @@ int mana_gd_verify_vf_version(struct pci_dev *pdev)
struct hw_channel_context *hwc;
int err;
+ /* The setup caller must exclude concurrent HWC teardown. */
hwc = gc->hwc.driver_data;
+
mana_gd_init_req_hdr(&req.hdr, GDMA_VERIFY_VF_DRIVER_VERSION,
sizeof(req), sizeof(resp));
@@ -2536,6 +2565,7 @@ static int mana_gd_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
mutex_init(&gc->eq_test_event_mutex);
mutex_init(&gc->gic_mutex);
+ spin_lock_init(&gc->hwc_lock);
pci_set_drvdata(pdev, gc);
gc->bar0_pa = pci_resource_start(pdev, 0);
gc->bar0_size = pci_resource_len(pdev, 0);
diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c
index 6605e7a9c481bcb11c95f90f627b7c422b62cc28..a4f7346d285f740c40f4f63c20348e30531f1435 100644
--- a/drivers/net/ethernet/microsoft/mana/hw_channel.c
+++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c
@@ -6,7 +6,6 @@
#include <net/mana/hw_channel.h>
#include <linux/vmalloc.h>
-/* Acquire a free inflight message slot, waiting for one if all are in use. */
static int mana_hwc_get_msg_index(struct hw_channel_context *hwc, u16 *msg_id)
{
struct gdma_resource *r = &hwc->inflight_msg_res;
@@ -14,12 +13,30 @@ static int mana_hwc_get_msg_index(struct hw_channel_context *hwc, u16 *msg_id)
unsigned long flags;
u32 index;
- down(&hwc->sema);
+ /* FIFO slot admission has a separate budget from the response wait.
+ * Expiry reports -ETIMEDOUT even while earlier requests make progress,
+ * so callers may initiate recovery on contention alone.
+ */
+ if (down_timeout(&hwc->sema, msecs_to_jiffies(hwc->hwc_timeout)))
+ return -ETIMEDOUT;
spin_lock_irqsave(&r->lock, flags);
- index = find_first_zero_bit(hwc->inflight_msg_res.map,
- hwc->inflight_msg_res.size);
+ if (!hwc->channel_up) {
+ spin_unlock_irqrestore(&r->lock, flags);
+ up(&hwc->sema);
+ return -ENODEV;
+ }
+
+ /* The semaphore admits at most r->size holders at a time, so a slot
+ * acquired above always has a free bit waiting for it here.
+ */
+ index = find_first_zero_bit(r->map, r->size);
+ if (WARN_ON_ONCE(index >= r->size)) {
+ spin_unlock_irqrestore(&r->lock, flags);
+ up(&hwc->sema);
+ return -EIO;
+ }
ctx = &hwc->caller_ctx[index];
reinit_completion(&ctx->comp_event);
@@ -28,11 +45,12 @@ static int mana_hwc_get_msg_index(struct hw_channel_context *hwc, u16 *msg_id)
*/
refcount_set(&ctx->refcnt, 2);
ctx->responded = false;
+ ctx->resp_pending = true;
ctx->msg_id = index;
ctx->error = -EINPROGRESS;
/* Publish the slot last, after it is fully initialised. */
- bitmap_set(hwc->inflight_msg_res.map, index, 1);
+ bitmap_set(r->map, index, 1);
spin_unlock_irqrestore(&r->lock, flags);
@@ -101,6 +119,7 @@ static void mana_hwc_handle_resp(struct hw_channel_context *hwc, u32 resp_len,
{
const struct gdma_resp_hdr *resp_msg = rx_req->buf_va;
struct hwc_caller_ctx *ctx;
+ bool release;
int err;
if (!test_bit(msg_id, hwc->inflight_msg_res.map)) {
@@ -113,13 +132,30 @@ static void mana_hwc_handle_resp(struct hw_channel_context *hwc, u32 resp_len,
spin_lock(&ctx->lock);
- /* Honour a response only while the sender owns the slot (output_buf
- * published) and has not already been answered; otherwise drop it as
- * premature, stale or duplicate without touching the refcount.
+ /* The sender has not published its buffer yet, so nothing asked for
+ * this response. Keep the slot reserved and drop the message.
+ */
+ if (!ctx->output_buf && !ctx->responded) {
+ spin_unlock(&ctx->lock);
+ mana_hwc_post_rx_wqe(hwc->rxq, rx_req);
+ return;
+ }
+
+ /* Take the response-side reference away exactly once: releasing it
+ * is what frees a slot whose sender has already given up.
*/
- if (!ctx->output_buf || ctx->responded) {
+ release = ctx->resp_pending;
+ ctx->resp_pending = false;
+
+ if (ctx->responded) {
+ /* The sender timed out and abandoned the slot, or a response
+ * was already applied. Consume this one without writing
+ * anything, then release the slot it was holding.
+ */
spin_unlock(&ctx->lock);
mana_hwc_post_rx_wqe(hwc->rxq, rx_req);
+ if (release)
+ hwc_ctx_put(hwc, ctx);
return;
}
ctx->responded = true;
@@ -138,7 +174,8 @@ static void mana_hwc_handle_resp(struct hw_channel_context *hwc, u32 resp_len,
complete(&ctx->comp_event);
spin_unlock(&ctx->lock);
- hwc_ctx_put(hwc, ctx);
+ if (release)
+ hwc_ctx_put(hwc, ctx);
}
static void mana_hwc_init_event_handler(void *ctx, struct gdma_queue *q_self,
@@ -593,6 +630,7 @@ static int mana_hwc_create_wq(struct hw_channel_context *hwc,
hwc_wq->gdma_wq = queue;
hwc_wq->queue_depth = q_depth;
hwc_wq->hwc_cq = hwc_cq;
+ spin_lock_init(&hwc_wq->lock);
err = mana_hwc_alloc_dma_buf(hwc, q_depth, max_msg_size,
&hwc_wq->msg_buf);
@@ -610,7 +648,7 @@ static int mana_hwc_create_wq(struct hw_channel_context *hwc,
return err;
}
-static int mana_hwc_post_tx_wqe(const struct hwc_wq *hwc_txq,
+static int mana_hwc_post_tx_wqe(struct hwc_wq *hwc_txq,
struct hwc_work_request *req,
u32 dest_virt_rq_id, u32 dest_virt_rcq_id,
bool dest_pf)
@@ -649,7 +687,10 @@ static int mana_hwc_post_tx_wqe(const struct hwc_wq *hwc_txq,
req->wqe_req.inline_oob_data = tx_oob;
req->wqe_req.client_data_unit = 0;
+ spin_lock(&hwc_txq->lock);
err = mana_gd_post_and_ring(hwc_txq->gdma_wq, &req->wqe_req, NULL);
+ spin_unlock(&hwc_txq->lock);
+
if (err)
dev_err(dev, "Failed to post WQE on HWC SQ: %d\n", err);
return err;
@@ -675,6 +716,7 @@ static int mana_hwc_test_channel(struct hw_channel_context *hwc, u16 q_depth,
struct hwc_wq *hwc_rxq = hwc->rxq;
struct hwc_work_request *req;
struct hwc_caller_ctx *ctx;
+ unsigned long flags;
int err;
int i;
@@ -697,7 +739,19 @@ static int mana_hwc_test_channel(struct hw_channel_context *hwc, u16 q_depth,
hwc->caller_ctx = ctx;
- return mana_gd_test_eq(gc, hwc->cq->gdma_eq);
+ /* Enable admission for the test EQ request. */
+ spin_lock_irqsave(&hwc->inflight_msg_res.lock, flags);
+ hwc->channel_up = true;
+ spin_unlock_irqrestore(&hwc->inflight_msg_res.lock, flags);
+
+ err = mana_gd_test_eq(gc, hwc->cq->gdma_eq);
+ if (err) {
+ spin_lock_irqsave(&hwc->inflight_msg_res.lock, flags);
+ hwc->channel_up = false;
+ spin_unlock_irqrestore(&hwc->inflight_msg_res.lock, flags);
+ }
+
+ return err;
}
static int mana_hwc_establish_channel(struct gdma_context *gc, u16 *q_depth,
@@ -797,6 +851,7 @@ int mana_hwc_create_channel(struct gdma_context *gc)
u32 max_req_msg_size, max_resp_msg_size;
struct gdma_dev *gd = &gc->hwc;
struct hw_channel_context *hwc;
+ unsigned long flags;
u16 q_depth_max;
int err;
@@ -805,10 +860,11 @@ int mana_hwc_create_channel(struct gdma_context *gc)
return -ENOMEM;
gd->gdma_context = gc;
- gd->driver_data = hwc;
hwc->gdma_dev = gd;
hwc->dev = gc->dev;
hwc->hwc_timeout = HW_CHANNEL_WAIT_RESOURCE_TIMEOUT_MS;
+ hwc->active_senders = 0;
+ init_waitqueue_head(&gc->hwc_drain_waitq);
/* HWC's instance number is always 0. */
gd->dev_id.as_uint32 = 0;
@@ -817,6 +873,11 @@ int mana_hwc_create_channel(struct gdma_context *gc)
gd->pdid = INVALID_PDID;
gd->doorbell = INVALID_DOORBELL;
+ /* Publish for setup; queue initialization below must precede senders. */
+ spin_lock_irqsave(&gc->hwc_lock, flags);
+ gc->hwc.driver_data = hwc;
+ spin_unlock_irqrestore(&gc->hwc_lock, flags);
+
/* mana_hwc_init_queues() only creates the required data structures,
* and doesn't touch the HWC device.
*/
@@ -851,11 +912,60 @@ int mana_hwc_create_channel(struct gdma_context *gc)
void mana_hwc_destroy_channel(struct gdma_context *gc)
{
+ /* The caller must serialize setup and teardown operations. */
struct hw_channel_context *hwc = gc->hwc.driver_data;
+ unsigned long flags;
if (!hwc)
return;
+ /* Nonzero num_inflight_msg means queue initialization completed. */
+ if (hwc->num_inflight_msg) {
+ spin_lock_irqsave(&hwc->inflight_msg_res.lock, flags);
+ hwc->channel_up = false;
+ spin_unlock_irqrestore(&hwc->inflight_msg_res.lock, flags);
+ }
+
+ /* Block new mana_gd_send_request() references before draining. */
+ spin_lock_irqsave(&gc->hwc_lock, flags);
+ gc->hwc.driver_data = NULL;
+ spin_unlock_irqrestore(&gc->hwc_lock, flags);
+
+ /* Complete occupied slots and drop pending response-side references. */
+ if (hwc->caller_ctx) {
+ struct hwc_caller_ctx *ctx;
+ bool drop_resp_ref;
+ int i;
+
+ for (i = 0; i < hwc->num_inflight_msg; i++) {
+ if (!test_bit(i, hwc->inflight_msg_res.map))
+ continue;
+
+ ctx = &hwc->caller_ctx[i];
+
+ spin_lock_irqsave(&ctx->lock, flags);
+ /* Preserve an already recorded result. */
+ if (!ctx->responded)
+ ctx->error = -ENODEV;
+ drop_resp_ref = ctx->resp_pending;
+ ctx->resp_pending = false;
+ ctx->responded = true;
+ complete(&ctx->comp_event);
+ spin_unlock_irqrestore(&ctx->lock, flags);
+
+ if (drop_resp_ref)
+ hwc_ctx_put(hwc, ctx);
+ }
+ }
+
+ /* Pair with the last sender's wakeup under hwc_lock, so it finishes
+ * accessing gc before the drain returns.
+ */
+ spin_lock_irq(&gc->hwc_lock);
+ wait_event_lock_irq(gc->hwc_drain_waitq,
+ hwc->active_senders == 0, gc->hwc_lock);
+ spin_unlock_irq(&gc->hwc_lock);
+
if (hwc->setup_active) {
if (!mana_smc_teardown_hwc(&gc->shm_channel, false))
hwc->setup_active = false;
@@ -864,14 +974,28 @@ void mana_hwc_destroy_channel(struct gdma_context *gc)
}
gc->max_num_cqs = 0;
+ /* Deregister the HWC EQ before freeing the work queues. */
+ if (hwc->cq)
+ mana_hwc_destroy_cq(hwc->gdma_dev->gdma_context, hwc->cq);
+
if (hwc->txq)
mana_hwc_destroy_wq(hwc, hwc->txq);
if (hwc->rxq)
mana_hwc_destroy_wq(hwc, hwc->rxq);
- if (hwc->cq)
- mana_hwc_destroy_cq(hwc->gdma_dev->gdma_context, hwc->cq);
+ if (hwc->caller_ctx) {
+ struct hwc_caller_ctx *ctx;
+ int i;
+
+ for (i = 0; i < hwc->num_inflight_msg; i++) {
+ if (!test_bit(i, hwc->inflight_msg_res.map))
+ continue;
+
+ ctx = &hwc->caller_ctx[i];
+ hwc_ctx_put(hwc, ctx);
+ }
+ }
kfree(hwc->caller_ctx);
hwc->caller_ctx = NULL;
@@ -886,7 +1010,6 @@ void mana_hwc_destroy_channel(struct gdma_context *gc)
hwc->hwc_timeout = 0;
kfree(hwc);
- gc->hwc.driver_data = NULL;
gc->hwc.gdma_context = NULL;
vfree(gc->cq_table);
@@ -903,6 +1026,8 @@ int mana_hwc_send_request(struct hw_channel_context *hwc, u32 req_len,
struct hwc_caller_ctx *ctx;
unsigned long flags;
bool drop_resp_ref;
+ bool abandoned = false;
+ bool cancelled;
u32 dest_vrcq = 0;
u32 dest_vrq = 0;
u32 command;
@@ -945,10 +1070,21 @@ int mana_hwc_send_request(struct hw_channel_context *hwc, u32 req_len,
dest_vrcq = hwc->pf_dest_vrcq_id;
}
- /* The response-side reference (from get_msg_index) keeps the slot
- * alive if hardware responds right after the doorbell.
+ /* Serialize cancellation with submission. An unsubmitted request
+ * cannot succeed, even if an unsolicited response was accepted.
*/
- err = mana_hwc_post_tx_wqe(txq, tx_wr, dest_vrq, dest_vrcq, false);
+ spin_lock_irqsave(&ctx->lock, flags);
+ cancelled = ctx->responded;
+ if (cancelled)
+ err = ctx->error ?: -EPROTO;
+ else
+ err = mana_hwc_post_tx_wqe(txq, tx_wr, dest_vrq, dest_vrcq,
+ false);
+ spin_unlock_irqrestore(&ctx->lock, flags);
+
+ if (cancelled)
+ goto out;
+
if (err) {
dev_err(hwc->dev, "HWC: Failed to post send WQE: %d\n", err);
goto out;
@@ -965,43 +1101,40 @@ int mana_hwc_send_request(struct hw_channel_context *hwc, u32 req_len,
ctx->output_buf = NULL;
err = ctx->error;
status = ctx->status_code;
+ if (err == -EINPROGRESS) {
+ /* Publish abandonment with buffer withdrawal so a late
+ * response can reclaim the slot. Keep its reference.
+ */
+ ctx->responded = true;
+ abandoned = true;
+ }
spin_unlock_irqrestore(&ctx->lock, flags);
- if (err != -EINPROGRESS) {
- /* A response raced in just after the timeout, so the
- * hardware is alive: keep the channel and report what
- * that response said rather than a timeout. It may
- * itself be an error -- a malformed response leaves
- * -EPROTO here -- which is still the answer to this
- * command.
- */
+ if (!abandoned) {
+ /* A completion won the race with timeout; use its result. */
hwc_ctx_put(hwc, ctx);
goto check_status;
}
- if (wait_ms != 0)
+ if (wait_ms != 0) {
dev_err(hwc->dev, "Command 0x%x timed out: %u ms\n",
command, wait_ms);
- err = -ETIMEDOUT;
-
- /* No-wait teardown (hwc_timeout == 0) is expected to expire;
- * just release the slot so the next teardown command can reuse
- * it.
- */
- if (wait_ms == 0)
- goto out;
+ /* Genuine timeout: shorten later waits so subsequent
+ * commands fail fast instead of each draining the
+ * full timeout.
+ */
+ if (hwc->hwc_timeout > 1)
+ hwc->hwc_timeout = 1;
+ }
- /* Genuine timeout: shorten later waits so subsequent commands
- * fail fast instead of each draining the full timeout.
- */
- if (hwc->hwc_timeout > 1)
- hwc->hwc_timeout = 1;
+ err = -ETIMEDOUT;
- /* Release the slot via out:; a late response no longer touches
- * it, so the sender must drop the reference here.
+ /* Drop only the sender's reference; the response-side one is
+ * what keeps the slot reserved.
*/
- goto out;
+ hwc_ctx_put(hwc, ctx);
+ goto done;
}
/* Clear output_buf and read the result under the lock; the slot may
@@ -1034,14 +1167,12 @@ int mana_hwc_send_request(struct hw_channel_context *hwc, u32 req_len,
err = 0;
goto done;
out:
- /* Error, no-wait teardown, or timeout: drop the sender's and the
- * response-side references. Latch ->responded so a racing response
- * is a no-op, and only drop the response-side ref if it has not.
- */
+ /* Release any references still held by this unsubmitted request. */
ctx = hwc->caller_ctx + msg_id;
spin_lock_irqsave(&ctx->lock, flags);
ctx->output_buf = NULL;
- drop_resp_ref = !ctx->responded;
+ drop_resp_ref = ctx->resp_pending;
+ ctx->resp_pending = false;
ctx->responded = true;
spin_unlock_irqrestore(&ctx->lock, flags);
if (drop_resp_ref)
diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h
index 308950f9b54b0485bac66b80d63e257eaf5f787e..571a533e62e64790f9000d42ab0e833fe36ccff6 100644
--- a/include/net/mana/gdma.h
+++ b/include/net/mana/gdma.h
@@ -468,6 +468,15 @@ struct gdma_context {
/* Hardware communication channel (HWC) */
struct gdma_dev hwc;
+ /* Sender drain; the final wakeup runs under hwc_lock. */
+ wait_queue_head_t hwc_drain_waitq;
+
+ /* Protects HWC publication, sender references, and short accesses in
+ * mana_need_log()/mana_serv_reset(). Setup and DMA-region readers
+ * still require lifecycle ordering. Not all timeout writers use it.
+ */
+ spinlock_t hwc_lock;
+
/* Azure network adapter */
struct gdma_dev mana;
diff --git a/include/net/mana/hw_channel.h b/include/net/mana/hw_channel.h
index b377e221aa5c8183825e65ac0972c6b9f959004c..fba27d8620a388a41ae7ddd3bf2b4792f7beec3d 100644
--- a/include/net/mana/hw_channel.h
+++ b/include/net/mana/hw_channel.h
@@ -164,6 +164,9 @@ struct hwc_wq {
u16 queue_depth;
struct hwc_cq *hwc_cq;
+
+ /* Serializes SQ posting; unused for the RQ. */
+ spinlock_t lock;
};
struct hwc_caller_ctx {
@@ -189,6 +192,9 @@ struct hwc_caller_ctx {
* so a later or duplicate response is dropped.
*/
bool responded;
+
+ /* Response-side reference outstanding; protected by lock. */
+ bool resp_pending;
};
struct hw_channel_context {
@@ -196,6 +202,7 @@ struct hw_channel_context {
struct device *dev;
u16 num_inflight_msg;
+
u32 max_req_msg_size;
u16 hwc_init_q_depth_max;
@@ -208,6 +215,9 @@ struct hw_channel_context {
struct hwc_wq *txq;
struct hwc_cq *cq;
+ /* Admission permits. Timed-out requests retain theirs until a
+ * response or teardown releases the slot.
+ */
struct semaphore sema;
struct gdma_resource inflight_msg_res;
@@ -215,9 +225,15 @@ struct hw_channel_context {
u32 pf_dest_vrcq_id;
u32 hwc_timeout;
+ /* Checked after slot acquisition; cleared on teardown to reject sends. */
+ bool channel_up;
+
/* PF may own the queue mappings; state lasts only for this context. */
bool setup_active;
+ /* mana_gd_send_request() callers, including waiters; under hwc_lock. */
+ unsigned int active_senders;
+
struct hwc_caller_ctx *caller_ctx;
};
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH net-next v5 4/4] net: mana: add dynamic HWC queue depth with reinit path
2026-09-08 3:51 [PATCH net-next v5 0/4] net: mana: concurrent HWC requests and dynamic queue depth Long Li
` (2 preceding siblings ...)
2026-09-08 3:51 ` [PATCH net-next v5 3/4] net: mana: support concurrent HWC requests Long Li
@ 2026-09-08 3:51 ` Long Li
2026-09-09 3:52 ` sashiko-bot
3 siblings, 1 reply; 9+ messages in thread
From: Long Li @ 2026-09-08 3:51 UTC (permalink / raw)
To: Long Li, 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
Rebuild depth-one HWC queues at the advertised maximum, capped at 128.
Preserve the 24-bit depth in u32, bound allocations and round message
buffers to a power of two. Advertise support in the driver-version request.
Keep bootstrap queues for incompatible initial dimensions. A failed
teardown or rebuild instead retries teardown of any submitted queues
before restoring depth one; never test queues after an indeterminate
destroy result. Abort creation if that teardown also fails. Reject
incompatible message sizes after bootstrap fallback, and guard CQ
unpublishing when a failed establish left no table.
Reset dimensions and routing IDs before each establish. The missing-
doorbell guard stops progression to the channel test on every attempt,
but does not cover BAR bounds or earlier IRQ rearming.
Signed-off-by: Long Li <longli@microsoft.com>
---
Changes in v5 (v4 -> v5):
- Handle CQ cleanup when establishment failed before allocating cq_table.
- Route initial destroy failures through confirmed teardown and fresh
bootstrap reconstruction, rather than testing possibly destroyed queues.
- Share the teardown gate with failed larger-depth establishment.
- Correct dimension-validation and doorbell-guard scope descriptions;
shorten comments without broadening validation policy.
Changes in v4 (standalone net-next rework after the v3 split):
- Rework former patch 7/7 as patch 4/4 without the net fixes as prerequisites.
- Cap depth at 128 and round message-buffer allocations to a power of two.
- Require bootstrap message sizes before increasing depth, and validate
the rebuilt channel's report against its allocations.
- Reset dimensions/routing IDs on each establish and reject a missing
doorbell after the handshake.
- Retry teardown before restoring depth one after failed re-establishment.
- Retain u32 storage of the 24-bit depth and allocation-overflow checks.
Changes in v3 (historical net fixes-only posting):
- Defer the dynamic-depth feature; it was not included in the net series.
Changes in v2 (v1 -> v2):
- Retain dynamic-depth patch 7/7 on the revised concurrency prerequisites;
rebase the series onto net-next.
v1:
- Introduce depth-one bootstrap, rebuild at the reported depth, capability
advertisement, allocation checks and bootstrap fallback in patch 7/7.
.../net/ethernet/microsoft/mana/gdma_main.c | 8 +-
.../net/ethernet/microsoft/mana/hw_channel.c | 197 +++++++++++++++++-
include/net/mana/gdma.h | 4 +
include/net/mana/hw_channel.h | 9 +-
4 files changed, 209 insertions(+), 9 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c
index eb88bae2b14d86de33e79eb597a076a7d6e54436..78424e1d884f18e6b216d627fc80e77630b0b350 100644
--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
+++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
@@ -1260,15 +1260,17 @@ static void mana_gd_create_cq(const struct gdma_queue_spec *spec,
static void mana_gd_destroy_cq(struct gdma_context *gc,
struct gdma_queue *queue)
{
+ struct gdma_queue **cq_table = READ_ONCE(gc->cq_table);
u32 id = queue->id;
- if (id >= gc->max_num_cqs)
+ /* HWC re-establishment can fail before allocating the CQ table. */
+ if (!cq_table || id >= gc->max_num_cqs)
return;
- if (!gc->cq_table[id])
+ if (!cq_table[id])
return;
- gc->cq_table[id] = NULL;
+ cq_table[id] = NULL;
}
int mana_gd_create_hwc_queue(struct gdma_dev *gd,
diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c
index a4f7346d285f740c40f4f63c20348e30531f1435..750ee8f72999489a42b2bf6e8561480e6ce07133 100644
--- a/drivers/net/ethernet/microsoft/mana/hw_channel.c
+++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c
@@ -216,7 +216,12 @@ static void mana_hwc_init_event_handler(void *ctx, struct gdma_queue *q_self,
break;
case HWC_INIT_DATA_QUEUE_DEPTH:
- hwc->hwc_init_q_depth_max = (u16)val;
+ /* HWC_INIT_DATA_QUEUE_DEPTH is a 24-bit field. Keep
+ * the full device-reported value here; it is clamped
+ * and validated in mana_hwc_create_channel() rather
+ * than silently truncated to u16.
+ */
+ hwc->hwc_init_q_depth_max = val;
break;
case HWC_INIT_DATA_MAX_REQUEST:
@@ -546,7 +551,11 @@ static int mana_hwc_alloc_dma_buf(struct hw_channel_context *hwc, u16 q_depth,
dma_buf->num_reqs = q_depth;
- buf_size = MANA_PAGE_ALIGN(q_depth * max_msg_size);
+ /* mana_gd_alloc_memory() only accepts a power-of-two length, as
+ * already assumed for the EQ and CQ rings above. The slots are
+ * carved from the head of the buffer, so any tail is unused.
+ */
+ buf_size = roundup_pow_of_two(MANA_PAGE_ALIGN(q_depth * max_msg_size));
gmi = &dma_buf->mem_info;
err = mana_gd_alloc_memory(gc, buf_size, gmi, false);
@@ -754,7 +763,7 @@ static int mana_hwc_test_channel(struct hw_channel_context *hwc, u16 q_depth,
return err;
}
-static int mana_hwc_establish_channel(struct gdma_context *gc, u16 *q_depth,
+static int mana_hwc_establish_channel(struct gdma_context *gc, u32 *q_depth,
u32 *max_req_msg_size,
u32 *max_resp_msg_size)
{
@@ -771,6 +780,15 @@ static int mana_hwc_establish_channel(struct gdma_context *gc, u16 *q_depth,
struct gdma_queue *cq = hwc->cq->gdma_cq;
int err;
+ /* Do not reuse dimensions or routing IDs from a previous establish. */
+ hwc->hwc_init_q_depth_max = 0;
+ hwc->hwc_init_max_req_msg_size = 0;
+ hwc->hwc_init_max_resp_msg_size = 0;
+ gc->hwc.doorbell = INVALID_DOORBELL;
+ gc->hwc.pdid = INVALID_PDID;
+ hwc->pf_dest_vrq_id = 0;
+ hwc->pf_dest_vrcq_id = 0;
+
init_completion(&hwc->hwc_init_eqe_comp);
err = mana_smc_setup_hwc(&gc->shm_channel, false,
@@ -789,6 +807,14 @@ static int mana_hwc_establish_channel(struct gdma_context *gc, u16 *q_depth,
*max_req_msg_size = hwc->hwc_init_max_req_msg_size;
*max_resp_msg_size = hwc->hwc_init_max_resp_msg_size;
+ /* Reject a missing doorbell before the channel test. This neither
+ * validates its BAR range nor protects earlier IRQ rearming.
+ */
+ if (gc->hwc.doorbell == INVALID_DOORBELL) {
+ dev_err(hwc->dev, "HWC: no doorbell in init data\n");
+ return -EPROTO;
+ }
+
/* Both were set in mana_hwc_init_event_handler(). */
if (WARN_ON(cq->id >= gc->max_num_cqs))
return -EPROTO;
@@ -807,6 +833,12 @@ static int mana_hwc_init_queues(struct hw_channel_context *hwc, u16 q_depth,
{
int err;
+ /* CQ depth is q_depth * 2 (SQ + RQ) passed as u16 to create_cq.
+ * Cap to prevent u16 truncation.
+ */
+ if (q_depth > U16_MAX / 2)
+ q_depth = U16_MAX / 2;
+
err = mana_hwc_init_inflight_msg(hwc, q_depth);
if (err)
return err;
@@ -846,13 +878,44 @@ static int mana_hwc_init_queues(struct hw_channel_context *hwc, u16 q_depth,
return err;
}
+/* Bring-up only: requires no senders or concurrent lifecycle operations.
+ * This helper does not unpublish the HWC or drain senders.
+ */
+static void mana_hwc_destroy_queues(struct hw_channel_context *hwc)
+{
+ struct gdma_context *gc = hwc->gdma_dev->gdma_context;
+
+ /* The CQ helper deregisters the HWC EQ before returning. */
+ if (hwc->cq) {
+ mana_hwc_destroy_cq(gc, hwc->cq);
+ hwc->cq = NULL;
+ }
+
+ kfree(hwc->caller_ctx);
+ hwc->caller_ctx = NULL;
+
+ if (hwc->txq) {
+ mana_hwc_destroy_wq(hwc, hwc->txq);
+ hwc->txq = NULL;
+ }
+
+ if (hwc->rxq) {
+ mana_hwc_destroy_wq(hwc, hwc->rxq);
+ hwc->rxq = NULL;
+ }
+
+ mana_gd_free_res_map(&hwc->inflight_msg_res);
+ hwc->num_inflight_msg = 0;
+}
+
int mana_hwc_create_channel(struct gdma_context *gc)
{
u32 max_req_msg_size, max_resp_msg_size;
struct gdma_dev *gd = &gc->hwc;
struct hw_channel_context *hwc;
+ struct gdma_queue **old_cq_table;
unsigned long flags;
- u16 q_depth_max;
+ u32 q_depth_max;
int err;
hwc = kzalloc_obj(*hwc);
@@ -896,8 +959,132 @@ int mana_hwc_create_channel(struct gdma_context *gc)
goto out;
}
+ if (q_depth_max > HW_CHANNEL_VF_BOOTSTRAP_QUEUE_DEPTH) {
+ /* Bound DMA allocations before using the 24-bit depth. */
+ if (q_depth_max > HW_CHANNEL_MAX_QUEUE_DEPTH)
+ q_depth_max = HW_CHANNEL_MAX_QUEUE_DEPTH;
+
+ /* Keep bootstrap message sizes for mandatory commands.
+ * Incompatible reports skip rebuilding, not channel creation.
+ */
+ if (max_req_msg_size != HW_CHANNEL_MAX_REQUEST_SIZE ||
+ max_resp_msg_size != HW_CHANNEL_MAX_RESPONSE_SIZE ||
+ (u64)q_depth_max * max_req_msg_size >
+ U32_MAX - MANA_PAGE_SIZE ||
+ (u64)q_depth_max * max_resp_msg_size >
+ U32_MAX - MANA_PAGE_SIZE) {
+ dev_err(hwc->dev,
+ "HWC: invalid dims q=%u req=%u resp=%u\n",
+ q_depth_max, max_req_msg_size,
+ max_resp_msg_size);
+ q_depth_max = HW_CHANNEL_VF_BOOTSTRAP_QUEUE_DEPTH;
+ goto skip_reinit;
+ }
+
+ err = mana_smc_teardown_hwc(&gc->shm_channel, false);
+ if (err) {
+ dev_err(hwc->dev,
+ "Failed to teardown HWC for reinit: %d\n",
+ err);
+ goto reinit_fallback;
+ }
+
+ hwc->setup_active = false;
+
+ /* Unpublish the CQ and drain its EQ before freeing the table. */
+ mana_hwc_destroy_queues(hwc);
+
+ old_cq_table = gc->cq_table;
+ gc->cq_table = NULL;
+ gc->max_num_cqs = 0;
+ synchronize_rcu();
+ vfree(old_cq_table);
+
+ err = mana_hwc_init_queues(hwc, q_depth_max,
+ max_req_msg_size,
+ max_resp_msg_size);
+ if (err) {
+ dev_err(hwc->dev, "Failed to reinit HWC: %d\n", err);
+ goto reinit_fallback;
+ }
+
+ err = mana_hwc_establish_channel(gc, &q_depth_max,
+ &max_req_msg_size,
+ &max_resp_msg_size);
+ if (!err &&
+ (q_depth_max < hwc->num_inflight_msg ||
+ max_req_msg_size != HW_CHANNEL_MAX_REQUEST_SIZE ||
+ max_resp_msg_size != HW_CHANNEL_MAX_RESPONSE_SIZE)) {
+ /* The rebuilt channel must support the allocated depth
+ * and message sizes.
+ */
+ dev_err(hwc->dev,
+ "HWC: rebuilt q=%u req=%u resp=%u, built for %u/%u/%u\n",
+ q_depth_max, max_req_msg_size,
+ max_resp_msg_size, hwc->num_inflight_msg,
+ HW_CHANNEL_MAX_REQUEST_SIZE,
+ HW_CHANNEL_MAX_RESPONSE_SIZE);
+ err = -EPROTO;
+ }
+ if (err) {
+ dev_err(hwc->dev, "Failed to re-establish HWC: %d\n",
+ err);
+ goto reinit_fallback;
+ }
+ }
+
+ goto skip_reinit;
+
+reinit_fallback:
+ /* A failed handshake leaves queue ownership uncertain. */
+ if (hwc->setup_active) {
+ if (mana_smc_teardown_hwc(&gc->shm_channel, false)) {
+ dev_err(hwc->dev,
+ "Failed to tear down HWC before bootstrap fallback\n");
+ goto out;
+ }
+ hwc->setup_active = false;
+ }
+
+ /* The failed establish may not have allocated cq_table. */
+ dev_warn(hwc->dev, "HWC reinit failed, falling back to bootstrap depth\n");
+
+ mana_hwc_destroy_queues(hwc);
+
+ old_cq_table = gc->cq_table;
+ gc->cq_table = NULL;
+ gc->max_num_cqs = 0;
+ synchronize_rcu();
+ vfree(old_cq_table);
+
+ err = mana_hwc_init_queues(hwc, HW_CHANNEL_VF_BOOTSTRAP_QUEUE_DEPTH,
+ HW_CHANNEL_MAX_REQUEST_SIZE,
+ HW_CHANNEL_MAX_RESPONSE_SIZE);
+ if (err) {
+ dev_err(hwc->dev, "Failed to restore bootstrap HWC: %d\n", err);
+ goto out;
+ }
+
+ err = mana_hwc_establish_channel(gc, &q_depth_max, &max_req_msg_size,
+ &max_resp_msg_size);
+ if (!err &&
+ (max_req_msg_size != HW_CHANNEL_MAX_REQUEST_SIZE ||
+ max_resp_msg_size != HW_CHANNEL_MAX_RESPONSE_SIZE)) {
+ /* The restored channel must report the allocated message sizes. */
+ dev_err(hwc->dev, "HWC: bootstrap reports req=%u resp=%u\n",
+ max_req_msg_size, max_resp_msg_size);
+ err = -EPROTO;
+ }
+ if (err) {
+ dev_err(hwc->dev, "Failed to re-establish bootstrap HWC: %d\n",
+ err);
+ goto out;
+ }
+
+skip_reinit:
+
err = mana_hwc_test_channel(gc->hwc.driver_data,
- HW_CHANNEL_VF_BOOTSTRAP_QUEUE_DEPTH,
+ hwc->num_inflight_msg,
max_req_msg_size, max_resp_msg_size);
if (err) {
dev_err(hwc->dev, "Failed to test HWC: %d\n", err);
diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h
index 571a533e62e64790f9000d42ab0e833fe36ccff6..22dd7c9cecc8a74ed08075bee2b6db6f39ea9cc0 100644
--- a/include/net/mana/gdma.h
+++ b/include/net/mana/gdma.h
@@ -692,6 +692,9 @@ enum {
/* Driver supports dynamic interrupt moderation - DIM */
#define GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION BIT(28)
+/* Driver supports dynamic queue depth for HWC */
+#define GDMA_DRV_CAP_FLAG_1_DYN_HWC_QUEUE_DEPTH BIT(29)
+
/* Driver supports non-contiguous queue buffers */
#define GDMA_DRV_CAP_FLAG_1_NON_CONTIGUOUS_BUFFERS BIT(30)
@@ -710,6 +713,7 @@ enum {
GDMA_DRV_CAP_FLAG_1_PROBE_RECOVERY | \
GDMA_DRV_CAP_FLAG_1_HANDLE_STALL_SQ_RECOVERY | \
GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECOVERY | \
+ GDMA_DRV_CAP_FLAG_1_DYN_HWC_QUEUE_DEPTH | \
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)
diff --git a/include/net/mana/hw_channel.h b/include/net/mana/hw_channel.h
index fba27d8620a388a41ae7ddd3bf2b4792f7beec3d..dfd92336b2b4dc0e9943732f9080887683ebfbbc 100644
--- a/include/net/mana/hw_channel.h
+++ b/include/net/mana/hw_channel.h
@@ -11,6 +11,13 @@
#define HW_CHANNEL_VF_BOOTSTRAP_QUEUE_DEPTH 1
+/* Upper bound on the device-reported HWC queue depth. The HWC is a
+ * control channel, so a modest depth already keeps enough management
+ * commands in flight, while the depth directly scales two coherent DMA
+ * buffers of q_depth * max_msg_size bytes each.
+ */
+#define HW_CHANNEL_MAX_QUEUE_DEPTH 128
+
#define HWC_INIT_DATA_CQID 1
#define HWC_INIT_DATA_RQID 2
#define HWC_INIT_DATA_SQID 3
@@ -205,7 +212,7 @@ struct hw_channel_context {
u32 max_req_msg_size;
- u16 hwc_init_q_depth_max;
+ u32 hwc_init_q_depth_max;
u32 hwc_init_max_req_msg_size;
u32 hwc_init_max_resp_msg_size;
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v5 1/4] net: mana: track when the HWC has been handed to the PF
2026-09-08 3:51 ` [PATCH net-next v5 1/4] net: mana: track when the HWC has been handed to the PF Long Li
@ 2026-09-09 3:52 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-09-09 3:52 UTC (permalink / raw)
To: Long Li; +Cc: linux-rdma, linux-hyperv
> Track possible PF queue ownership with setup_active for HWC reinit.
> Set it before submitting ESTABLISH_HWC. Cleanup now attempts DESTROY_HWC
> after submitted setup failures even before MAX_NUM_CQS arrives, adding
> potentially tens of seconds of polling. Resources are still freed if
> teardown fails.
>
> Signed-off-by: Long Li <longli@microsoft.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908035201.402424-1-longli@microsoft.com?part=1
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v5 2/4] net: mana: give each HWC message slot its own completion state
2026-09-08 3:51 ` [PATCH net-next v5 2/4] net: mana: give each HWC message slot its own completion state Long Li
@ 2026-09-09 3:52 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-09-09 3:52 UTC (permalink / raw)
To: Long Li; +Cc: linux-hyperv, linux-rdma
> Add per-slot locking, sender/response references and a responded flag;
> make ctx->error signed. Stop copying after buffer withdrawal or completion.
> Return a response recorded at the timeout check instead of -ETIMEDOUT,
> without shortening later waits.
>
> Ignore zero timeout-query replies; asynchronous updates remain unfiltered.
> Timed-out slots remain reusable; the next patch adds quarantine.
>
> Signed-off-by: Long Li <longli@microsoft.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908035201.402424-1-longli@microsoft.com?part=2
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v5 4/4] net: mana: add dynamic HWC queue depth with reinit path
2026-09-08 3:51 ` [PATCH net-next v5 4/4] net: mana: add dynamic HWC queue depth with reinit path Long Li
@ 2026-09-09 3:52 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-09-09 3:52 UTC (permalink / raw)
To: Long Li; +Cc: linux-rdma, linux-hyperv
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [Critical] Capping the hardware-reported HWC queue depth without negotiating it with the host causes a fatal ring buffer size mismatch, leading to out-of-bounds memory accesses.
Pre-existing issues:
- [High] CQ queue memory is freed without waiting for an RCU grace period, risking use-after-free in the IRQ handler.
- [High] Use-After-Free during channel teardown due to incorrect queue destruction order.
--
commit e9ca1d1d51e325f4557f02fd7fcfac8f36e41ace
Author: Long Li <longli@microsoft.com>
net: mana: add dynamic HWC queue depth with reinit path
Rebuild depth-one HWC queues at the advertised maximum, capped at 128.
Preserve the 24-bit depth in u32, bound allocations and round message
buffers to a power of two. Advertise support in the driver-version request.
> diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c
> index eb88bae2b14d8..78424e1d884f1 100644
> --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
> +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
> @@ -1260,15 +1260,17 @@ static void mana_gd_create_cq(const struct gdma_queue_spec *spec,
> static void mana_gd_destroy_cq(struct gdma_context *gc,
> struct gdma_queue *queue)
> {
> + struct gdma_queue **cq_table = READ_ONCE(gc->cq_table);
> u32 id = queue->id;
>
> - if (id >= gc->max_num_cqs)
> + /* HWC re-establishment can fail before allocating the CQ table. */
> + if (!cq_table || id >= gc->max_num_cqs)
> return;
>
> - if (!gc->cq_table[id])
> + if (!cq_table[id])
> return;
>
> - gc->cq_table[id] = NULL;
> + cq_table[id] = NULL;
[Severity: High]
This isn't a bug introduced by this patch, but can this lead to a
use-after-free in the EQ interrupt handler?
The CQ memory is freed by the caller (mana_gd_destroy_queue()) immediately
after mana_gd_destroy_cq() unpublishes it from the table, without waiting for
an RCU grace period.
Concurrently, the EQ interrupt processor (mana_gd_process_eqe()) runs under
rcu_read_lock(), reads the CQ pointer from the table, and dereferences it. If
the CQ is freed during this window, the interrupt handler will access freed
memory. Should this wait for an RCU grace period before freeing the queue?
> }
>
> int mana_gd_create_hwc_queue(struct gdma_dev *gd,
> diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c
> index a4f7346d285f7..750ee8f729994 100644
> --- a/drivers/net/ethernet/microsoft/mana/hw_channel.c
> +++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c
[ ... ]
> @@ -846,13 +878,44 @@ static int mana_hwc_init_queues(struct hw_channel_context *hwc, u16 q_depth,
> return err;
> }
>
> +/* Bring-up only: requires no senders or concurrent lifecycle operations.
> + * This helper does not unpublish the HWC or drain senders.
> + */
> +static void mana_hwc_destroy_queues(struct hw_channel_context *hwc)
> +{
> + struct gdma_context *gc = hwc->gdma_dev->gdma_context;
> +
> + /* The CQ helper deregisters the HWC EQ before returning. */
> + if (hwc->cq) {
> + mana_hwc_destroy_cq(gc, hwc->cq);
[Severity: High]
This is a pre-existing issue, but does this queue destruction order cause a
use-after-free during teardown?
mana_hwc_destroy_cq() frees the CQ object before it destroys the EQ. The EQ
remains active and can still receive events from the hardware. If a teardown
fails or times out (which is actively exercised by the new reinit_fallback
logic) and an initialization event arrives after the CQ is freed but before
the EQ is disabled, the EQ interrupt handler will dereference the freed CQ
when processing HWC_INIT_DATA_CQID.
> + hwc->cq = NULL;
> + }
[ ... ]
> @@ -896,8 +959,132 @@ int mana_hwc_create_channel(struct gdma_context *gc)
> goto out;
> }
>
> + if (q_depth_max > HW_CHANNEL_VF_BOOTSTRAP_QUEUE_DEPTH) {
> + /* Bound DMA allocations before using the 24-bit depth. */
> + if (q_depth_max > HW_CHANNEL_MAX_QUEUE_DEPTH)
> + q_depth_max = HW_CHANNEL_MAX_QUEUE_DEPTH;
[Severity: Critical]
Does capping the hardware-reported HWC queue depth without negotiating it
with the host cause a ring buffer size mismatch?
The driver limits the queue depth to HW_CHANNEL_MAX_QUEUE_DEPTH (128) and
allocates DMA rings based on this limit. However, the mana_smc_setup_hwc()
protocol lacks a queue size field, meaning this restriction isn't
communicated to the host hardware.
If the hardware advertises a larger depth (e.g., 256), will it write up to
256 entries into the rings and exceed the 128 allocated entries, leading to
an out-of-bounds memory write?
> +
> + /* Keep bootstrap message sizes for mandatory commands.
> + * Incompatible reports skip rebuilding, not channel creation.
> + */
> + if (max_req_msg_size != HW_CHANNEL_MAX_REQUEST_SIZE ||
[ ... ]
> + err = mana_hwc_establish_channel(gc, &q_depth_max,
> + &max_req_msg_size,
> + &max_resp_msg_size);
> + if (!err &&
> + (q_depth_max < hwc->num_inflight_msg ||
[Severity: Critical]
Does this validation check incorrectly allow the out-of-bounds access?
By verifying q_depth_max < hwc->num_inflight_msg, it permits the hardware
depth (q_depth_max) to be larger than the driver's allocated depth
(hwc->num_inflight_msg). Should this reject the configuration if the hardware
depth exceeds the allocated depth?
> + max_req_msg_size != HW_CHANNEL_MAX_REQUEST_SIZE ||
> + max_resp_msg_size != HW_CHANNEL_MAX_RESPONSE_SIZE)) {
> + /* The rebuilt channel must support the allocated depth
> + * and message sizes.
> + */
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908035201.402424-1-longli@microsoft.com?part=4
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net-next v5 3/4] net: mana: support concurrent HWC requests
2026-09-08 3:51 ` [PATCH net-next v5 3/4] net: mana: support concurrent HWC requests Long Li
@ 2026-09-09 3:52 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-09-09 3:52 UTC (permalink / raw)
To: Long Li; +Cc: linux-rdma, linux-hyperv
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Preexisting use-after-free of `hwc_cq->comp_buf` during HWC CQ destruction due to a race with the interrupt handler.
--
commit a87caaea6f635bdcf7c331ad2a7a65f2ff025635
Author: Long Li <longli@microsoft.com>
Subject: net: mana: support concurrent HWC requests
This commit serializes SQ posting and protects HWC lookup and sender accounting
with hwc_lock. Teardown now stops admission, force-completes requests, and
drains senders before destroying the CQ, TXQ, and RXQ.
> diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c
> index 6605e7a9c481b..a4f7346d285f7 100644
> --- a/drivers/net/ethernet/microsoft/mana/hw_channel.c
> +++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c
[ ... ]
> @@ -864,14 +974,28 @@ void mana_hwc_destroy_channel(struct gdma_context *gc)
> }
> gc->max_num_cqs = 0;
>
> + /* Deregister the HWC EQ before freeing the work queues. */
> + if (hwc->cq)
> + mana_hwc_destroy_cq(hwc->gdma_dev->gdma_context, hwc->cq);
> +
[Severity: High]
This is a pre-existing issue, but since this patch moves the call to
mana_hwc_destroy_cq(), is it worth fixing a use-after-free bug in that
function's teardown path?
In mana_hwc_destroy_cq(), hwc_cq->comp_buf is freed before the EQ is
destroyed:
mana_hwc_destroy_cq() {
kfree(hwc_cq->comp_buf);
if (hwc_cq->gdma_cq)
mana_gd_destroy_queue(gc, hwc_cq->gdma_cq);
if (hwc_cq->gdma_eq)
mana_gd_destroy_queue(gc, hwc_cq->gdma_eq);
...
}
Because mana_gd_destroy_queue() on the EQ is what performs synchronize_rcu()
to wait for concurrent interrupt handlers to finish, a racing interrupt
handler on another CPU could still execute mana_hwc_comp_event() after the
buffer is freed:
mana_hwc_comp_event() {
...
completions = hwc_cq->comp_buf;
comp_read = mana_gd_poll_cq(q_self, completions, hwc_cq->queue_depth);
...
}
Could this lead to a use-after-free and memory corruption when
mana_gd_poll_cq() writes hardware completion events into the freed
completions buffer?
> if (hwc->txq)
> mana_hwc_destroy_wq(hwc, hwc->txq);
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908035201.402424-1-longli@microsoft.com?part=3
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-09-09 3:52 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-08 3:51 [PATCH net-next v5 0/4] net: mana: concurrent HWC requests and dynamic queue depth Long Li
2026-09-08 3:51 ` [PATCH net-next v5 1/4] net: mana: track when the HWC has been handed to the PF Long Li
2026-09-09 3:52 ` sashiko-bot
2026-09-08 3:51 ` [PATCH net-next v5 2/4] net: mana: give each HWC message slot its own completion state Long Li
2026-09-09 3:52 ` sashiko-bot
2026-09-08 3:51 ` [PATCH net-next v5 3/4] net: mana: support concurrent HWC requests Long Li
2026-09-09 3:52 ` sashiko-bot
2026-09-08 3:51 ` [PATCH net-next v5 4/4] net: mana: add dynamic HWC queue depth with reinit path Long Li
2026-09-09 3:52 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox