[RFC 1/1] nvme: Add NVMe LBA Fault Injection

Alan Adamson alan.adamson at oracle.com
Thu Dec 21 15:10:43 PST 2023


Add support for injecting a fault when reading or writing a specific
Logical Block Address (LBA).

Steps to setup a fault on a LBA access.

1. Setup the attributes: probability, times, and status according to
the original NVMe fault injector.

echo 100 > /sys/kernel/debug/nvme0n1/fault_inject/probability
echo 1 > /sys/kernel/debug/nvme0n1/fault_inject/times
echo 1 > /sys/kernel/debug/nvme0n1/fault_inject/status

2. Specify one or more LBAs: To configure a LBA, set the lba-inject-set
attribute to the LBA.

echo 0x22 > /sys/kernel/debug/nvme0n1/fault_inject/lba-inject-set

3. Enable the LBA injector: by setting the lba-inject-enable attribute
to on.

echo on > /sys/kernel/debug/nvme0n1/fault_inject/lba-inject-enable

To display the LBAs configured:

cat /sys/kernel/debug/nvme0n1/fault_inject/lba-inject-show
LBA  Status
22   1

To stop injecting a fault on a LBA:

echo 1 > /sys/kernel/debug/nvme0n1/fault_inject/lba-inject-clear
echo 0x22 > /sys/kernel/debug/nvme0n1/fault_inject/lba-inject-set

Signed-off-by: Alan Adamson <alan.adamson at oracle.com>
---
 .../fault-injection/nvme-fault-injection.rst  |  28 ++++
 drivers/nvme/host/core.c                      |   4 +-
 drivers/nvme/host/fault_inject.c              | 145 ++++++++++++++++--
 drivers/nvme/host/nvme.h                      |  16 +-
 4 files changed, 178 insertions(+), 15 deletions(-)

diff --git a/Documentation/fault-injection/nvme-fault-injection.rst b/Documentation/fault-injection/nvme-fault-injection.rst
index 1d4427890d75..e17f49886fc4 100644
--- a/Documentation/fault-injection/nvme-fault-injection.rst
+++ b/Documentation/fault-injection/nvme-fault-injection.rst
@@ -176,3 +176,31 @@ Message from dmesg::
    secondary_startup_64+0xa4/0xb0
   nvme nvme0: Could not set queue count (16385)
   nvme nvme0: IO queues not created
+
+Example 4: Inject an error when reading or writing Logical Block Addrss (LBA) 0x22
+----------------------------------------------------------------------------------
+
+::
+
+  echo 1 > /sys/kernel/debug/nvme0n1/fault_inject/times
+  echo 100 > /sys/kernel/debug/nvme0n1/fault_inject/probability
+  echo 1 > /sys/kernel/debug/nvme0n1/fault_inject/status
+  echo 1 > /sys/kernel/debug/nvme0n1/fault_inject/dont_retry
+  echo 0x22 > /sys/kernel/debug/nvme0n1/fault_inject/lba-inject-set
+  cat /sys/kernel/debug/nvme0n1/fault_inject/lba-inject-show
+  LBA	Status
+  21	1
+
+  echo 1 > /sys/kernel/debug/nvme0n1/fault_inject/lba-inject-enable
+  dd if=/dev/nvme0n1 of=/dev/null iflag=direct count=100
+  dd: error reading '/dev/nvme0n1': Operation not supported
+  33+0 records in
+  33+0 records out
+  16896 bytes (17 kB, 16 KiB) copied, 0.00711112 s, 2.4 MB/s
+
+  echo 1 > /sys/kernel/debug/nvme0n1/fault_inject/lba-inject-clear
+  echo 0x22 > /sys/kernel/debug/nvme0n1/fault_inject/lba-inject-set
+  cat /sys/kernel/debug/nvme0n1/fault_inject/lba-inject-show
+  LBA   Status
+
+  echo 0 > /sys/kernel/debug/nvme0n1/fault_inject/lba-inject-enable
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index 8ebdfd623e0f..435ef8b475c0 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -3681,7 +3681,7 @@ static void nvme_alloc_ns(struct nvme_ctrl *ctrl, struct nvme_ns_info *info)
 		nvme_add_ns_cdev(ns);
 
 	nvme_mpath_add_disk(ns, info->anagrpid);
-	nvme_fault_inject_init(&ns->fault_inject, ns->disk->disk_name);
+	nvme_fault_inject_init(&ns->fault_inject, ns->disk->disk_name, 1);
 
 	return;
 
