From: Mohamed Khalfella <mkhalfella@purestorage.com>
To: Justin Tee <justin.tee@broadcom.com>,
Naresh Gottumukkala <nareshgottumukkala83@gmail.com>,
Paul Ely <paul.ely@broadcom.com>,
Chaitanya Kulkarni <kch@nvidia.com>,
Christoph Hellwig <hch@lst.de>, Jens Axboe <axboe@kernel.dk>,
Keith Busch <kbusch@kernel.org>, Sagi Grimberg <sagi@grimberg.me>,
James Smart <jsmart833426@gmail.com>,
Hannes Reinecke <hare@suse.de>,
Randy Jennings <randyj@purestorage.com>,
Dhaval Giani <dgiani@purestorage.com>
Cc: Aaron Dailey <adailey@purestorage.com>,
linux-nvme@lists.infradead.org, linux-kernel@vger.kernel.org,
Mohamed Khalfella <mkhalfella@purestorage.com>
Subject: [PATCH v5 16/16] nvme: Add support for CQT to nvme host
Date: Sat, 11 Jul 2026 19:23:37 -0700 [thread overview]
Message-ID: <20260712022437.3743117-17-mkhalfella@purestorage.com> (raw)
In-Reply-To: <20260712022437.3743117-1-mkhalfella@purestorage.com>
TP4129 KATO Corrections and Clarifications defined CQT (Command Quiesce
Time) which is used along with KATO (Keep Alive Timeout) to set an upper
limit for attempting Cross-Controller Recovery. Add ctrl->cqt, read its
value from controller identify response, expose it via sysfs, and have
nvme_fence_timeout_ms() account for it.
Use CQT to drive time-based recovery in fc, rdma, and tcp transports.
Add a fenced_work delayed work to each transport controller. If CCR
fails, fencing_work switches to error recovery immediately if CQT is not
supported. Otherwise it schedules fenced_work after the remaining fence
timeout, which also switches to error recovery when scheduled.
Signed-off-by: Mohamed Khalfella <mkhalfella@purestorage.com>
---
drivers/nvme/host/core.c | 1 +
drivers/nvme/host/fc.c | 41 +++++++++++++++++++++++++++++++++++----
drivers/nvme/host/nvme.h | 5 +++--
drivers/nvme/host/rdma.c | 41 +++++++++++++++++++++++++++++++++++----
drivers/nvme/host/sysfs.c | 2 ++
drivers/nvme/host/tcp.c | 41 +++++++++++++++++++++++++++++++++++----
6 files changed, 117 insertions(+), 14 deletions(-)
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index 18de3805eff8..d3a2db50a89c 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -3821,6 +3821,7 @@ static int nvme_init_identify(struct nvme_ctrl *ctrl)
ctrl->ciu = id->ciu;
ctrl->cirn = le64_to_cpu(id->cirn);
ctrl->ccrl = id->ccrl;
+ ctrl->cqt = le16_to_cpu(id->cqt);
ctrl->oacs = le16_to_cpu(id->oacs);
ctrl->oncs = le16_to_cpu(id->oncs);
diff --git a/drivers/nvme/host/fc.c b/drivers/nvme/host/fc.c
index 006b5a984dfe..dca7a26a4b15 100644
--- a/drivers/nvme/host/fc.c
+++ b/drivers/nvme/host/fc.c
@@ -167,6 +167,7 @@ struct nvme_fc_ctrl {
struct blk_mq_tag_set tag_set;
struct work_struct fencing_work;
+ struct delayed_work fenced_work;
struct work_struct ioerr_work;
struct delayed_work connect_work;
@@ -1878,6 +1879,18 @@ __nvme_fc_fcpop_chk_teardowns(struct nvme_fc_ctrl *ctrl,
return ret;
}
+static void nvme_fc_fenced_work(struct work_struct *work)
+{
+ struct nvme_fc_ctrl *fc_ctrl = container_of(to_delayed_work(work),
+ struct nvme_fc_ctrl, fenced_work);
+ struct nvme_ctrl *ctrl = &fc_ctrl->ctrl;
+
+ dev_info(ctrl->device, "Time-based recovery finished\n");
+ 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);
+}
+
static void nvme_fc_fencing_work(struct work_struct *work)
{
struct nvme_fc_ctrl *fc_ctrl =
@@ -1886,14 +1899,33 @@ static void nvme_fc_fencing_work(struct work_struct *work)
unsigned long rem;
rem = nvme_fence_ctrl(ctrl);
- if (rem)
- dev_info(ctrl->device, "CCR failed, starting error recovery\n");
+ if (!rem)
+ goto done;
+
+ if (!ctrl->cqt) {
+ dev_info(ctrl->device,
+ "CCR failed, CQT not supported, skip time-based recovery\n");
+ goto done;
+ }
+
+ dev_info(ctrl->device,
+ "CCR failed, switch to time-based recovery, timeout = %ums\n",
+ jiffies_to_msecs(rem));
+ queue_delayed_work(nvme_wq, &fc_ctrl->fenced_work, rem);
+ return;
+done:
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);
}
+static void nvme_fc_flush_fencing_works(struct nvme_fc_ctrl *ctrl)
+{
+ flush_work(&ctrl->fencing_work);
+ flush_delayed_work(&ctrl->fenced_work);
+}
+
static void
nvme_fc_ctrl_ioerr_work(struct work_struct *work)
{
@@ -1915,7 +1947,7 @@ nvme_fc_ctrl_ioerr_work(struct work_struct *work)
return;
}
- flush_work(&ctrl->fencing_work);
+ nvme_fc_flush_fencing_works(ctrl);
nvme_fc_error_recovery(ctrl);
}
@@ -3395,7 +3427,7 @@ nvme_fc_reset_ctrl_work(struct work_struct *work)
struct nvme_fc_ctrl *ctrl =
container_of(work, struct nvme_fc_ctrl, ctrl.reset_work);
- flush_work(&ctrl->fencing_work);
+ nvme_fc_flush_fencing_works(ctrl);
nvme_stop_ctrl(&ctrl->ctrl);
/* will block will waiting for io to terminate */
@@ -3573,6 +3605,7 @@ nvme_fc_alloc_ctrl(struct device *dev, struct nvmf_ctrl_options *opts,
INIT_WORK(&ctrl->ctrl.reset_work, nvme_fc_reset_ctrl_work);
INIT_DELAYED_WORK(&ctrl->connect_work, nvme_fc_connect_ctrl_work);
INIT_WORK(&ctrl->fencing_work, nvme_fc_fencing_work);
+ INIT_DELAYED_WORK(&ctrl->fenced_work, nvme_fc_fenced_work);
INIT_WORK(&ctrl->ioerr_work, nvme_fc_ctrl_ioerr_work);
spin_lock_init(&ctrl->lock);
diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
index 578fedda9946..8ea7d22b78f8 100644
--- a/drivers/nvme/host/nvme.h
+++ b/drivers/nvme/host/nvme.h
@@ -392,6 +392,7 @@ struct nvme_ctrl {
u32 max_zone_append;
#endif
u16 crdt[3];
+ u16 cqt;
u16 oncs;
u8 dmrl;
u8 ciu;
@@ -1334,8 +1335,8 @@ static inline bool nvme_multi_css(struct nvme_ctrl *ctrl)
static inline unsigned long nvme_fence_timeout_ms(struct nvme_ctrl *ctrl)
{
if (ctrl->ctratt & NVME_CTRL_ATTR_TBKAS)
- return 3 * ctrl->kato * 1000;
- return 2 * ctrl->kato * 1000;
+ return 3 * ctrl->kato * 1000 + ctrl->cqt;
+ return 2 * ctrl->kato * 1000 + ctrl->cqt;
}
#endif /* _NVME_H */
diff --git a/drivers/nvme/host/rdma.c b/drivers/nvme/host/rdma.c
index 7c8108523980..3745b7242fce 100644
--- a/drivers/nvme/host/rdma.c
+++ b/drivers/nvme/host/rdma.c
@@ -113,6 +113,7 @@ struct nvme_rdma_ctrl {
/* other member variables */
struct blk_mq_tag_set tag_set;
struct work_struct fencing_work;
+ struct delayed_work fenced_work;
struct work_struct err_work;
struct nvme_rdma_qe async_event_sqe;
@@ -1152,6 +1153,18 @@ static void nvme_rdma_reconnect_ctrl_work(struct work_struct *work)
nvme_rdma_reconnect_or_remove(ctrl, ret);
}
+static void nvme_rdma_fenced_work(struct work_struct *work)
+{
+ struct nvme_rdma_ctrl *rdma_ctrl = container_of(to_delayed_work(work),
+ struct nvme_rdma_ctrl, fenced_work);
+ struct nvme_ctrl *ctrl = &rdma_ctrl->ctrl;
+
+ dev_info(ctrl->device, "Time-based recovery finished\n");
+ nvme_change_ctrl_state(ctrl, NVME_CTRL_FENCED);
+ if (nvme_change_ctrl_state(ctrl, NVME_CTRL_RESETTING))
+ queue_work(nvme_reset_wq, &rdma_ctrl->err_work);
+}
+
static void nvme_rdma_fencing_work(struct work_struct *work)
{
struct nvme_rdma_ctrl *rdma_ctrl = container_of(work,
@@ -1160,20 +1173,39 @@ static void nvme_rdma_fencing_work(struct work_struct *work)
unsigned long rem;
rem = nvme_fence_ctrl(ctrl);
- if (rem)
- dev_info(ctrl->device, "CCR failed, starting error recovery\n");
+ if (!rem)
+ goto done;
+
+ if (!ctrl->cqt) {
+ dev_info(ctrl->device,
+ "CCR failed, CQT not supported, skip time-based recovery\n");
+ goto done;
+ }
+
+ dev_info(ctrl->device,
+ "CCR failed, switch to time-based recovery, timeout = %ums\n",
+ jiffies_to_msecs(rem));
+ queue_delayed_work(nvme_wq, &rdma_ctrl->fenced_work, rem);
+ return;
+done:
nvme_change_ctrl_state(ctrl, NVME_CTRL_FENCED);
if (nvme_change_ctrl_state(ctrl, NVME_CTRL_RESETTING))
queue_work(nvme_reset_wq, &rdma_ctrl->err_work);
}
+static void nvme_rdma_flush_fencing_works(struct nvme_rdma_ctrl *ctrl)
+{
+ flush_work(&ctrl->fencing_work);
+ flush_delayed_work(&ctrl->fenced_work);
+}
+
static void nvme_rdma_error_recovery_work(struct work_struct *work)
{
struct nvme_rdma_ctrl *ctrl = container_of(work,
struct nvme_rdma_ctrl, err_work);
- flush_work(&ctrl->fencing_work);
+ nvme_rdma_flush_fencing_works(ctrl);
nvme_stop_keep_alive(&ctrl->ctrl);
flush_work(&ctrl->ctrl.async_event_work);
nvme_rdma_teardown_io_queues(ctrl, false);
@@ -2226,7 +2258,7 @@ static void nvme_rdma_reset_ctrl_work(struct work_struct *work)
container_of(work, struct nvme_rdma_ctrl, ctrl.reset_work);
int ret;
- flush_work(&ctrl->fencing_work);
+ nvme_rdma_flush_fencing_works(ctrl);
nvme_stop_ctrl(&ctrl->ctrl);
nvme_rdma_shutdown_ctrl(ctrl, false);
@@ -2348,6 +2380,7 @@ static struct nvme_rdma_ctrl *nvme_rdma_alloc_ctrl(struct device *dev,
INIT_DELAYED_WORK(&ctrl->reconnect_work,
nvme_rdma_reconnect_ctrl_work);
INIT_WORK(&ctrl->fencing_work, nvme_rdma_fencing_work);
+ INIT_DELAYED_WORK(&ctrl->fenced_work, nvme_rdma_fenced_work);
INIT_WORK(&ctrl->err_work, nvme_rdma_error_recovery_work);
INIT_WORK(&ctrl->ctrl.reset_work, nvme_rdma_reset_ctrl_work);
diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c
index 3853c9a6356e..7479cd370584 100644
--- a/drivers/nvme/host/sysfs.c
+++ b/drivers/nvme/host/sysfs.c
@@ -486,6 +486,7 @@ nvme_show_int_function(numa_node);
nvme_show_int_function(queue_count);
nvme_show_int_function(sqsize);
nvme_show_int_function(kato);
+nvme_show_int_function(cqt);
static ssize_t nvme_sysfs_ciu_show(struct device *dev,
struct device_attribute *attr,
@@ -966,6 +967,7 @@ static struct attribute *nvme_dev_attrs[] = {
&dev_attr_sqsize.attr,
&dev_attr_ciu.attr,
&dev_attr_cirn.attr,
+ &dev_attr_cqt.attr,
&dev_attr_hostnqn.attr,
&dev_attr_hostid.attr,
&dev_attr_ctrl_loss_tmo.attr,
diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c
index a1711dd1d3c2..13e250110c1b 100644
--- a/drivers/nvme/host/tcp.c
+++ b/drivers/nvme/host/tcp.c
@@ -162,6 +162,7 @@ struct nvme_tcp_ctrl {
struct nvme_ctrl ctrl;
struct work_struct fencing_work;
+ struct delayed_work fenced_work;
struct work_struct err_work;
struct delayed_work connect_work;
struct nvme_tcp_request async_req;
@@ -2501,6 +2502,18 @@ static void nvme_tcp_reconnect_ctrl_work(struct work_struct *work)
nvme_tcp_reconnect_or_remove(ctrl, ret);
}
+static void nvme_tcp_fenced_work(struct work_struct *work)
+{
+ struct nvme_tcp_ctrl *tcp_ctrl = container_of(to_delayed_work(work),
+ struct nvme_tcp_ctrl, fenced_work);
+ struct nvme_ctrl *ctrl = &tcp_ctrl->ctrl;
+
+ dev_info(ctrl->device, "Time-based recovery finished\n");
+ nvme_change_ctrl_state(ctrl, NVME_CTRL_FENCED);
+ if (nvme_change_ctrl_state(ctrl, NVME_CTRL_RESETTING))
+ queue_work(nvme_reset_wq, &tcp_ctrl->err_work);
+}
+
static void nvme_tcp_fencing_work(struct work_struct *work)
{
struct nvme_tcp_ctrl *tcp_ctrl = container_of(work,
@@ -2509,21 +2522,40 @@ static void nvme_tcp_fencing_work(struct work_struct *work)
unsigned long rem;
rem = nvme_fence_ctrl(ctrl);
- if (rem)
- dev_info(ctrl->device, "CCR failed, starting error recovery\n");
+ if (!rem)
+ goto done;
+ if (!ctrl->cqt) {
+ dev_info(ctrl->device,
+ "CCR failed, CQT not supported, skip time-based recovery\n");
+ goto done;
+ }
+
+ dev_info(ctrl->device,
+ "CCR failed, switch to time-based recovery, timeout = %ums\n",
+ jiffies_to_msecs(rem));
+ queue_delayed_work(nvme_wq, &tcp_ctrl->fenced_work, rem);
+ return;
+
+done:
nvme_change_ctrl_state(ctrl, NVME_CTRL_FENCED);
if (nvme_change_ctrl_state(ctrl, NVME_CTRL_RESETTING))
queue_work(nvme_reset_wq, &tcp_ctrl->err_work);
}
+static void nvme_tcp_flush_fencing_works(struct nvme_ctrl *ctrl)
+{
+ flush_work(&to_tcp_ctrl(ctrl)->fencing_work);
+ flush_delayed_work(&to_tcp_ctrl(ctrl)->fenced_work);
+}
+
static void nvme_tcp_error_recovery_work(struct work_struct *work)
{
struct nvme_tcp_ctrl *tcp_ctrl = container_of(work,
struct nvme_tcp_ctrl, err_work);
struct nvme_ctrl *ctrl = &tcp_ctrl->ctrl;
- flush_work(&to_tcp_ctrl(ctrl)->fencing_work);
+ nvme_tcp_flush_fencing_works(ctrl);
if (nvme_tcp_key_revoke_needed(ctrl))
nvme_auth_revoke_tls_key(ctrl);
nvme_stop_keep_alive(ctrl);
@@ -2566,7 +2598,7 @@ static void nvme_reset_ctrl_work(struct work_struct *work)
container_of(work, struct nvme_ctrl, reset_work);
int ret;
- flush_work(&to_tcp_ctrl(ctrl)->fencing_work);
+ nvme_tcp_flush_fencing_works(ctrl);
if (nvme_tcp_key_revoke_needed(ctrl))
nvme_auth_revoke_tls_key(ctrl);
nvme_stop_ctrl(ctrl);
@@ -2956,6 +2988,7 @@ static struct nvme_tcp_ctrl *nvme_tcp_alloc_ctrl(struct device *dev,
INIT_DELAYED_WORK(&ctrl->connect_work,
nvme_tcp_reconnect_ctrl_work);
INIT_WORK(&ctrl->fencing_work, nvme_tcp_fencing_work);
+ INIT_DELAYED_WORK(&ctrl->fenced_work, nvme_tcp_fenced_work);
INIT_WORK(&ctrl->err_work, nvme_tcp_error_recovery_work);
INIT_WORK(&ctrl->ctrl.reset_work, nvme_reset_ctrl_work);
--
2.54.0
next prev parent reply other threads:[~2026-07-12 2:25 UTC|newest]
Thread overview: 43+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-12 2:23 [PATCH v5 00/16] TP8028 Rapid Path Failure Recovery Mohamed Khalfella
2026-07-12 2:23 ` [PATCH v5 01/16] nvmet: Rapid Path Failure Recovery set controller identify fields Mohamed Khalfella
2026-08-23 0:49 ` Sagi Grimberg
2026-07-12 2:23 ` [PATCH v5 02/16] nvmet/debugfs: Export controller CIU and CIRN via debugfs Mohamed Khalfella
2026-08-23 0:50 ` Sagi Grimberg
2026-07-12 2:23 ` [PATCH v5 03/16] nvmet: Implement CCR nvme command Mohamed Khalfella
2026-08-23 0:53 ` Sagi Grimberg
2026-09-04 23:17 ` Mohamed Khalfella
2026-07-12 2:23 ` [PATCH v5 04/16] nvmet: Implement CCR logpage Mohamed Khalfella
2026-08-23 0:53 ` Sagi Grimberg
2026-07-12 2:23 ` [PATCH v5 05/16] nvmet: Send an AEN on CCR completion Mohamed Khalfella
2026-08-23 0:54 ` Sagi Grimberg
2026-07-12 2:23 ` [PATCH v5 06/16] nvme: Rapid Path Failure Recovery read controller identify fields Mohamed Khalfella
2026-08-23 0:55 ` Sagi Grimberg
2026-07-12 2:23 ` [PATCH v5 07/16] nvme: Introduce FENCING and FENCED controller states Mohamed Khalfella
2026-08-23 0:55 ` Sagi Grimberg
2026-07-12 2:23 ` [PATCH v5 08/16] nvme: Implement cross-controller reset recovery Mohamed Khalfella
2026-07-13 7:04 ` Hannes Reinecke
2026-09-04 23:04 ` Mohamed Khalfella
2026-08-23 0:59 ` Sagi Grimberg
2026-07-12 2:23 ` [PATCH v5 09/16] nvme: Implement cross-controller reset completion Mohamed Khalfella
2026-07-13 7:11 ` Hannes Reinecke
2026-09-04 22:29 ` Mohamed Khalfella
2026-08-23 1:01 ` Sagi Grimberg
2026-07-12 2:23 ` [PATCH v5 10/16] nvme-tcp: Use CCR to recover controller that hits an error Mohamed Khalfella
2026-07-13 7:17 ` Hannes Reinecke
2026-09-04 23:01 ` Mohamed Khalfella
2026-08-23 1:03 ` Sagi Grimberg
2026-09-04 22:52 ` Mohamed Khalfella
2026-09-05 2:09 ` Mohamed Khalfella
2026-07-12 2:23 ` [PATCH v5 11/16] nvme-rdma: " Mohamed Khalfella
2026-07-13 7:18 ` Hannes Reinecke
2026-07-12 2:23 ` [PATCH v5 12/16] nvme-fc: Refactor IO error recovery Mohamed Khalfella
2026-07-13 7:29 ` Hannes Reinecke
2026-09-04 23:19 ` Mohamed Khalfella
2026-07-12 2:23 ` [PATCH v5 13/16] nvme-fc: Use CCR to recover controller that hits an error Mohamed Khalfella
2026-07-12 2:23 ` [PATCH v5 14/16] nvme-fc: Hold inflight requests while in FENCING state Mohamed Khalfella
2026-07-12 2:23 ` [PATCH v5 15/16] nvmet: Add support for CQT to nvme target Mohamed Khalfella
2026-07-13 7:35 ` Hannes Reinecke
2026-08-23 1:04 ` Sagi Grimberg
2026-07-12 2:23 ` Mohamed Khalfella [this message]
2026-07-13 7:36 ` [PATCH v5 16/16] nvme: Add support for CQT to nvme host Hannes Reinecke
2026-08-23 1:07 ` Sagi Grimberg
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=20260712022437.3743117-17-mkhalfella@purestorage.com \
--to=mkhalfella@purestorage.com \
--cc=adailey@purestorage.com \
--cc=axboe@kernel.dk \
--cc=dgiani@purestorage.com \
--cc=hare@suse.de \
--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.