Defined by TP8028 Rapid Path Failure Recovery, CCR (Cross-Controller
Reset) command is an nvme command the is issued to source controller by
initiator to reset impacted controller. Implement CCR command for linux
nvme target.
Signed-off-by: Mohamed Khalfella <mkhalfella@purestorage.com>
---
drivers/nvme/target/admin-cmd.c | 79 +++++++++++++++++++++++++++++++++
drivers/nvme/target/core.c | 69 ++++++++++++++++++++++++++++
drivers/nvme/target/nvmet.h | 13 ++++++
include/linux/nvme.h | 23 ++++++++++
4 files changed, 184 insertions(+)
diff --git a/drivers/nvme/target/admin-cmd.c b/drivers/nvme/target/admin-cmd.c
index aaceb697e4d2..a55ca010d34f 100644
--- a/drivers/nvme/target/admin-cmd.c
+++ b/drivers/nvme/target/admin-cmd.c
@@ -376,7 +376,9 @@ static void nvmet_get_cmd_effects_admin(struct nvmet_ctrl *ctrl,
log->acs[nvme_admin_get_features] =
log->acs[nvme_admin_async_event] =
log->acs[nvme_admin_keep_alive] =
+ log->acs[nvme_admin_cross_ctrl_reset] =
cpu_to_le32(NVME_CMD_EFFECTS_CSUPP);
+
}
static void nvmet_get_cmd_effects_nvm(struct nvme_effects_log *log)
@@ -1615,6 +1617,80 @@ void nvmet_execute_keep_alive(struct nvmet_req *req)
nvmet_req_complete(req, status);
}
+void nvmet_execute_cross_ctrl_reset(struct nvmet_req *req)
+{
+ struct nvmet_ctrl *ictrl, *ctrl = req->sq->ctrl;
+ struct nvme_command *cmd = req->cmd;
+ struct nvmet_ccr *ccr, *new_ccr;
+ int ccr_active, ccr_total;
+ u16 cntlid, status = 0;
+
+ cntlid = le16_to_cpu(cmd->ccr.icid);
+ if (ctrl->cntlid == cntlid) {
+ req->error_loc =
+ offsetof(struct nvme_cross_ctrl_reset_cmd, icid);
+ status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
+ goto out;
+ }
+
+ ictrl = nvmet_ctrl_find_get_ccr(ctrl->subsys, ctrl->hostnqn,
+ cmd->ccr.ciu, cntlid,
+ le64_to_cpu(cmd->ccr.cirn));
+ if (!ictrl) {
+ /* Immediate Reset Successful */
+ nvmet_set_result(req, 1);
+ status = NVME_SC_SUCCESS;
+ goto out;
+ }
+
+ new_ccr = kmalloc(sizeof(*ccr), GFP_KERNEL);
+ if (!new_ccr) {
+ status = NVME_SC_INTERNAL;
+ goto out_put_ctrl;
+ }
+
+ ccr_total = ccr_active = 0;
+ mutex_lock(&ctrl->lock);
+ list_for_each_entry(ccr, &ctrl->ccrs, entry) {
+ if (ccr->ctrl == ictrl) {
+ status = NVME_SC_CCR_IN_PROGRESS | NVME_STATUS_DNR;
+ goto out_unlock;
+ }
+
+ ccr_total++;
+ if (ccr->ctrl)
+ ccr_active++;
+ }
+
+ if (ccr_active >= NVMF_CCR_LIMIT) {
+ status = NVME_SC_CCR_LIMIT_EXCEEDED;
+ goto out_unlock;
+ }
+ if (ccr_total >= NVMF_CCR_PER_PAGE) {
+ status = NVME_SC_CCR_LOGPAGE_FULL;
+ goto out_unlock;
+ }
+
+ new_ccr->ciu = cmd->ccr.ciu;
+ new_ccr->icid = cntlid;
+ new_ccr->ctrl = ictrl;
+ list_add_tail(&new_ccr->entry, &ctrl->ccrs);
+ mutex_unlock(&ctrl->lock);
+
+ nvmet_ctrl_fatal_error(ictrl);
+ nvmet_ctrl_put(ictrl);
+ nvmet_req_complete(req, 0);
+ return;
+
+out_unlock:
+ mutex_unlock(&ctrl->lock);
+ kfree(new_ccr);
+out_put_ctrl:
+ nvmet_ctrl_put(ictrl);
+out:
+ nvmet_req_complete(req, status);
+}
+
u32 nvmet_admin_cmd_data_len(struct nvmet_req *req)
{
struct nvme_command *cmd = req->cmd;
@@ -1692,6 +1768,9 @@ u16 nvmet_parse_admin_cmd(struct nvmet_req *req)
case nvme_admin_keep_alive:
req->execute = nvmet_execute_keep_alive;
return 0;
+ case nvme_admin_cross_ctrl_reset:
+ req->execute = nvmet_execute_cross_ctrl_reset;
+ return 0;
default:
return nvmet_report_invalid_opcode(req);
}
diff --git a/drivers/nvme/target/core.c b/drivers/nvme/target/core.c
index 409928202503..7dbe9255ff42 100644
--- a/drivers/nvme/target/core.c
+++ b/drivers/nvme/target/core.c
@@ -114,6 +114,20 @@ u16 nvmet_zero_sgl(struct nvmet_req *req, off_t off, size_t len)
return 0;
}
+void nvmet_ctrl_cleanup_ccrs(struct nvmet_ctrl *ctrl, bool all)
+{
+ struct nvmet_ccr *ccr, *tmp;
+
+ lockdep_assert_held(&ctrl->lock);
+
+ list_for_each_entry_safe(ccr, tmp, &ctrl->ccrs, entry) {
+ if (all || ccr->ctrl == NULL) {
+ list_del(&ccr->entry);
+ kfree(ccr);
+ }
+ }
+}
+
static u32 nvmet_max_nsid(struct nvmet_subsys *subsys)
{
struct nvmet_ns *cur;
@@ -1396,6 +1410,7 @@ static void nvmet_start_ctrl(struct nvmet_ctrl *ctrl)
if (!nvmet_is_disc_subsys(ctrl->subsys)) {
ctrl->uniquifier = ((u8)(ctrl->uniquifier + 1)) ? : 1;
ctrl->random = get_random_u64();
+ nvmet_ctrl_cleanup_ccrs(ctrl, false);
}
ctrl->csts = NVME_CSTS_RDY;
@@ -1501,6 +1516,38 @@ struct nvmet_ctrl *nvmet_ctrl_find_get(const char *subsysnqn,
return ctrl;
}
+struct nvmet_ctrl *nvmet_ctrl_find_get_ccr(struct nvmet_subsys *subsys,
+ const char *hostnqn, u8 ciu,
+ u16 cntlid, u64 cirn)
+{
+ struct nvmet_ctrl *ctrl;
+ bool found = false;
+
+ mutex_lock(&subsys->lock);
+ list_for_each_entry(ctrl, &subsys->ctrls, subsys_entry) {
+ if (ctrl->cntlid != cntlid)
+ continue;
+ if (strncmp(ctrl->hostnqn, hostnqn, NVMF_NQN_SIZE))
+ continue;
+
+ /* Avoid racing with a controller that is becoming ready */
+ mutex_lock(&ctrl->lock);
+ if (ctrl->uniquifier == ciu && ctrl->random == cirn)
+ found = true;
+ mutex_unlock(&ctrl->lock);
+
+ if (found) {
+ if (kref_get_unless_zero(&ctrl->ref))
+ goto out;
+ break;
+ }
+ };
+ ctrl = NULL;
+out:
+ mutex_unlock(&subsys->lock);
+ return ctrl;
+}
+
u16 nvmet_check_ctrl_status(struct nvmet_req *req)
{
if (unlikely(!(req->sq->ctrl->cc & NVME_CC_ENABLE))) {
@@ -1626,6 +1673,7 @@ struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args)
subsys->clear_ids = 1;
#endif
+ INIT_LIST_HEAD(&ctrl->ccrs);
INIT_WORK(&ctrl->async_event_work, nvmet_async_event_work);
INIT_LIST_HEAD(&ctrl->async_events);
INIT_RADIX_TREE(&ctrl->p2p_ns_map, GFP_KERNEL);
@@ -1740,12 +1788,33 @@ struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args)
}
EXPORT_SYMBOL_GPL(nvmet_alloc_ctrl);
+static void nvmet_ctrl_complete_pending_ccr(struct nvmet_ctrl *ctrl)
+{
+ struct nvmet_subsys *subsys = ctrl->subsys;
+ struct nvmet_ctrl *sctrl;
+ struct nvmet_ccr *ccr;
+
+ mutex_lock(&ctrl->lock);
+ nvmet_ctrl_cleanup_ccrs(ctrl, true);
+ mutex_unlock(&ctrl->lock);
+
+ list_for_each_entry(sctrl, &subsys->ctrls, subsys_entry) {
+ mutex_lock(&sctrl->lock);
+ list_for_each_entry(ccr, &sctrl->ccrs, entry) {
+ if (ccr->ctrl == ctrl)
+ ccr->ctrl = NULL;
+ }
+ mutex_unlock(&sctrl->lock);
+ }
+}
+
static void nvmet_ctrl_free(struct kref *ref)
{
struct nvmet_ctrl *ctrl = container_of(ref, struct nvmet_ctrl, ref);
struct nvmet_subsys *subsys = ctrl->subsys;
mutex_lock(&subsys->lock);
+ nvmet_ctrl_complete_pending_ccr(ctrl);
nvmet_ctrl_destroy_pr(ctrl);
nvmet_release_p2p_ns_map(ctrl);
list_del(&ctrl->subsys_entry);
diff --git a/drivers/nvme/target/nvmet.h b/drivers/nvme/target/nvmet.h
index 4195c9eff1da..6c0091b8af8b 100644
--- a/drivers/nvme/target/nvmet.h
+++ b/drivers/nvme/target/nvmet.h
@@ -267,6 +267,7 @@ struct nvmet_ctrl {
u32 kato;
u64 random;
+ struct list_head ccrs;
struct nvmet_port *port;
u32 aen_enabled;
@@ -314,6 +315,13 @@ struct nvmet_ctrl {
struct nvmet_pr_log_mgr pr_log_mgr;
};
+struct nvmet_ccr {
+ struct nvmet_ctrl *ctrl;
+ struct list_head entry;
+ u16 icid;
+ u8 ciu;
+};
+
struct nvmet_subsys {
enum nvme_subsys_type type;
@@ -576,6 +584,7 @@ void nvmet_req_free_sgls(struct nvmet_req *req);
void nvmet_execute_set_features(struct nvmet_req *req);
void nvmet_execute_get_features(struct nvmet_req *req);
void nvmet_execute_keep_alive(struct nvmet_req *req);
+void nvmet_execute_cross_ctrl_reset(struct nvmet_req *req);
u16 nvmet_check_cqid(struct nvmet_ctrl *ctrl, u16 cqid, bool create);
u16 nvmet_check_io_cqid(struct nvmet_ctrl *ctrl, u16 cqid, bool create);
@@ -618,6 +627,10 @@ struct nvmet_ctrl *nvmet_alloc_ctrl(struct nvmet_alloc_ctrl_args *args);
struct nvmet_ctrl *nvmet_ctrl_find_get(const char *subsysnqn,
const char *hostnqn, u16 cntlid,
struct nvmet_req *req);
+struct nvmet_ctrl *nvmet_ctrl_find_get_ccr(struct nvmet_subsys *subsys,
+ const char *hostnqn, u8 ciu,
+ u16 cntlid, u64 cirn);
+void nvmet_ctrl_cleanup_ccrs(struct nvmet_ctrl *ctrl, bool all);
void nvmet_ctrl_put(struct nvmet_ctrl *ctrl);
u16 nvmet_check_ctrl_status(struct nvmet_req *req);
ssize_t nvmet_ctrl_host_traddr(struct nvmet_ctrl *ctrl,
diff --git a/include/linux/nvme.h b/include/linux/nvme.h
index 5135cdc3c120..0f305b317aa3 100644
--- a/include/linux/nvme.h
+++ b/include/linux/nvme.h
@@ -23,6 +23,7 @@
#define NVMF_CQT_MS 0
#define NVMF_CCR_LIMIT 4
+#define NVMF_CCR_PER_PAGE 511
#define NVME_DISC_SUBSYS_NAME "nqn.2014-08.org.nvmexpress.discovery"
@@ -1225,6 +1226,22 @@ struct nvme_zone_mgmt_recv_cmd {
__le32 cdw14[2];
};
+struct nvme_cross_ctrl_reset_cmd {
+ __u8 opcode;
+ __u8 flags;
+ __u16 command_id;
+ __le32 nsid;
+ __le64 rsvd2[2];
+ union nvme_data_ptr dptr;
+ __u8 rsvd10;
+ __u8 ciu;
+ __le16 icid;
+ __le32 cdw11;
+ __le64 cirn;
+ __le32 cdw14;
+ __le32 cdw15;
+};
+
struct nvme_io_mgmt_recv_cmd {
__u8 opcode;
__u8 flags;
@@ -1323,6 +1340,7 @@ enum nvme_admin_opcode {
nvme_admin_virtual_mgmt = 0x1c,
nvme_admin_nvme_mi_send = 0x1d,
nvme_admin_nvme_mi_recv = 0x1e,
+ nvme_admin_cross_ctrl_reset = 0x38,
nvme_admin_dbbuf = 0x7C,
nvme_admin_format_nvm = 0x80,
nvme_admin_security_send = 0x81,
@@ -1356,6 +1374,7 @@ enum nvme_admin_opcode {
nvme_admin_opcode_name(nvme_admin_virtual_mgmt), \
nvme_admin_opcode_name(nvme_admin_nvme_mi_send), \
nvme_admin_opcode_name(nvme_admin_nvme_mi_recv), \
+ nvme_admin_opcode_name(nvme_admin_cross_ctrl_reset), \
nvme_admin_opcode_name(nvme_admin_dbbuf), \
nvme_admin_opcode_name(nvme_admin_format_nvm), \
nvme_admin_opcode_name(nvme_admin_security_send), \
@@ -2009,6 +2028,7 @@ struct nvme_command {
struct nvme_dbbuf dbbuf;
struct nvme_directive_cmd directive;
struct nvme_io_mgmt_recv_cmd imr;
+ struct nvme_cross_ctrl_reset_cmd ccr;
};
};
@@ -2173,6 +2193,9 @@ enum {
NVME_SC_PMR_SAN_PROHIBITED = 0x123,
NVME_SC_ANA_GROUP_ID_INVALID = 0x124,
NVME_SC_ANA_ATTACH_FAILED = 0x125,
+ NVME_SC_CCR_IN_PROGRESS = 0x13f,
+ NVME_SC_CCR_LOGPAGE_FULL = 0x140,
+ NVME_SC_CCR_LIMIT_EXCEEDED = 0x141,
/*
* I/O Command Set Specific - NVM commands:
--
2.51.2
On 26/11/2025 4:11, Mohamed Khalfella wrote:
> Defined by TP8028 Rapid Path Failure Recovery, CCR (Cross-Controller
> Reset) command is an nvme command the is issued to source controller by
> initiator to reset impacted controller. Implement CCR command for linux
> nvme target.
>
> Signed-off-by: Mohamed Khalfella <mkhalfella@purestorage.com>
> ---
> drivers/nvme/target/admin-cmd.c | 79 +++++++++++++++++++++++++++++++++
> drivers/nvme/target/core.c | 69 ++++++++++++++++++++++++++++
> drivers/nvme/target/nvmet.h | 13 ++++++
> include/linux/nvme.h | 23 ++++++++++
> 4 files changed, 184 insertions(+)
>
> diff --git a/drivers/nvme/target/admin-cmd.c b/drivers/nvme/target/admin-cmd.c
> index aaceb697e4d2..a55ca010d34f 100644
> --- a/drivers/nvme/target/admin-cmd.c
> +++ b/drivers/nvme/target/admin-cmd.c
> @@ -376,7 +376,9 @@ static void nvmet_get_cmd_effects_admin(struct nvmet_ctrl *ctrl,
> log->acs[nvme_admin_get_features] =
> log->acs[nvme_admin_async_event] =
> log->acs[nvme_admin_keep_alive] =
> + log->acs[nvme_admin_cross_ctrl_reset] =
> cpu_to_le32(NVME_CMD_EFFECTS_CSUPP);
> +
> }
>
> static void nvmet_get_cmd_effects_nvm(struct nvme_effects_log *log)
> @@ -1615,6 +1617,80 @@ void nvmet_execute_keep_alive(struct nvmet_req *req)
> nvmet_req_complete(req, status);
> }
>
> +void nvmet_execute_cross_ctrl_reset(struct nvmet_req *req)
> +{
> + struct nvmet_ctrl *ictrl, *ctrl = req->sq->ctrl;
> + struct nvme_command *cmd = req->cmd;
> + struct nvmet_ccr *ccr, *new_ccr;
> + int ccr_active, ccr_total;
> + u16 cntlid, status = 0;
> +
> + cntlid = le16_to_cpu(cmd->ccr.icid);
> + if (ctrl->cntlid == cntlid) {
> + req->error_loc =
> + offsetof(struct nvme_cross_ctrl_reset_cmd, icid);
> + status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
> + goto out;
> + }
> +
> + ictrl = nvmet_ctrl_find_get_ccr(ctrl->subsys, ctrl->hostnqn,
What does the 'i' stand for?
> + cmd->ccr.ciu, cntlid,
> + le64_to_cpu(cmd->ccr.cirn));
> + if (!ictrl) {
> + /* Immediate Reset Successful */
> + nvmet_set_result(req, 1);
> + status = NVME_SC_SUCCESS;
> + goto out;
> + }
> +
> + new_ccr = kmalloc(sizeof(*ccr), GFP_KERNEL);
> + if (!new_ccr) {
> + status = NVME_SC_INTERNAL;
> + goto out_put_ctrl;
> + }
Allocating this later when you actually use it would probably simplify
error path.
> +
> + ccr_total = ccr_active = 0;
> + mutex_lock(&ctrl->lock);
> + list_for_each_entry(ccr, &ctrl->ccrs, entry) {
> + if (ccr->ctrl == ictrl) {
> + status = NVME_SC_CCR_IN_PROGRESS | NVME_STATUS_DNR;
> + goto out_unlock;
> + }
> +
> + ccr_total++;
> + if (ccr->ctrl)
> + ccr_active++;
> + }
> +
> + if (ccr_active >= NVMF_CCR_LIMIT) {
> + status = NVME_SC_CCR_LIMIT_EXCEEDED;
> + goto out_unlock;
> + }
> + if (ccr_total >= NVMF_CCR_PER_PAGE) {
> + status = NVME_SC_CCR_LOGPAGE_FULL;
> + goto out_unlock;
> + }
> +
> + new_ccr->ciu = cmd->ccr.ciu;
> + new_ccr->icid = cntlid;
> + new_ccr->ctrl = ictrl;
> + list_add_tail(&new_ccr->entry, &ctrl->ccrs);
> + mutex_unlock(&ctrl->lock);
> +
> + nvmet_ctrl_fatal_error(ictrl);
Don't you need to wait for it to complete?
e.g. flush_work(&ictrl->fatal_err_work);
Or is that done async? will need to look downstream...
On Thu 2025-12-25 15:14:31 +0200, Sagi Grimberg wrote:
>
>
> On 26/11/2025 4:11, Mohamed Khalfella wrote:
> > Defined by TP8028 Rapid Path Failure Recovery, CCR (Cross-Controller
> > Reset) command is an nvme command the is issued to source controller by
> > initiator to reset impacted controller. Implement CCR command for linux
> > nvme target.
> >
> > Signed-off-by: Mohamed Khalfella <mkhalfella@purestorage.com>
> > ---
> > drivers/nvme/target/admin-cmd.c | 79 +++++++++++++++++++++++++++++++++
> > drivers/nvme/target/core.c | 69 ++++++++++++++++++++++++++++
> > drivers/nvme/target/nvmet.h | 13 ++++++
> > include/linux/nvme.h | 23 ++++++++++
> > 4 files changed, 184 insertions(+)
> >
> > diff --git a/drivers/nvme/target/admin-cmd.c b/drivers/nvme/target/admin-cmd.c
> > index aaceb697e4d2..a55ca010d34f 100644
> > --- a/drivers/nvme/target/admin-cmd.c
> > +++ b/drivers/nvme/target/admin-cmd.c
> > @@ -376,7 +376,9 @@ static void nvmet_get_cmd_effects_admin(struct nvmet_ctrl *ctrl,
> > log->acs[nvme_admin_get_features] =
> > log->acs[nvme_admin_async_event] =
> > log->acs[nvme_admin_keep_alive] =
> > + log->acs[nvme_admin_cross_ctrl_reset] =
> > cpu_to_le32(NVME_CMD_EFFECTS_CSUPP);
> > +
> > }
> >
> > static void nvmet_get_cmd_effects_nvm(struct nvme_effects_log *log)
> > @@ -1615,6 +1617,80 @@ void nvmet_execute_keep_alive(struct nvmet_req *req)
> > nvmet_req_complete(req, status);
> > }
> >
> > +void nvmet_execute_cross_ctrl_reset(struct nvmet_req *req)
> > +{
> > + struct nvmet_ctrl *ictrl, *ctrl = req->sq->ctrl;
> > + struct nvme_command *cmd = req->cmd;
> > + struct nvmet_ccr *ccr, *new_ccr;
> > + int ccr_active, ccr_total;
> > + u16 cntlid, status = 0;
> > +
> > + cntlid = le16_to_cpu(cmd->ccr.icid);
> > + if (ctrl->cntlid == cntlid) {
> > + req->error_loc =
> > + offsetof(struct nvme_cross_ctrl_reset_cmd, icid);
> > + status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
> > + goto out;
> > + }
> > +
> > + ictrl = nvmet_ctrl_find_get_ccr(ctrl->subsys, ctrl->hostnqn,
>
> What does the 'i' stand for?
'i' stands for impacted controller. Also, if you see sctrl the 's'
stands for source controller. These terms are from TP8028.
>
> > + cmd->ccr.ciu, cntlid,
> > + le64_to_cpu(cmd->ccr.cirn));
> > + if (!ictrl) {
> > + /* Immediate Reset Successful */
> > + nvmet_set_result(req, 1);
> > + status = NVME_SC_SUCCESS;
> > + goto out;
> > + }
> > +
> > + new_ccr = kmalloc(sizeof(*ccr), GFP_KERNEL);
> > + if (!new_ccr) {
> > + status = NVME_SC_INTERNAL;
> > + goto out_put_ctrl;
> > + }
>
> Allocating this later when you actually use it would probably simplify
> error path.
Right, it will save us kfree(). Will do that.
>
> > +
> > + ccr_total = ccr_active = 0;
> > + mutex_lock(&ctrl->lock);
> > + list_for_each_entry(ccr, &ctrl->ccrs, entry) {
> > + if (ccr->ctrl == ictrl) {
> > + status = NVME_SC_CCR_IN_PROGRESS | NVME_STATUS_DNR;
> > + goto out_unlock;
> > + }
> > +
> > + ccr_total++;
> > + if (ccr->ctrl)
> > + ccr_active++;
> > + }
> > +
> > + if (ccr_active >= NVMF_CCR_LIMIT) {
> > + status = NVME_SC_CCR_LIMIT_EXCEEDED;
> > + goto out_unlock;
> > + }
> > + if (ccr_total >= NVMF_CCR_PER_PAGE) {
> > + status = NVME_SC_CCR_LOGPAGE_FULL;
> > + goto out_unlock;
> > + }
> > +
> > + new_ccr->ciu = cmd->ccr.ciu;
> > + new_ccr->icid = cntlid;
> > + new_ccr->ctrl = ictrl;
> > + list_add_tail(&new_ccr->entry, &ctrl->ccrs);
> > + mutex_unlock(&ctrl->lock);
> > +
> > + nvmet_ctrl_fatal_error(ictrl);
>
> Don't you need to wait for it to complete?
> e.g. flush_work(&ictrl->fatal_err_work);
>
> Or is that done async? will need to look downstream...
No, we do not need to wait for ictrl->fatal_err_work to complete. An AEN
will be sent when ictrl exits. It is okay if AEN is sent before CCR
request is completed. The initiator should expect this behavior and deal
with it.
>>> +void nvmet_execute_cross_ctrl_reset(struct nvmet_req *req)
>>> +{
>>> + struct nvmet_ctrl *ictrl, *ctrl = req->sq->ctrl;
>>> + struct nvme_command *cmd = req->cmd;
>>> + struct nvmet_ccr *ccr, *new_ccr;
>>> + int ccr_active, ccr_total;
>>> + u16 cntlid, status = 0;
>>> +
>>> + cntlid = le16_to_cpu(cmd->ccr.icid);
>>> + if (ctrl->cntlid == cntlid) {
>>> + req->error_loc =
>>> + offsetof(struct nvme_cross_ctrl_reset_cmd, icid);
>>> + status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
>>> + goto out;
>>> + }
>>> +
>>> + ictrl = nvmet_ctrl_find_get_ccr(ctrl->subsys, ctrl->hostnqn,
>> What does the 'i' stand for?
> 'i' stands for impacted controller. Also, if you see sctrl the 's'
> stands for source controller. These terms are from TP8028.
Can you perhaps add a comment on this?
>>> + new_ccr->ciu = cmd->ccr.ciu;
>>> + new_ccr->icid = cntlid;
>>> + new_ccr->ctrl = ictrl;
>>> + list_add_tail(&new_ccr->entry, &ctrl->ccrs);
>>> + mutex_unlock(&ctrl->lock);
>>> +
>>> + nvmet_ctrl_fatal_error(ictrl);
>> Don't you need to wait for it to complete?
>> e.g. flush_work(&ictrl->fatal_err_work);
>>
>> Or is that done async? will need to look downstream...
> No, we do not need to wait for ictrl->fatal_err_work to complete. An AEN
> will be sent when ictrl exits. It is okay if AEN is sent before CCR
> request is completed. The initiator should expect this behavior and deal
> with it.
Yes, saw that in a later patch (didn't get to do a full review yet)
On Sat 2025-12-27 11:39:55 +0200, Sagi Grimberg wrote:
>
> >>> +void nvmet_execute_cross_ctrl_reset(struct nvmet_req *req)
> >>> +{
> >>> + struct nvmet_ctrl *ictrl, *ctrl = req->sq->ctrl;
> >>> + struct nvme_command *cmd = req->cmd;
> >>> + struct nvmet_ccr *ccr, *new_ccr;
> >>> + int ccr_active, ccr_total;
> >>> + u16 cntlid, status = 0;
> >>> +
> >>> + cntlid = le16_to_cpu(cmd->ccr.icid);
> >>> + if (ctrl->cntlid == cntlid) {
> >>> + req->error_loc =
> >>> + offsetof(struct nvme_cross_ctrl_reset_cmd, icid);
> >>> + status = NVME_SC_INVALID_FIELD | NVME_STATUS_DNR;
> >>> + goto out;
> >>> + }
> >>> +
> >>> + ictrl = nvmet_ctrl_find_get_ccr(ctrl->subsys, ctrl->hostnqn,
> >> What does the 'i' stand for?
> > 'i' stands for impacted controller. Also, if you see sctrl the 's'
> > stands for source controller. These terms are from TP8028.
>
> Can you perhaps add a comment on this?
Okay, will do that.
>
> >>> + new_ccr->ciu = cmd->ccr.ciu;
> >>> + new_ccr->icid = cntlid;
> >>> + new_ccr->ctrl = ictrl;
> >>> + list_add_tail(&new_ccr->entry, &ctrl->ccrs);
> >>> + mutex_unlock(&ctrl->lock);
> >>> +
> >>> + nvmet_ctrl_fatal_error(ictrl);
> >> Don't you need to wait for it to complete?
> >> e.g. flush_work(&ictrl->fatal_err_work);
> >>
> >> Or is that done async? will need to look downstream...
> > No, we do not need to wait for ictrl->fatal_err_work to complete. An AEN
> > will be sent when ictrl exits. It is okay if AEN is sent before CCR
> > request is completed. The initiator should expect this behavior and deal
> > with it.
>
> Yes, saw that in a later patch (didn't get to do a full review yet)
On Tue, Nov 25, 2025 at 6:13 PM Mohamed Khalfella <mkhalfella@purestorage.com> wrote: > > Defined by TP8028 Rapid Path Failure Recovery, CCR (Cross-Controller > Reset) command is an nvme command the is issued to source controller by > initiator to reset impacted controller. Implement CCR command for linux > nvme target. Remove extraneous "the is" in second line. > > Signed-off-by: Mohamed Khalfella <mkhalfella@purestorage.com> Reviewed-by: Randy Jennings <randyj@purestorage.com>
On Mon 2025-12-15 19:01:30 -0800, Randy Jennings wrote: > On Tue, Nov 25, 2025 at 6:13 PM Mohamed Khalfella > <mkhalfella@purestorage.com> wrote: > > > > Defined by TP8028 Rapid Path Failure Recovery, CCR (Cross-Controller > > Reset) command is an nvme command the is issued to source controller by > > initiator to reset impacted controller. Implement CCR command for linux > > nvme target. > Remove extraneous "the is" in second line. Removed. > > > > > Signed-off-by: Mohamed Khalfella <mkhalfella@purestorage.com> > > Reviewed-by: Randy Jennings <randyj@purestorage.com>
© 2016 - 2026 Red Hat, Inc.