@@ -4548,7 +4548,7 @@ int nvme_init_ctrl(struct nvme_ctrl *ctrl, struct device *dev,
 	dev_pm_qos_update_user_latency_tolerance(ctrl->device,
 		min(default_ps_max_latency_us, (unsigned long)S32_MAX));
 
-	nvme_fault_inject_init(&ctrl->fault_inject, dev_name(ctrl->device));
+	nvme_fault_inject_init(&ctrl->fault_inject, dev_name(ctrl->device), 0);
 	nvme_mpath_init_ctrl(ctrl);
 	ret = nvme_auth_init_ctrl(ctrl);
 	if (ret)
diff --git a/drivers/nvme/host/fault_inject.c b/drivers/nvme/host/fault_inject.c
index 1ba10a5c656d..81107ab66e21 100644
--- a/drivers/nvme/host/fault_inject.c
+++ b/drivers/nvme/host/fault_inject.c
@@ -5,6 +5,8 @@
  * Copyright (c) 2018, Oracle and/or its affiliates
  */
 
+#include <linux/kernel.h>
+#include <linux/module.h>
 #include <linux/moduleparam.h>
 #include "nvme.h"
 
@@ -15,8 +17,88 @@ static DECLARE_FAULT_ATTR(fail_default_attr);
 static char *fail_request;
 module_param(fail_request, charp, 0000);
 
+static void nvme_free_lba_block(struct lba_blk *lba_block)
+{
+	list_del(&lba_block->node);
+	kfree(lba_block);
+}
+
+static void nvme_free_all_lba_blocks(struct nvme_fault_inject *fault_inj)
+{
+	struct lba_blk *lba_block;
+	struct list_head *list_ptr, *tmp;
+
+	list_for_each_prev_safe(list_ptr, tmp, &fault_inj->lba_list) {
+		lba_block = list_entry(list_ptr, struct lba_blk, node);
+		list_del(list_ptr);
+		kfree(lba_block);
+	}
+}
+
+static int nvme_lba_inject_set(void *data, u64 val)
+{
+	struct nvme_fault_inject *fault_inj = data;
+	struct lba_blk *lba_block;
+	int lba;
+	int ret = 0;
+
+	lba =  val;
+	mutex_lock(&fault_inj->lba_block_lock);
+	if (fault_inj->lba_clear) {
+		list_for_each_entry(lba_block,
+		    &fault_inj->lba_list, node) {
+			if (lba_block->lba == lba) {
+				nvme_free_lba_block(lba_block);
+				fault_inj->lba_clear = 0;
+				mutex_unlock(&fault_inj->lba_block_lock);
+				return ret;
+			}
+		}
+		fault_inj->lba_clear = 0;
+		mutex_unlock(&fault_inj->lba_block_lock);
+		return ret;
+	}
+
+	list_for_each_entry(lba_block, &fault_inj->lba_list, node) {
+		if (lba_block->lba == lba) {
+			lba_block->status = fault_inj->status;
+			mutex_unlock(&fault_inj->lba_block_lock);
+			return ret;
+		}
+	}
+
+	lba_block = kmalloc(sizeof(struct lba_blk), GFP_KERNEL);
+	lba_block->lba = lba;
+	lba_block->status = fault_inj->status;
+
+	list_add(&lba_block->node, &fault_inj->lba_list);
+
+	mutex_unlock(&fault_inj->lba_block_lock);
+	return ret;
+}
+
+static int nvme_lba_inject_show(struct seq_file *file, void *data)
+{
+	struct nvme_fault_inject *fault_inj = file->private;
+	struct lba_blk *lba_block;
+
+	seq_printf(file, "LBA	Status\n");
+	mutex_lock(&fault_inj->lba_block_lock);
+	list_for_each_entry(lba_block, &fault_inj->lba_list, node) {
+		seq_printf(file, "%llx	%x\n",
+		    lba_block->lba, lba_block->status);
+	}
+	mutex_unlock(&fault_inj->lba_block_lock);
+
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(nvme_lba_inject);
+DEFINE_DEBUGFS_ATTRIBUTE(nvme_lba_inject_set_fops, NULL,
+			 nvme_lba_inject_set, "%llu\n");
+
 void nvme_fault_inject_init(struct nvme_fault_inject *fault_inj,
-			    const char *dev_name)
+			    const char *dev_name, bool lba_inject)
 {
 	struct dentry *dir, *parent;
 	struct fault_attr *attr = &fault_inj->attr;
@@ -41,15 +123,34 @@ void nvme_fault_inject_init(struct nvme_fault_inject *fault_inj,
 	}
 	fault_inj->parent = parent;
 
+	INIT_LIST_HEAD(&fault_inj->lba_list);
+
 	/* create debugfs for status code and dont_retry */
 	fault_inj->status = NVME_SC_INVALID_OPCODE;
 	fault_inj->dont_retry = true;
+	fault_inj->lba_clear = 0;
+	fault_inj->lba_inject_enable = false;
+	mutex_init(&fault_inj->lba_block_lock);
+
 	debugfs_create_x16("status", 0600, dir,	&fault_inj->status);
 	debugfs_create_bool("dont_retry", 0600, dir, &fault_inj->dont_retry);
+	if (lba_inject) {
+		debugfs_create_bool("lba-inject-clear", 0600, dir, &fault_inj->lba_clear);
+		debugfs_create_bool("lba-inject-enable", 0600, dir,
+		    &fault_inj->lba_inject_enable);
+		debugfs_create_file("lba-inject-show", 0600, dir,
+		    fault_inj, &nvme_lba_inject_fops);
+		debugfs_create_file_unsafe("lba-inject-set", 0600, dir,
+		    fault_inj, &nvme_lba_inject_set_fops);
+	}
 }
 
 void nvme_fault_inject_fini(struct nvme_fault_inject *fault_inject)
 {
+        mutex_lock(&fault_inject->lba_block_lock);
+	nvme_free_all_lba_blocks(fault_inject);
+        mutex_unlock(&fault_inject->lba_block_lock);
+
 	/* remove debugfs directories */
 	debugfs_remove_recursive(fault_inject->parent);
 }
@@ -58,25 +159,47 @@ void nvme_should_fail(struct request *req)
 {
 	struct gendisk *disk = req->q->disk;
 	struct nvme_fault_inject *fault_inject = NULL;
+	struct nvme_ns *ns = NULL;
+	struct lba_blk *lba_block = NULL;
 	u16 status;
 
 	if (disk) {
-		struct nvme_ns *ns = disk->private_data;
-
+		ns = disk->private_data;
 		if (ns)
 			fault_inject = &ns->fault_inject;
 		else
 			WARN_ONCE(1, "No namespace found for request\n");
-	} else {
+	} else
 		fault_inject = &nvme_req(req)->ctrl->fault_inject;
-	}
 
-	if (fault_inject && should_fail(&fault_inject->attr, 1)) {
-		/* inject status code and DNR bit */
-		status = fault_inject->status;
-		if (fault_inject->dont_retry)
-			status |= NVME_SC_DNR;
-		nvme_req(req)->status =	status;
+	if (!fault_inject || !should_fail(&fault_inject->attr, 1))
+		return;
+
+	mutex_lock(&fault_inject->lba_block_lock);
+
+	if (fault_inject->lba_inject_enable) {
+		u64 req_lba_start, req_lba_end;
+
+		req_lba_start = blk_rq_pos(req);
+		req_lba_end = req_lba_start + blk_rq_sectors(req) - 1;
+
+		list_for_each_entry(lba_block, &fault_inject->lba_list, node) {
+			if ((lba_block->lba >= req_lba_start) &&
+			    (lba_block->lba <= req_lba_end))
+				goto inject_fault;
+		}
+
+		mutex_unlock(&fault_inject->lba_block_lock);
+		return;
 	}
+
+inject_fault:
+	mutex_unlock(&fault_inject->lba_block_lock);
+
+	/* inject status code and DNR bit */
+	status = fault_inject->status;
+	if (fault_inject->dont_retry)
+		status |= NVME_SC_DNR;
+	nvme_req(req)->status =	status;
 }
 EXPORT_SYMBOL_GPL(nvme_should_fail);
diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
index e7411dac00f7..447ce832613c 100644
--- a/drivers/nvme/host/nvme.h
+++ b/drivers/nvme/host/nvme.h
@@ -16,6 +16,7 @@
 #include <linux/rcupdate.h>
 #include <linux/wait.h>
 #include <linux/t10-pi.h>
+#include <linux/list.h>
 
 #include <trace/events/block.h>
 
@@ -245,7 +246,11 @@ struct nvme_fault_inject {
 	struct fault_attr attr;
 	struct dentry *parent;
 	bool dont_retry;	/* DNR, do not retry */
+	bool lba_clear;
+	bool lba_inject_enable;
 	u16 status;		/* status code */
+	struct mutex		lba_block_lock;
+	struct list_head	lba_list;
 #endif
 };
 
@@ -484,6 +489,13 @@ enum nvme_ns_features {
 	NVME_NS_DEAC,		/* DEAC bit in Write Zeores supported */
 };
 
+typedef struct lba_blk {
+	u64			lba;
+	u16			status;
+	u32			times;
+	struct list_head	node;
+} lba_blk_t;
+
 struct nvme_ns {
 	struct list_head list;
 
@@ -621,12 +633,12 @@ static inline void nvme_print_device_info(struct nvme_ctrl *ctrl)
 
 #ifdef CONFIG_FAULT_INJECTION_DEBUG_FS
 void nvme_fault_inject_init(struct nvme_fault_inject *fault_inj,
-			    const char *dev_name);
+			    const char *dev_name, bool lba_inject);
 void nvme_fault_inject_fini(struct nvme_fault_inject *fault_inject);
 void nvme_should_fail(struct request *req);
 #else
 static inline void nvme_fault_inject_init(struct nvme_fault_inject *fault_inj,
-					  const char *dev_name)
+					  const char *dev_name, bool lba_inject)
 {
 }
 static inline void nvme_fault_inject_fini(struct nvme_fault_inject *fault_inj)
-- 
2.39.3




More information about the Linux-nvme mailing list