[PATCH 3/7] nvme: export command error counters via sysfs

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

When an NVMe command completes with an error status, the driver
logs the error to the kernel log. However, these messages may be
lost or overwritten over time since dmesg is a circular buffer.

Expose per-path and ctrl command error counters through sysfs to
provide persistent visibility into error occurrences. This allows
users to observe the total number of commands that have failed on
a given path over time, which can be useful for diagnosing path
health and stability.

These counters can also be consumed by observability tools such as
nvme-top to provide additional insight into NVMe error behavior.

Signed-off-by: Nilay Shroff <nilay@linux.ibm.com>
---
drivers/nvme/host/core.c | 16 ++++++++++++++--
drivers/nvme/host/nvme.h | 2 ++
drivers/nvme/host/sysfs.c | 29 +++++++++++++++++++++++++++++
3 files changed, 45 insertions(+), 2 deletions(-)

diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index d6490cc2a8e3..d16a3f4cc466 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -440,11 +440,23 @@ static inline void nvme_end_req_zoned(struct request *req)

static inline void __nvme_end_req(struct request *req)
{
+ struct nvme_ns *ns = req->q->queuedata;
+
if (unlikely(nvme_req(req)->status && !(req->rq_flags & RQF_QUIET))) {
- if (blk_rq_is_passthrough(req))
+ if (blk_rq_is_passthrough(req)) {
nvme_log_err_passthru(req);
- else
+ if (ns)
+ ns->errors++;
+ else {
+ struct nvme_request *nr = nvme_req(req);
+ struct nvme_ctrl *ctrl = nr->ctrl;
+
+ ctrl->errors++;
+ }
+ } else {
nvme_log_error(req);
+ ns->errors++;
+ }
}
nvme_end_req_zoned(req);
nvme_trace_bio_complete(req);
diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
index 119ba2344039..b7e46bdd2d59 100644
--- a/drivers/nvme/host/nvme.h
+++ b/drivers/nvme/host/nvme.h
@@ -360,6 +360,7 @@ struct nvme_ctrl {
struct work_struct fw_act_work;
unsigned long events;
u64 retries;
+ u64 errors;
#ifdef CONFIG_NVME_MULTIPATH
/* asymmetric namespace access: */
u8 anacap;
@@ -537,6 +538,7 @@ struct nvme_ns {
u64 failover;
#endif
u64 retries;
+ u64 errors;
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 8fc593c36b74..41218fa5081f 100644
--- a/drivers/nvme/host/sysfs.c
+++ b/drivers/nvme/host/sysfs.c
@@ -6,6 +6,7 @@
*/

#include <linux/nvme-auth.h>
+#include <linux/blkdev.h>

#include "nvme.h"
#include "fabrics.h"
@@ -257,6 +258,16 @@ static struct device_attribute dev_attr_io_command_retries =
__ATTR(command_retry_count, 0444,
nvme_io_command_retries_show, NULL);

+static ssize_t nvme_io_errors_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->errors);
+}
+struct device_attribute dev_attr_io_errors =
+ __ATTR(command_error_count, 0444, nvme_io_errors_show, NULL);
+
static struct attribute *nvme_ns_attrs[] = {
&dev_attr_wwid.attr,
&dev_attr_uuid.attr,
@@ -276,6 +287,7 @@ static struct attribute *nvme_ns_attrs[] = {
#endif
&dev_attr_io_passthru_err_log_enabled.attr,
&dev_attr_io_command_retries.attr,
+ &dev_attr_io_errors.attr,
NULL,
};

@@ -301,6 +313,12 @@ static umode_t nvme_ns_attrs_are_visible(struct kobject *kobj,
if (a == &dev_attr_io_command_retries.attr) {
struct gendisk *disk = dev_to_disk(dev);

+ if (nvme_disk_is_ns_head(disk))
+ return 0;
+ }
+ if (a == &dev_attr_io_errors.attr) {
+ struct gendisk *disk = dev_to_disk(dev);
+
if (nvme_disk_is_ns_head(disk))
return 0;
}
@@ -635,6 +653,16 @@ static struct device_attribute dev_attr_adm_command_retries =
__ATTR(command_retry_count, 0444,
nvme_adm_command_retries_show, NULL);

+static ssize_t nvme_adm_errors_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->errors);
+}
+struct device_attribute dev_attr_adm_errors =
+ __ATTR(command_error_count, 0444, nvme_adm_errors_show, NULL);
+
#ifdef CONFIG_NVME_HOST_AUTH
static ssize_t nvme_ctrl_dhchap_secret_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -782,6 +810,7 @@ static struct attribute *nvme_dev_attrs[] = {
#endif
&dev_attr_adm_passthru_err_log_enabled.attr,
&dev_attr_adm_command_retries.attr,
+ &dev_attr_adm_errors.attr,
NULL
};

--
2.52.0