[PATCH i-g-t V2 2/2] tests/amdgpu/amd_deadlock: add gfx user-queue priv-fault reset test

Jesse Zhang <[email protected]>
Newsgroups org.freedesktop.lists.igt-dev
Message-ID <[email protected]>
Submit a gfx user-queue job that raises a priv-fault interrupt and then
hangs cleanly: a minimal invalid opcode (0xf2) makes the CP raise
CP_BAD_OPCODE_ERROR without running off into memory, and a trailing
WAIT_REG_MEM that never completes keeps the queue hung. The driver recovers
it with a per-queue reset instead of a full GPU reset. The job is submitted
on the synchronised path so it blocks until the reset completes the fence.
Add it as the amdgpu-gfx-priv-fault-umq subtest, gated on
AMDGPU_ENABLE_USERQTEST and per-queue reset capability.

v2: move all hang packet creation into ip-block hooks wait_reg_mem_hang
    and priv_fault_hang in amd_ip_blocks_ex.c; deadlock helpers now call
    the hooks only, no direct PACKET3() in test code (Vitaly)

Co-developed-by: Vitaly Prosyak <[email protected]>
Signed-off-by: Jesse Zhang <[email protected]>
---
 lib/amdgpu/amd_deadlock_helpers.c | 110 ++++++++++++++++++++++++++----
 lib/amdgpu/amd_deadlock_helpers.h |   4 ++
 lib/amdgpu/amd_ip_blocks.h        |  22 ++++++
 lib/amdgpu/amd_ip_blocks_ex.c     |  68 ++++++++++++++++++
 tests/amdgpu/amd_deadlock.c       |   9 +++
 5 files changed, 201 insertions(+), 12 deletions(-)

