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 <[email protected]>
---
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

Reply via email to