[PATCH 1/7] nvme: export command retry count via sysfs

Nilay Shroff
2026-01-30T18:21:33Z

When Advanced Command Retry Enable (ACRE) is configured, a controller
may interrupt command execution and return a completion status
indicating command interrupted with the DNR bit cleared. In this case,
the driver retries the command based on the Command Retry Delay (CRD)
value provided in the completion status.

Currently, these command retries are handled entirely within the NVMe
driver and are not visible to userspace. As a result, there is no
observability into retry behavior, which can be a useful diagnostic
signal.

Expose the command retries count through sysfs to provide visibility
into retry activity. This information can help identify controller-side
congestion under load and enables comparison across paths in multipath
setups (for example, detecting cases where one path experiences
significantly more retries than another under identical workloads).

This exported metric is intended for diagnostics and monitoring tools
such as nvme-top, and does not change command retry behavior.

Signed-off-by: Nilay Shroff <nilay@linux.ibm.com>
---
drivers/nvme/host/core.c | 6 ++++++
drivers/nvme/host/nvme.h | 3 ++-
drivers/nvme/host/sysfs.c | 30 ++++++++++++++++++++++++++++++
3 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index 7bf228df6001..d6490cc2a8e3 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -323,6 +323,7 @@ static void nvme_retry_req(struct request *req)
{
unsigned long delay = 0;
u16 crd;
+ struct nvme_ns *ns = req->q->queuedata;

/* The mask and shift result must be <= 3 */
crd = (nvme_req(req)->status & NVME_STATUS_CRD) >> 11;
@@ -330,6 +331,11 @@ static void nvme_retry_req(struct request *req)
delay = nvme_req(req)->ctrl->crdt[crd - 1] * 100;

nvme_req(req)->retries++;
+ if (ns)
+ ns->retries++;
+ else
+ nvme_req(req)->ctrl->retries++;
+
blk_mq_requeue_request(req, false);
blk_mq_delay_kick_requeue_list(req->q, delay);
}
diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
index 9a5f28c5103c..d8a2831ed34c 100644
--- a/drivers/nvme/host/nvme.h
+++ b/drivers/nvme/host/nvme.h
@@ -359,7 +359,7 @@ struct nvme_ctrl {
unsigned long ka_last_check_time;
struct work_struct fw_act_work;
unsigned long events;
-
+ u64 retries;
#ifdef CONFIG_NVME_MULTIPATH
/* asymmetric namespace access: */
u8 anacap;
@@ -535,6 +535,7 @@ struct nvme_ns {
enum nvme_ana_state ana_state;
u32 ana_grpid;
#endif
+ u64 retries;
struct list_head siblings;
struct kref kref;
struct nvme_ns_head *head;
diff --git a/drivers/nvme/host/sysfs.c b/drivers/nvme/host/sysfs.c
index 29430949ce2f..c1e27088a053 100644
--- a/drivers/nvme/host/sysfs.c
+++ b/drivers/nvme/host/sysfs.c
@@ -246,6 +246,17 @@ static ssize_t nuse_show(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_RO(nuse);

+static ssize_t nvme_io_command_retries_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct nvme_ns *ns = nvme_get_ns_from_dev(dev);
+
+ return sysfs_emit(buf, "%llu\n", ns->retries);
+}
+static struct device_attribute dev_attr_io_command_retries =
+ __ATTR(command_retry_count, 0444,
+ nvme_io_command_retries_show, NULL);
+
static struct attribute *nvme_ns_attrs[] = {
&dev_attr_wwid.attr,
&dev_attr_uuid.attr,
@@ -263,6 +274,7 @@ static struct attribute *nvme_ns_attrs[] = {
&dev_attr_delayed_removal_secs.attr,
#endif
&dev_attr_io_passthru_err_log_enabled.attr,
+ &dev_attr_io_command_retries.attr,
NULL,
};

@@ -285,6 +297,12 @@ static umode_t nvme_ns_attrs_are_visible(struct kobject *kobj,
if (!memchr_inv(ids->eui64, 0, sizeof(ids->eui64)))
return 0;
}
+ if (a == &dev_attr_io_command_retries.attr) {
+ struct gendisk *disk = dev_to_disk(dev);
+
+ if (nvme_disk_is_ns_head(disk))
+ return 0;
+ }
#ifdef CONFIG_NVME_MULTIPATH
if (a == &dev_attr_ana_grpid.attr || a == &dev_attr_ana_state.attr) {
/* per-path attr */
@@ -601,6 +619,17 @@ static ssize_t dctype_show(struct device *dev,
}
static DEVICE_ATTR_RO(dctype);

+static ssize_t nvme_adm_command_retries_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct nvme_ctrl *ctrl = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%llu\n", ctrl->retries);
+}
+static struct device_attribute dev_attr_adm_command_retries =
+ __ATTR(command_retry_count, 0444,
+ nvme_adm_command_retries_show, NULL);
+
#ifdef CONFIG_NVME_HOST_AUTH
static ssize_t nvme_ctrl_dhchap_secret_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -747,6 +776,7 @@ static struct attribute *nvme_dev_attrs[] = {
&dev_attr_dhchap_ctrl_secret.attr,
#endif
&dev_attr_adm_passthru_err_log_enabled.attr,
+ &dev_attr_adm_command_retries.attr,
NULL
};

--
2.52.0