From: Mohamed Khalfella <mkhalfella@purestorage.com>
To: Keith Busch <kbusch@kernel.org>, Jens Axboe <axboe@kernel.dk>,
Christoph Hellwig <hch@lst.de>, Sagi Grimberg <sagi@grimberg.me>
Cc: Justin Tee <justin.tee@broadcom.com>,
Naresh Gottumukkala <nareshgottumukkala83@gmail.com>,
Paul Ely <paul.ely@broadcom.com>,
Hannes Reinecke <hare@kernel.org>,
Chaitanya Kulkarni <kch@nvidia.com>,
James Smart <jsmart833426@gmail.com>,
Randy Jennings <randyj@purestorage.com>,
Mohamed Khalfella <mkhalfella@purestorage.com>,
linux-nvme@lists.infradead.org, linux-kernel@vger.kernel.org
Subject: [PATCH v6 15/18] nvme-fc: Hold inflight requests while in FENCING state
Date: Sun, 20 Sep 2026 11:28:13 -0700 [thread overview]
Message-ID: <20260920182936.2317916-16-mkhalfella@purestorage.com> (raw)
In-Reply-To: <20260920182936.2317916-1-mkhalfella@purestorage.com>
With the association torn down at the start of nvme_fc_fencing_work(),
canceling the held requests inside nvme_fc_delete_association() would
complete them at fence START, letting them fail over to other paths
before CCR/CQT guarantees the target can no longer execute the original
commands - defeating the hold this patch introduces.
Split nvme_fc_delete_association() into __nvme_fc_teardown_association()
(LLDD-facing teardown, no request completion) and
nvme_fc_flush_held_requests() (cancel tagsets + unquiesce queues).
nvme_fc_fencing_work() now runs only the teardown half up front and
flushes the held requests on the FENCING -> FENCED transition. The
queues stay quiesced across the fence so nothing new reaches the dead
association.
Update nvme_fc_fcpio_done() to not complete aborted requests or requests
with transport errors. Held requests cannot be forced out mid-fence by
the block layer, nvme_fc_timeout() always returns BLK_EH_RESET_TIMER and
nvme_fc_start_ioerr_recovery() is a no-op in FENCING state. Every held
op is FCPOP_STATE_COMPLETE before the fence starts (aborts drained,
synchronize_rcu() done), so the flush never touches the LLDD.
Signed-off-by: Mohamed Khalfella <mkhalfella@purestorage.com>
---
drivers/nvme/host/fc.c | 116 +++++++++++++++++++++++++++++++++--------
1 file changed, 94 insertions(+), 22 deletions(-)
diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c
index 42671cca439f..a6e0fa2dc341 100644
--- a/drivers/nvme/host/fc.c
+++ b/drivers/nvme/host/fc.c
@@ -172,7 +172,7 @@ struct nvme_fc_ctrl {
struct kref ref;
unsigned long flags;
- u32 iocnt;
+ atomic_t iocnt;
wait_queue_head_t ioabort_wait;
struct nvme_fc_fcp_op aen_ops[NVME_NR_AEN_COMMANDS];
@@ -991,7 +991,8 @@ static void nvme_fc_ctrl_put(struct nvme_fc_ctrl *);
static int nvme_fc_ctrl_get(struct nvme_fc_ctrl *);
static void nvme_fc_error_recovery(struct nvme_fc_ctrl *ctrl);
-static void nvme_fc_delete_association(struct nvme_fc_ctrl *ctrl);
+static void __nvme_fc_teardown_association(struct nvme_fc_ctrl *ctrl);
+static void nvme_fc_flush_held_requests(struct nvme_fc_ctrl *ctrl);
static void
__nvme_fc_finish_ls_req(struct nvmefc_ls_req_op *lsop)
@@ -1826,7 +1827,7 @@ __nvme_fc_abort_op(struct nvme_fc_ctrl *ctrl, struct nvme_fc_fcp_op *op)
atomic_set(&op->state, opstate);
else if (test_bit(FCCTRL_TERMIO, &ctrl->flags)) {
op->flags |= FCOP_FLAGS_TERMIO;
- ctrl->iocnt++;
+ atomic_inc(&ctrl->iocnt);
}
spin_unlock_irqrestore(&ctrl->lock, flags);
@@ -1856,20 +1857,29 @@ nvme_fc_abort_aen_ops(struct nvme_fc_ctrl *ctrl)
}
static inline void
+__nvme_fc_fcpop_count_one_down(struct nvme_fc_ctrl *ctrl)
+{
+ if (atomic_dec_return(&ctrl->iocnt) == 0)
+ wake_up(&ctrl->ioabort_wait);
+}
+
+static inline bool
__nvme_fc_fcpop_chk_teardowns(struct nvme_fc_ctrl *ctrl,
struct nvme_fc_fcp_op *op, int opstate)
{
unsigned long flags;
+ bool ret = false;
if (opstate == FCPOP_STATE_ABORTED) {
spin_lock_irqsave(&ctrl->lock, flags);
if (test_bit(FCCTRL_TERMIO, &ctrl->flags) &&
op->flags & FCOP_FLAGS_TERMIO) {
- if (!--ctrl->iocnt)
- wake_up(&ctrl->ioabort_wait);
+ ret = true;
}
spin_unlock_irqrestore(&ctrl->lock, flags);
}
+
+ return ret;
}
static void nvme_fc_fencing_work(struct work_struct *work)
@@ -1885,12 +1895,14 @@ static void nvme_fc_fencing_work(struct work_struct *work)
*/
nvme_stop_keep_alive(ctrl);
cancel_work_sync(&ctrl->async_event_work);
- nvme_fc_delete_association(fc_ctrl);
+ if (test_and_clear_bit(ASSOC_ACTIVE, &fc_ctrl->flags))
+ __nvme_fc_teardown_association(fc_ctrl);
rem = nvme_fence_ctrl(ctrl);
if (rem)
dev_info(ctrl->device, "CCR failed, starting error recovery\n");
+ nvme_fc_flush_held_requests(fc_ctrl);
nvme_change_ctrl_state(ctrl, NVME_CTRL_FENCED);
if (nvme_change_ctrl_state(ctrl, NVME_CTRL_RESETTING))
queue_work(nvme_reset_wq, &fc_ctrl->ioerr_work);
@@ -2003,7 +2015,8 @@ nvme_fc_fcpio_done(struct nvmefc_fcp_req *req)
struct nvme_command *sqe = &op->cmd_iu.sqe;
__le16 status = cpu_to_le16(NVME_SC_SUCCESS << 1);
union nvme_result result;
- bool terminate_assoc = true;
+ bool op_term, terminate_assoc = true;
+ enum nvme_ctrl_state state;
int opstate;
/*
@@ -2043,6 +2056,9 @@ nvme_fc_fcpio_done(struct nvmefc_fcp_req *req)
* association to be terminated.
*/
+ /* Pairs with synchronize_rcu() in __nvme_fc_teardown_association() */
+ rcu_read_lock();
+
opstate = atomic_xchg(&op->state, FCPOP_STATE_COMPLETE);
fc_dma_sync_single_for_cpu(ctrl->lport->dev, op->fcp_req.rspdma,
@@ -2136,20 +2152,44 @@ nvme_fc_fcpio_done(struct nvmefc_fcp_req *req)
done:
if (op->flags & FCOP_FLAGS_AEN) {
nvme_complete_async_event(&queue->ctrl->ctrl, status, &result);
- __nvme_fc_fcpop_chk_teardowns(ctrl, op, opstate);
+ if (__nvme_fc_fcpop_chk_teardowns(ctrl, op, opstate))
+ __nvme_fc_fcpop_count_one_down(ctrl);
atomic_set(&op->state, FCPOP_STATE_IDLE);
op->flags = FCOP_FLAGS_AEN; /* clear other flags */
nvme_fc_ctrl_put(ctrl);
goto check_error;
}
- __nvme_fc_fcpop_chk_teardowns(ctrl, op, opstate);
+ /*
+ * We can not access op after the request is completed because it can
+ * be reused immediately. At the same time we want to wakeup the thread
+ * waiting for ongoing IOs _after_ requests are completed. This is
+ * necessary because that thread will start canceling inflight IOs
+ * and we want to avoid request completion racing with cancellation.
+ */
+ op_term = __nvme_fc_fcpop_chk_teardowns(ctrl, op, opstate);
+
+ /*
+ * If we are going to terminate associations and the controller is
+ * LIVE or FENCING, then do not complete this request now. Let error
+ * recovery cancel this request when it is safe to do so.
+ */
+ state = nvme_ctrl_state(&ctrl->ctrl);
+ if (terminate_assoc &&
+ (state == NVME_CTRL_LIVE || state == NVME_CTRL_FENCING))
+ goto check_op_term;
+
if (!nvme_try_complete_req(rq, status, result))
nvme_fc_complete_rq(rq);
+check_op_term:
+ if (op_term)
+ __nvme_fc_fcpop_count_one_down(ctrl);
check_error:
if (terminate_assoc)
nvme_fc_start_ioerr_recovery(ctrl, "io error");
+
+ rcu_read_unlock();
}
static int
@@ -2802,7 +2842,8 @@ nvme_fc_start_fcp_op(struct nvme_fc_ctrl *ctrl, struct nvme_fc_queue *queue,
* cmd with the csn was supposed to arrive.
*/
opstate = atomic_xchg(&op->state, FCPOP_STATE_COMPLETE);
- __nvme_fc_fcpop_chk_teardowns(ctrl, op, opstate);
+ if (__nvme_fc_fcpop_chk_teardowns(ctrl, op, opstate))
+ __nvme_fc_fcpop_count_one_down(ctrl);
if (!(op->flags & FCOP_FLAGS_AEN)) {
nvme_fc_unmap_data(ctrl, op->rq, op);
@@ -3260,25 +3301,25 @@ nvme_fc_create_association(struct nvme_fc_ctrl *ctrl)
return ret;
}
-
/*
- * This routine stops operation of the controller on the host side.
- * On the host os stack side: Admin and IO queues are stopped,
- * outstanding ios on them terminated via FC ABTS.
- * On the link side: the association is terminated.
+ * This routine tears down the association on the link side and releases
+ * all LLDD resources: outstanding ios are terminated via FC ABTS, hw
+ * queues are deleted and the controller is marked inactive on the
+ * rport. Requests that nvme_fc_fcpio_done() decided to hold are NOT
+ * completed here, the caller must follow up with
+ * nvme_fc_flush_held_requests() once it is safe to release them.
+ *
+ * The caller must own the ASSOC_ACTIVE bit (test_and_clear_bit()).
*/
static void
-nvme_fc_delete_association(struct nvme_fc_ctrl *ctrl)
+__nvme_fc_teardown_association(struct nvme_fc_ctrl *ctrl)
{
struct nvmefc_ls_rcv_op *disls = NULL;
unsigned long flags;
- if (!test_and_clear_bit(ASSOC_ACTIVE, &ctrl->flags))
- return;
-
spin_lock_irqsave(&ctrl->lock, flags);
set_bit(FCCTRL_TERMIO, &ctrl->flags);
- ctrl->iocnt = 0;
+ atomic_set(&ctrl->iocnt, 0);
spin_unlock_irqrestore(&ctrl->lock, flags);
__nvme_fc_abort_outstanding_ios(ctrl, false);
@@ -3287,11 +3328,18 @@ nvme_fc_delete_association(struct nvme_fc_ctrl *ctrl)
nvme_fc_abort_aen_ops(ctrl);
/* wait for all io that had to be aborted */
+ wait_event(ctrl->ioabort_wait, atomic_read(&ctrl->iocnt) == 0);
spin_lock_irq(&ctrl->lock);
- wait_event_lock_irq(ctrl->ioabort_wait, ctrl->iocnt == 0, ctrl->lock);
clear_bit(FCCTRL_TERMIO, &ctrl->flags);
spin_unlock_irq(&ctrl->lock);
+ /*
+ * Wait for a started nvme_fc_fcpio_done() to complete before we
+ * proceed with cancelling inflight requests. This guarantees a
+ * request is completed by one of the two codepaths, not both.
+ */
+ synchronize_rcu();
+
nvme_fc_term_aen_ops(ctrl);
/*
@@ -3323,13 +3371,37 @@ nvme_fc_delete_association(struct nvme_fc_ctrl *ctrl)
__nvme_fc_delete_hw_queue(ctrl, &ctrl->queues[0], 0);
nvme_fc_free_queue(&ctrl->queues[0]);
+ nvme_fc_ctlr_inactive_on_rport(ctrl);
+}
+
+/* Complete the requests held in __nvme_fc_teardown_association() */
+static void
+nvme_fc_flush_held_requests(struct nvme_fc_ctrl *ctrl)
+{
+ nvme_cancel_tagset(&ctrl->ctrl);
+ nvme_cancel_admin_tagset(&ctrl->ctrl);
+
/* re-enable the admin_q so anything new can fast fail */
nvme_unquiesce_admin_queue(&ctrl->ctrl);
/* resume the io queues so that things will fast fail */
nvme_unquiesce_io_queues(&ctrl->ctrl);
+}
- nvme_fc_ctlr_inactive_on_rport(ctrl);
+/*
+ * This routine stops operation of the controller on the host side.
+ * On the host os stack side: Admin and IO queues are stopped,
+ * outstanding ios on them terminated via FC ABTS.
+ * On the link side: the association is terminated.
+ */
+static void
+nvme_fc_delete_association(struct nvme_fc_ctrl *ctrl)
+{
+ if (!test_and_clear_bit(ASSOC_ACTIVE, &ctrl->flags))
+ return;
+
+ __nvme_fc_teardown_association(ctrl);
+ nvme_fc_flush_held_requests(ctrl);
}
static void
--
2.55.0
next prev parent reply other threads:[~2026-09-20 18:30 UTC|newest]
Thread overview: 46+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-20 18:27 [PATCH v6 00/18] TP8028 Rapid Path Failure Recovery Mohamed Khalfella
2026-09-20 18:27 ` [PATCH v6 01/18] nvmet: Rapid Path Failure Recovery set controller identify fields Mohamed Khalfella
2026-10-15 12:58 ` Daniel Wagner
2026-10-19 18:06 ` Mohamed Khalfella
2026-09-20 18:28 ` [PATCH v6 02/18] nvmet/debugfs: Export controller CIU and CIRN via debugfs Mohamed Khalfella
2026-10-19 12:01 ` Daniel Wagner
2026-10-19 19:01 ` Mohamed Khalfella
2026-10-20 12:59 ` Daniel Wagner
2026-09-20 18:28 ` [PATCH v6 03/18] nvmet: Implement CCR nvme command Mohamed Khalfella
2026-10-19 16:00 ` Daniel Wagner
2026-10-19 20:21 ` Mohamed Khalfella
2026-09-20 18:28 ` [PATCH v6 04/18] nvmet: Implement CCR logpage Mohamed Khalfella
2026-10-19 16:18 ` Daniel Wagner
2026-09-20 18:28 ` [PATCH v6 05/18] nvmet: Send an AEN on CCR completion Mohamed Khalfella
2026-10-19 16:31 ` Daniel Wagner
2026-09-20 18:28 ` [PATCH v6 06/18] nvme: Rapid Path Failure Recovery read controller identify fields Mohamed Khalfella
2026-10-19 16:56 ` Daniel Wagner
2026-10-19 21:18 ` Mohamed Khalfella
2026-09-20 18:28 ` [PATCH v6 07/18] nvme: Introduce FENCING and FENCED controller states Mohamed Khalfella
2026-10-19 17:01 ` Daniel Wagner
2026-10-19 17:18 ` Daniel Wagner
2026-10-19 21:40 ` Mohamed Khalfella
2026-10-20 13:15 ` Daniel Wagner
2026-10-19 21:26 ` Mohamed Khalfella
2026-10-20 13:10 ` Daniel Wagner
2026-09-20 18:28 ` [PATCH v6 08/18] nvme: Implement cross-controller reset recovery Mohamed Khalfella
2026-10-20 8:55 ` Daniel Wagner
2026-09-20 18:28 ` [PATCH v6 09/18] nvme: Implement cross-controller reset completion Mohamed Khalfella
2026-10-20 9:18 ` Daniel Wagner
2026-09-20 18:28 ` [PATCH v6 10/18] nvme-tcp: Use CCR to recover controller that hits an error Mohamed Khalfella
2026-10-20 9:36 ` Daniel Wagner
2026-09-20 18:28 ` [PATCH v6 11/18] nvme-rdma: " Mohamed Khalfella
2026-10-20 9:36 ` Daniel Wagner
2026-09-20 18:28 ` [PATCH v6 12/18] nvme-fc: start error recovery instead of aborting timed out IOs Mohamed Khalfella
2026-10-20 9:55 ` Daniel Wagner
2026-09-20 18:28 ` [PATCH v6 13/18] nvme-fc: perform error recovery directly from ioerr_work Mohamed Khalfella
2026-10-20 10:20 ` Daniel Wagner
2026-09-20 18:28 ` [PATCH v6 14/18] nvme-fc: Use CCR to recover controller that hits an error Mohamed Khalfella
2026-10-20 11:56 ` Daniel Wagner
2026-09-20 18:28 ` Mohamed Khalfella [this message]
2026-10-20 12:21 ` [PATCH v6 15/18] nvme-fc: Hold inflight requests while in FENCING state Daniel Wagner
2026-09-20 18:28 ` [PATCH v6 16/18] nvmet: Add support for CQT to nvme target Mohamed Khalfella
2026-10-20 12:40 ` Daniel Wagner
2026-09-20 18:28 ` [PATCH v6 17/18] nvme: Add support for CQT to nvme host Mohamed Khalfella
2026-10-20 12:46 ` Daniel Wagner
2026-09-20 18:28 ` [PATCH v6 18/18] nvme: let controller deletion wait out a fencing window Mohamed Khalfella
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260920182936.2317916-16-mkhalfella@purestorage.com \
--to=mkhalfella@purestorage.com \
--cc=axboe@kernel.dk \
--cc=hare@kernel.org \
--cc=hch@lst.de \
--cc=jsmart833426@gmail.com \
--cc=justin.tee@broadcom.com \
--cc=kbusch@kernel.org \
--cc=kch@nvidia.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-nvme@lists.infradead.org \
--cc=nareshgottumukkala83@gmail.com \
--cc=paul.ely@broadcom.com \
--cc=randyj@purestorage.com \
--cc=sagi@grimberg.me \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.