[PATCH] nvme: add opcode filtering for fault injection

Mohamed Khalfella <[email protected]>
Newsgroups org.infradead.lists.linux-nvme,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Currently NVMe fault injection applies to every command routed through
nvme_should_fail(), which makes it hard to target a specific command
type when reproducing an issue in error-handling paths.

Add an "opcode" debugfs attribute alongside the existing "status" and
"dont_retry" knobs. It defaults to 0xffff, meaning "match any opcode"
and preserving the previous behavior. When set to a valid opcode
(<= 0xff), fault injection is only considered for commands whose opcode
matches.

Signed-off-by: Mohamed Khalfella <[email protected]>
---
 .../fault-injection/nvme-fault-injection.rst  | 65 +++++++++++++++++++
 drivers/nvme/host/fault_inject.c              | 14 +++-
 drivers/nvme/host/nvme.h                      |  1 +
 3 files changed, 78 insertions(+), 2 deletions(-)

diff --git a/Documentation/fault-injection/nvme-fault-injection.rst b/Documentation/fault-injection/nvme-fault-injection.rst
index 1d4427890d75..09730acf0163 100644
--- a/Documentation/fault-injection/nvme-fault-injection.rst
+++ b/Documentation/fault-injection/nvme-fault-injection.rst
@@ -176,3 +176,68 @@ 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 into the first write command
+-------------------------------------------------------
+
+::
+
+  echo 0x01 > /sys/kernel/debug/nvme0n1/fault_inject/opcode
+  echo 1 > /sys/kernel/debug/nvme0n1/fault_inject/times
+  echo 100 > /sys/kernel/debug/nvme0n1/fault_inject/probability
+  dd if=/dev/zero of=/dev/nvme0n1 oflag=direct bs=512 count=1
+
+Expected Result::
+
+  The first write command sent to nvme0n1 fails
+
+Message from dmesg::
+
+  FAULT_INJECTION: forcing a failure.
+  name fault_inject, interval 1, probability 100, space 0, times 1
+  CPU: 4 UID: 0 PID: 0 Comm: swapper/4 Not tainted 7.1.0+ #5 PREEMPT(full)
+  Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-20240910_120124-localhost 04/01/2014
+  Call Trace:
+   <IRQ>
+   dump_stack_lvl+0x6e/0xa0
+   dump_stack+0x10/0x16
+   should_fail_ex+0x461/0x510
+   should_fail+0xb/0x20
+   nvme_should_fail+0x11b/0x240 [nvme_core]
+   nvme_poll_cq+0x6ad/0xb30 [nvme]
+   nvme_irq+0x84/0xe0 [nvme]
+   ? __pfx_nvme_irq+0x10/0x10 [nvme]
+   ? rcu_core+0xa40/0xa90
+   ? __pfx_sched_balance_softirq+0x10/0x10
+   ? debug_smp_processor_id+0x17/0x20
+   ? rcu_is_watching+0x13/0xa0
+   __handle_irq_event_percpu+0x396/0x610
+   handle_irq_event_percpu+0xf/0x90
+   handle_irq_event+0xab/0x110
+   handle_edge_irq+0x1a3/0x210
+   __common_interrupt+0xff/0x170
+   common_interrupt+0x90/0xc0
+   </IRQ>
+   <TASK>
+   asm_common_interrupt+0x27/0x40
+  RIP: 0010:pv_native_safe_halt+0x13/0x20
+  Code: 1f 84 00 00 00 00 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 8b 05 0a 2a 58 01 85 c0 7e 07 0f 00 2d ff cc 0d 00 fb f4 <c3> cc 0
+  RSP: 0018:ffff888100a67e40 EFLAGS: 00000242
+  RAX: 0000000000000001 RBX: ffff888100a49c40 RCX: ffffed102b6c645b
+  RDX: ffffed102b6c645b RSI: ffffffff82a0d3c0 RDI: ffffffff81428b9b
+  RBP: ffff888100a67e48 R08: ffffed102b6c645b R09: 0000000000000004
+  R10: ffffed102b6c645a R11: 0000000000000001 R12: 0000000000000000
+  R13: 0000000000000000 R14: ffffed1020149388 R15: dffffc0000000000
+   ? do_idle+0x19b/0x2c0
+   ? default_idle+0x9/0x20
+   arch_cpu_idle+0x9/0x10
+   default_idle_call+0x6b/0xa0
+   do_idle+0x19b/0x2c0
+   ? __pfx_do_idle+0x10/0x10
+   ? complete_with_flags+0x63/0x70
+   cpu_startup_entry+0x55/0x60
+   start_secondary+0x1df/0x1e0
+   common_startup_64+0x13e/0x158
+   </TASK>
+  nvme0n1: Write(0x1) @ LBA 0, 1 blocks, Invalid Command Opcode (sct 0x0 / sc 0x1) DNR
+  operation not supported error, dev nvme0n1, sector 0 op 0x1:(WRITE) flags 0x8800 phys_seg 1 prio class 2
diff --git a/drivers/nvme/host/fault_inject.c b/drivers/nvme/host/fault_inject.c
index 105d6cb41c72..783e1999fef4 100644
--- a/drivers/nvme/host/fault_inject.c
+++ b/drivers/nvme/host/fault_inject.c
@@ -42,9 +42,11 @@ void nvme_fault_inject_init(struct nvme_fault_inject *fault_inj,
 	}
 	fault_inj->parent = parent;
 
-	/* create debugfs for status code and dont_retry */
+	/* create debugfs for opcode, status code, and dont_retry */
+	fault_inj->opcode = 0xffff;
 	fault_inj->status = NVME_SC_INVALID_OPCODE;
 	fault_inj->dont_retry = true;
+	debugfs_create_x16("opcode", 0600, dir,	&fault_inj->opcode);
 	debugfs_create_x16("status", 0600, dir,	&fault_inj->status);
 	debugfs_create_bool("dont_retry", 0600, dir, &fault_inj->dont_retry);
 }
@@ -59,6 +61,7 @@ void nvme_should_fail(struct request *req)
 {
 	struct gendisk *disk = req->q->disk;
 	struct nvme_fault_inject *fault_inject = NULL;
+	struct nvme_command *cmd = nvme_req(req)->cmd;
 	u16 status;
 
 	if (disk) {
@@ -72,7 +75,14 @@ void nvme_should_fail(struct request *req)
 		fault_inject = &nvme_req(req)->ctrl->fault_inject;
 	}
 
-	if (fault_inject && should_fail(&fault_inject->attr, 1)) {
+	if (!fault_inject)
+		return;
+
+	if (fault_inject->opcode <= 0xff &&
+	    fault_inject->opcode != cmd->common.opcode)
+		return;
+
+	if (should_fail(&fault_inject->attr, 1)) {
 		/* inject status code and DNR bit */
 		status = fault_inject->status;
 		if (fault_inject->dont_retry)
diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
index 28cec87e4427..9a0fb1e35a5d 100644
--- a/drivers/nvme/host/nvme.h
+++ b/drivers/nvme/host/nvme.h
@@ -323,6 +323,7 @@ struct nvme_fault_inject {
 #ifdef CONFIG_FAULT_INJECTION_DEBUG_FS
 	struct fault_attr attr;
 	struct dentry *parent;
+	u16 opcode;
 	bool dont_retry;	/* DNR, do not retry */
 	u16 status;		/* status code */
 #endif
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.