diff --git a/lib/amdgpu/amd_deadlock_helpers.c b/lib/amdgpu/amd_deadlock_helpers.c
index 4395fc527..92d816e13 100644
--- a/lib/amdgpu/amd_deadlock_helpers.c
+++ b/lib/amdgpu/amd_deadlock_helpers.c
@@ -416,21 +416,16 @@ bad_access_helper(amdgpu_device_handle device_handle, unsigned int cmd_error,
 /*
  * Emit a WAIT_REG_MEM that waits forever: poll bo_mc (initialised to 0) for a
  * value != 0. This is a valid packet that never completes, i.e. a clean hang
- * with no page fault, so it can be recovered by a per-queue reset.
+ * with no page fault, so it can be recovered by a per-queue reset. All packet
+ * creation lives in the IP block hook for ASIC portability.
  */
-static void gfx_ring_emit_wait_reg_mem_hang(struct amdgpu_ring_context *ring_context)
+static void gfx_ring_emit_wait_reg_mem_hang(
+	const struct amdgpu_ip_block_version *ip_block,
+	struct amdgpu_ring_context *ring_context)
 {
 	uint32_t i = 0;
 
-	ring_context->pm4[i++] = PACKET3(PACKET3_WAIT_REG_MEM, 5);
-	ring_context->pm4[i++] = WAIT_REG_MEM_MEM_SPACE(1)  | /* memory */
-				 WAIT_REG_MEM_FUNCTION(4)  | /* != */
-				 WAIT_REG_MEM_ENGINE(0);     /* me */
-	ring_context->pm4[i++] = lower_32_bits(ring_context->bo_mc) & 0xfffffffc;
-	ring_context->pm4[i++] = upper_32_bits(ring_context->bo_mc);
-	ring_context->pm4[i++] = 0;          /* reference value */
-	ring_context->pm4[i++] = 0xffffffff; /* and mask */
-	ring_context->pm4[i++] = 0x00000004; /* poll interval */
+	ip_block->funcs->wait_reg_mem_hang(ip_block->funcs, ring_context, &i);
 	ring_context->pm4_dw = i;
 }
 
@@ -490,7 +485,98 @@ void amdgpu_hang_ring_helper(amdgpu_device_handle device_handle, unsigned int ip
 	memset((void *)ring_context->bo_cpu, 0, ring_context->write_length * sizeof(uint32_t));
 	ring_context->resources[0] = ring_context->bo;
 
-	gfx_ring_emit_wait_reg_mem_hang(ring_context);
+	gfx_ring_emit_wait_reg_mem_hang(ip_block, ring_context);
+
+	amdgpu_test_exec_cs_helper(device_handle, ip_block->type, ring_context, 0);
+
+	amdgpu_bo_unmap_and_free(ring_context->bo, ring_context->va_handle, ring_context->bo_mc,
+				 ring_context->write_length * sizeof(uint32_t));
+	if (user_queue) {
+		ip_block->funcs->userq_destroy(device_handle, ring_context, ip_type);
+	} else {
+		free(ring_context->pm4);
+		free(ring_context);
+	}
+}
+
+/*
+ * Build a packet stream that raises a gfx priv-fault interrupt and then hangs
+ * the queue cleanly: a minimal invalid opcode (CP_BAD_OPCODE_ERROR) followed by
+ * a WAIT_REG_MEM that never completes. The bad opcode alone would let the CP
+ * run to completion (self-recovering), and a bad opcode with a mis-parseable
+ * body drags the CP into a page fault (only recoverable by a full GPU reset).
+ * Combining a minimal bad opcode with a clean wait gives the wanted case: the
+ * priv-fault interrupt fires, the queue hangs without faulting the CP, and the
+ * driver recovers it with a per-queue reset.
+ */
+static void gfx_ring_emit_priv_fault_hang(
+	const struct amdgpu_ip_block_version *ip_block,
+	struct amdgpu_ring_context *ring_context)
+{
+	uint32_t i = 0;
+
+	ip_block->funcs->priv_fault_hang(ip_block->funcs, ring_context, &i);
+	ring_context->pm4_dw = i;
+}
+
+/*
+ * Fault a user queue with an invalid opcode followed by an endless wait: the
+ * bad opcode raises the gfx priv-fault interrupt and the wait hangs the queue
+ * cleanly, so the driver recovers it with a per-queue reset (no full GPU
+ * reset). The faulting submit uses the normal (synchronised) path so it blocks
+ * until the per-queue reset completes the fence.
+ */
+void amdgpu_priv_fault_ring_helper(amdgpu_device_handle device_handle, unsigned int ip_type,
+				   struct pci_addr *pci, bool user_queue)
+{
+	const struct amdgpu_ip_block_version *ip_block;
+	const int write_length = 128;
+	const int pm4_dw = 256;
+	struct amdgpu_ring_context *ring_context;
+	int r = 0;
+
+	ip_block = get_ip_block(device_handle, ip_type);
+	ring_context = calloc(1, sizeof(*ring_context));
+	igt_assert(ring_context);
+
+	if (user_queue) {
+		ip_block->funcs->userq_create(device_handle, ring_context, ip_type);
+	} else {
+		r = amdgpu_cs_ctx_create(device_handle, &ring_context->context_handle);
+		igt_assert_eq(r, 0);
+	}
+
+	ring_context->write_length = write_length;
+	ring_context->pm4 = calloc(pm4_dw, sizeof(*ring_context->pm4));
+	ring_context->pm4_size = pm4_dw;
+	ring_context->res_cnt = 1;
+	ring_context->ring_id = 0;
+	ring_context->user_queue = user_queue;
+	igt_assert(ring_context->pm4);
+
+	r = amdgpu_bo_alloc_and_map_sync(device_handle,
+				    ring_context->write_length * sizeof(uint32_t),
+				    4096, AMDGPU_GEM_DOMAIN_GTT,
+				    AMDGPU_GEM_CREATE_CPU_GTT_USWC,
+				    AMDGPU_VM_MTYPE_UC,
+				    &ring_context->bo,
+				    (void **)&ring_context->bo_cpu,
+				    &ring_context->bo_mc,
+				    &ring_context->va_handle,
+				    ring_context->timeline_syncobj_handle,
+				    ++ring_context->point, user_queue);
+	igt_assert_eq(r, 0);
+	if (user_queue) {
+		r = amdgpu_timeline_syncobj_wait(device_handle,
+			ring_context->timeline_syncobj_handle,
+			ring_context->point);
+		igt_assert_eq(r, 0);
+	}
+
+	memset((void *)ring_context->bo_cpu, 0, ring_context->write_length * sizeof(uint32_t));
+	ring_context->resources[0] = ring_context->bo;
+
+	gfx_ring_emit_priv_fault_hang(ip_block, ring_context);
 
 	amdgpu_test_exec_cs_helper(device_handle, ip_block->type, ring_context, 0);
 
diff --git a/lib/amdgpu/amd_deadlock_helpers.h b/lib/amdgpu/amd_deadlock_helpers.h
index accbf2e41..6e69e62cb 100644
--- a/lib/amdgpu/amd_deadlock_helpers.h
+++ b/lib/amdgpu/amd_deadlock_helpers.h
@@ -37,5 +37,9 @@ amdgpu_hang_sdma_ring_helper(amdgpu_device_handle device_handle, uint8_t hang_ty
 void
 amdgpu_hang_ring_helper(amdgpu_device_handle device_handle, unsigned int ip_type,
 			struct pci_addr *pci, bool user_queue);
+
+void
+amdgpu_priv_fault_ring_helper(amdgpu_device_handle device_handle, unsigned int ip_type,
+			      struct pci_addr *pci, bool user_queue);
 #endif
 
diff --git a/lib/amdgpu/amd_ip_blocks.h b/lib/amdgpu/amd_ip_blocks.h
index 2adea2f58..6e8ac1511 100644
--- a/lib/amdgpu/amd_ip_blocks.h
+++ b/lib/amdgpu/amd_ip_blocks.h
@@ -426,6 +426,28 @@ struct amdgpu_ip_funcs {
 		bool wr_confirm
 	);
 
+	/*
+	 * Emit PACKET3_WAIT_REG_MEM for deadlock/hang tests. Uses FUNCTION(4)
+	 * for != comparison and polls ring_context->bo_mc (initialised to 0)
+	 * for a value that never arrives, hanging the queue for reset testing.
+	 */
+	int (*wait_reg_mem_hang)(
+		const struct amdgpu_ip_funcs *func,
+		const struct amdgpu_ring_context *context,
+		uint32_t *pm4_dw
+	);
+
+	/*
+	 * Emit an invalid opcode followed by a WAIT_REG_MEM hang for priv-fault
+	 * tests: the bad opcode raises CP_BAD_OPCODE_ERROR, then the queue hangs
+	 * cleanly so the driver recovers it with a per-queue reset.
+	 */
+	int (*priv_fault_hang)(
+		const struct amdgpu_ip_funcs *func,
+		const struct amdgpu_ring_context *context,
+		uint32_t *pm4_dw
+	);
+
 };
 
 extern const struct amdgpu_ip_block_version gfx_v6_0_ip_block;
diff --git a/lib/amdgpu/amd_ip_blocks_ex.c b/lib/amdgpu/amd_ip_blocks_ex.c
index b3b708507..cec0f4d66 100644
--- a/lib/amdgpu/amd_ip_blocks_ex.c
+++ b/lib/amdgpu/amd_ip_blocks_ex.c
@@ -217,6 +217,13 @@ static void gfx_write_data_mem_default(
 }
 
 
+static int gfx_ring_wait_reg_mem_hang(const struct amdgpu_ip_funcs *func,
+				      const struct amdgpu_ring_context *ring_context,
+				      uint32_t *pm4_dw);
+static int gfx_ring_priv_fault_hang(const struct amdgpu_ip_funcs *func,
+				    const struct amdgpu_ring_context *ring_context,
+				    uint32_t *pm4_dw);
+
 void amd_ip_blocks_ex_init(struct amdgpu_ip_funcs *funcs)
 {
 	funcs->gfx_program_compute = gfx_program_compute_default;
@@ -225,6 +232,10 @@ void amd_ip_blocks_ex_init(struct amdgpu_ip_funcs *funcs)
 	funcs->gfx_emit_nops = gfx_emit_nops_default;
 	funcs->gfx_write_data_mem = gfx_write_data_mem_default;
 
+	/* Deadlock/hang test hooks */
+	funcs->wait_reg_mem_hang = gfx_ring_wait_reg_mem_hang;
+	funcs->priv_fault_hang = gfx_ring_priv_fault_hang;
+
 	switch (funcs->family_id) {
 	case AMDGPU_FAMILY_RV:
 	case AMDGPU_FAMILY_NV:
@@ -251,3 +262,60 @@ void amd_ip_blocks_ex_init(struct amdgpu_ip_funcs *funcs)
 	}
 }
 
+/*
+ * Emit PACKET3_WAIT_REG_MEM for deadlock/hang tests. Uses FUNCTION(4) for !=
+ * comparison, polling a memory location (initialised to 0) for a value that
+ * never arrives, so the queue hangs cleanly for per-queue reset testing.
+ */
+static int
+gfx_ring_wait_reg_mem_hang(const struct amdgpu_ip_funcs *func,
+			   const struct amdgpu_ring_context *ring_context,
+			   uint32_t *pm4_dw)
+{
+	uint32_t i = *pm4_dw;
+
+	ring_context->pm4[i++] = PACKET3(PACKET3_WAIT_REG_MEM, 5);
+	ring_context->pm4[i++] = (WAIT_REG_MEM_MEM_SPACE(1) | /* memory */
+				  WAIT_REG_MEM_FUNCTION(4) | /* != */
+				  WAIT_REG_MEM_ENGINE(0));   /* me */
+	ring_context->pm4[i++] = lower_32_bits(ring_context->bo_mc) & 0xfffffffc;
+	ring_context->pm4[i++] = upper_32_bits(ring_context->bo_mc);
+	ring_context->pm4[i++] = 0;          /* reference value */
+	ring_context->pm4[i++] = 0xffffffff; /* and mask */
+	ring_context->pm4[i++] = 0x00000004; /* poll interval */
+	*pm4_dw = i;
+
+	return 0;
+}
+
+/*
+ * Emit an invalid opcode followed by a WAIT_REG_MEM hang for priv-fault tests.
+ * The invalid opcode raises CP_BAD_OPCODE_ERROR (a gfx priv-fault) without
+ * running the CP off into memory, then the WAIT_REG_MEM hangs the queue cleanly.
+ */
+static int
+gfx_ring_priv_fault_hang(const struct amdgpu_ip_funcs *func,
+			 const struct amdgpu_ring_context *ring_context,
+			 uint32_t *pm4_dw)
+{
+	uint32_t i = *pm4_dw;
+
+	/* Invalid opcode: CP raises CP_BAD_OPCODE_ERROR (gfx priv-fault). */
+	ring_context->pm4[i++] = PACKET3(0xf2, 0);
+	ring_context->pm4[i++] = 0x0;
+
+	/* Then hang cleanly on a WAIT_REG_MEM that never completes. */
+	ring_context->pm4[i++] = PACKET3(PACKET3_WAIT_REG_MEM, 5);
+	ring_context->pm4[i++] = (WAIT_REG_MEM_MEM_SPACE(1) |
+				  WAIT_REG_MEM_FUNCTION(4) |
+				  WAIT_REG_MEM_ENGINE(0));
+	ring_context->pm4[i++] = lower_32_bits(ring_context->bo_mc) & 0xfffffffc;
+	ring_context->pm4[i++] = upper_32_bits(ring_context->bo_mc);
+	ring_context->pm4[i++] = 0;          /* reference value */
+	ring_context->pm4[i++] = 0xffffffff; /* and mask */
+	ring_context->pm4[i++] = 0x00000004; /* poll interval */
+	*pm4_dw = i;
+
+	return 0;
+}
+
diff --git a/tests/amdgpu/amd_deadlock.c b/tests/amdgpu/amd_deadlock.c
index 118948992..0d0e4ba6e 100644
--- a/tests/amdgpu/amd_deadlock.c
+++ b/tests/amdgpu/amd_deadlock.c
@@ -255,6 +255,15 @@ int igt_main()
 			amdgpu_hang_ring_helper(device, AMDGPU_HW_IP_GFX, &pci, true);
 		}
 	}
+
+	igt_describe("Test-per-queue-reset-recovery-of-a-gfx-user-queue-priv-fault");
+	igt_subtest_with_dynamic("amdgpu-gfx-priv-fault-umq") {
+		if (enable_test && userq_arr_cap[AMD_IP_GFX] &&
+			is_reset_enable(AMD_IP_GFX, AMDGPU_RESET_TYPE_PER_QUEUE, &pci)) {
+			igt_dynamic_f("amdgpu-gfx-priv-fault-umq")
+			amdgpu_priv_fault_ring_helper(device, AMDGPU_HW_IP_GFX, &pci, true);
+		}
+	}
 #endif
 
 	igt_fixture() {
-- 
2.49.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.