All of lore.kernel.org
 help / color / mirror / Atom feed
From: Rob Clark <robin.clark@oss.qualcomm.com>
To: dri-devel@lists.freedesktop.org
Cc: linux-arm-msm@vger.kernel.org, freedreno@lists.freedesktop.org,
	Alexander Koskovich <akoskovich@pm.me>,
	Jie Zhang <jie.zhang@oss.qualcomm.com>,
	Akhil P Oommen <akhilpo@oss.qualcomm.com>,
	Bhaskar Reddy Valluru <bvalluru@qti.qualcomm.com>,
	Rob Clark <robin.clark@oss.qualcomm.com>,
	Dmitry Baryshkov <lumag@kernel.org>,
	Abhinav Kumar <abhinav.kumar@linux.dev>,
	Jessica Zhang <jesszhan0024@gmail.com>,
	Sean Paul <sean@poorly.run>,
	Marijn Suijten <marijn.suijten@somainline.org>,
	David Airlie <airlied@gmail.com>, Simona Vetter <simona@ffwll.ch>,
	Konrad Dybcio <konradybcio@kernel.org>,
	linux-kernel@vger.kernel.org (open list)
Subject: [PATCH 09/12] drm/msm/a8xx: devcoredump state capture support
Date: Tue, 20 Oct 2026 13:46:23 -0700	[thread overview]
Message-ID: <20261020204728.28844-10-robin.clark@oss.qualcomm.com> (raw)
In-Reply-To: <20261020204728.28844-1-robin.clark@oss.qualcomm.com>

This attempts to make it easier to compare to kgsl, the downstream
driver, to make it easier to spot bugs or missing things.  Such as
re-using function names, etc.

Signed-off-by: Rob Clark <robin.clark@oss.qualcomm.com>
Co-developed-by: Akhil P Oommen <akhilpo@oss.qualcomm.com>
Signed-off-by: Rob Clark <robin.clark@oss.qualcomm.com>
---
 drivers/gpu/drm/msm/Makefile                 |    4 +-
 drivers/gpu/drm/msm/adreno/a6xx_gpu.c        |    7 +
 drivers/gpu/drm/msm/adreno/a6xx_gpu.h        |    5 +
 drivers/gpu/drm/msm/adreno/a8xx_gpu_state.c  | 1253 ++++++++++++++++++
 drivers/gpu/drm/msm/adreno/adreno_snapshot.h |  205 +++
 5 files changed, 1473 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/msm/adreno/a8xx_gpu_state.c

diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 1e246616f83f..b5d64ee7eb74 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -31,7 +31,9 @@ adreno-y := \
 
 adreno-$(CONFIG_DEBUG_FS) += adreno/a5xx_debugfs.o \
 
-adreno-$(CONFIG_DRM_MSM_GPU_STATE)	+= adreno/a6xx_gpu_state.o
+adreno-$(CONFIG_DRM_MSM_GPU_STATE) += \
+	adreno/a6xx_gpu_state.o \
+	adreno/a8xx_gpu_state.o \
 
 msm-display-$(CONFIG_DRM_MSM_HDMI) += \
 	hdmi/hdmi.o \
diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c
index 09d76cdf059f..74064eba98c7 100644
--- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c
+++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c
@@ -2946,9 +2946,16 @@ const struct adreno_gpu_funcs a8xx_gpu_funcs = {
 		.active_ring = a6xx_active_ring,
 		.irq = a8xx_irq,
 		.destroy = a6xx_destroy,
+#if defined(CONFIG_DRM_MSM_GPU_STATE)
+		.show = a8xx_show,
+#endif
 		.gpu_busy = a8xx_gpu_busy,
 		.gpu_get_freq = a6xx_gmu_get_freq,
 		.gpu_set_freq = a6xx_gpu_set_freq,
+#if defined(CONFIG_DRM_MSM_GPU_STATE)
+		.gpu_state_get = a8xx_gpu_state_get,
+		.gpu_state_put = a8xx_gpu_state_put,
+#endif
 		.create_vm = a6xx_create_vm,
 		.create_private_vm = a6xx_create_private_vm,
 		.get_rptr = a6xx_get_rptr,
diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.h b/drivers/gpu/drm/msm/adreno/a6xx_gpu.h
index 5e1f35c83d63..07198e8bbff2 100644
--- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.h
+++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.h
@@ -370,4 +370,9 @@ void a8xx_preempt_irq(struct msm_gpu *gpu);
 bool a8xx_progress(struct msm_gpu *gpu, struct msm_ringbuffer *ring);
 void a8xx_perfcntr_flush(struct msm_gpu *gpu);
 void a8xx_recover(struct msm_gpu *gpu);
+void a8xx_show(struct msm_gpu *gpu, struct msm_gpu_state *state,
+		struct drm_printer *p);
+struct msm_gpu_state *a8xx_gpu_state_get(struct msm_gpu *gpu);
+int a8xx_gpu_state_put(struct msm_gpu_state *state);
+
 #endif /* __A6XX_GPU_H__ */
diff --git a/drivers/gpu/drm/msm/adreno/a8xx_gpu_state.c b/drivers/gpu/drm/msm/adreno/a8xx_gpu_state.c
new file mode 100644
index 000000000000..e4eb2f335a89
--- /dev/null
+++ b/drivers/gpu/drm/msm/adreno/a8xx_gpu_state.c
@@ -0,0 +1,1253 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include "msm_gem.h"
+#include "a6xx_gpu.h"
+#include "a6xx_gmu.h"
+#include "adreno_snapshot.h"
+#include "adreno_gen8_snapshot.h"
+
+#define NUM_SLICES(gpu)  (hweight32((gpu)->slice_mask))
+
+static inline int next_slice(int slice, u32 slice_mask)
+{
+	for (; slice < fls(slice_mask); slice++)
+		if (BIT(slice) & slice_mask)
+			return slice;
+
+	return slice;
+}
+
+#define NEXT_SLICE(_slice, _region, _slice_mask) \
+	((_region == UNSLICE) ? fls(_slice_mask) : next_slice(_slice, _slice_mask))
+
+#define FIRST_SLICE(_region, _slice_mask) \
+	((_region == UNSLICE) ? 0 : NEXT_SLICE(0, _region, _slice_mask))
+
+#define FOR_EACH_SLICE(_slice, _region, _slice_mask) \
+	for (_slice = FIRST_SLICE(_region, _slice_mask); \
+	     _slice < fls(_slice_mask); \
+	     _slice = NEXT_SLICE(++(_slice), _region, _slice_mask))
+
+/* Use SLICE_ID to access the region (0 for unsliced) */
+#define SLICE_ID(region, j) ((region == SLICE) ? j : 0)
+
+/*
+ * Use HEADER_SLICE_ID to specify the slice in the section header (UINT_MAX for unsliced).
+ * This allows snapshot parsers to differentiate between slice ID 0 and unsliced regions.
+ */
+#define HEADER_SLICE_ID(region, j) ((region == SLICE) ? j : UINT_MAX)
+
+#define GEN8_CP_APERTURE_REG_VAL(_sliceid, _pipe, _cluster, _context) (	\
+		A8XX_CP_APERTURE_CNTL_HOST_USESLICEID |			\
+		A8XX_CP_APERTURE_CNTL_HOST_SLICEID(_sliceid) |		\
+		A8XX_CP_APERTURE_CNTL_HOST_PIPEID(_pipe) |		\
+		A8XX_CP_APERTURE_CNTL_HOST_CLUSTERID(_cluster) |	\
+		A8XX_CP_APERTURE_CNTL_HOST_CONTEXTID3D(_context) |	\
+		0)
+
+#define GEN8_SP_READ_SEL_VAL(_contextid, _sliceid, _location, _pipe, _statetype, _usptp, _sptp) ( \
+		A7XX_SP_READ_SEL_CONTEXT(_contextid) |			\
+		A7XX_SP_READ_SEL_SLICE(_sliceid) |			\
+		A7XX_SP_READ_SEL_LOCATION(_location) |			\
+		A7XX_SP_READ_SEL_PIPE(_pipe) |				\
+		A7XX_SP_READ_SEL_STATETYPE(_statetype) |		\
+		A7XX_SP_READ_SEL_USPTP(_usptp) |			\
+		A7XX_SP_READ_SEL_SPTP(_sptp) |				\
+		0)
+
+/*
+ * Allocate 4MB for the crashdumper scratch region - 2MB for the script and
+ * the rest for the data.
+ *
+ * Note gen8_snapshot_indexed_registers() is worst case, needing separate
+ * CD_WRITE + CD_READ per reg, 0xa000 times (so script is ~1.2MB)
+ *
+ * TODO we could perhaps be clever and fill in the out addrs in a 2nd pass
+ * to accomodate invocations that need larger script and smaller data, like
+ * indexed regs.
+ */
+#define A8XX_CD_SIZE        SZ_4M
+#define A8XX_CD_DATA_OFFSET (2 * SZ_1M)
+#define A8XX_CD_DATA_SIZE   (A8XX_CD_SIZE - A8XX_CD_DATA_OFFSET)
+
+static int a8xx_crashdumper_init(struct msm_gpu *gpu,
+				 struct adreno_crashdumper *dumper)
+{
+	dumper->ptr = msm_gem_kernel_new(gpu->dev,
+		A8XX_CD_SIZE, MSM_BO_WC, gpu->vm,
+		&dumper->bo, &dumper->iova);
+
+	if (!IS_ERR(dumper->ptr)) {
+		msm_gem_object_set_name(dumper->bo, "crashdump");
+		dumper->data_offset = A8XX_CD_DATA_OFFSET;
+	}
+
+	return PTR_ERR_OR_ZERO(dumper->ptr);
+}
+
+static inline int __cd_run(struct msm_gpu *gpu, struct adreno_crashdumper *dumper)
+{
+	u32 val;
+	int ret;
+
+	if (IS_ERR_OR_NULL(dumper->ptr))
+		return -EINVAL;
+
+	/* Make sure all pending memory writes are posted */
+	wmb();
+
+	gpu_write64(gpu, REG_A8XX_CP_CRASH_DUMP_SCRIPT_BASE, dumper->iova);
+
+	gpu_write(gpu, REG_A8XX_CP_CRASH_DUMP_CNTL, 1);
+
+	/* NOTE: kgsl uses 500ms timeout */
+	ret = gpu_poll_timeout(gpu, REG_A8XX_CP_CRASH_DUMP_STATUS, val,
+			       val & 0x02, 100, 100000);
+
+	gpu_write(gpu, REG_A8XX_CP_CRASH_DUMP_CNTL, 0);
+
+	drm_WARN_ONCE(gpu->dev, ret, "crashdumper timed out: status=%08x (%d)\n", val, ret);
+
+	return ret;
+}
+
+static inline bool __cd_check_overflow(struct cd_script *p)
+{
+	if (WARN_ON_ONCE(p->in > (u64 *)(p->dumper->ptr + A8XX_CD_DATA_OFFSET)))
+		return false;
+	if (WARN_ON_ONCE(p->out > p->dumper->iova + A8XX_CD_SIZE))
+		return false;
+	return true;
+}
+
+static const struct gen8_snapshot_block_list *
+block_list(struct msm_gpu *gpu)
+{
+	return to_adreno_gpu(gpu)->info->snapshot_block_list;
+}
+
+/* The GMU is ioremapped separately, matching how it is described in dt.
+ * However the snapshot tables generated from hw spec do not take into
+ * account dt, and consider it as yet another sub-block of the GPU.  This
+ * helper helps to bridge the worlds.
+ */
+static uint32_t reg_read(struct msm_gpu *gpu, uint32_t reg)
+{
+	struct a6xx_gmu *gmu = &to_a6xx_gpu(to_adreno_gpu(gpu))->gmu;
+	if ((reg << 2) >= gmu->mmio_offset)
+		return gmu_read(gmu, reg);
+	return gpu_read(gpu, reg);
+}
+
+static void adreno_snapshot_registers_v2(struct msm_gpu *gpu,
+					 struct adreno_gpu_state *state,
+					 const u32 *ptr)
+{
+	int k, count;
+
+	/* Figure out how many registers we are going to dump */
+	count = adreno_snapshot_regs_count(ptr);
+
+	snapshot_sect (state, REGS_V2, 4 * count, {}) {
+		for (; ptr[0] != UINT_MAX; ptr += 2) {
+			u32 cnt = REG_COUNT(ptr);
+
+			if (cnt == 1) {
+				snapshot_sect_u32(&sect, BIT(31) | ptr[0]);
+			} else {
+				snapshot_sect_u32(&sect, ptr[0]);
+				snapshot_sect_u32(&sect, cnt);
+			}
+
+			for (k = ptr[0]; k <= ptr[1]; k++) {
+				snapshot_sect_u32(&sect, reg_read(gpu, k));
+			}
+		}
+	}
+}
+
+static void snapshot_mvc_regs_v3_subheader(struct adreno_snapshot_sect *sect, const u32 *ptr)
+{
+	/*
+	 * Note this might look like adreno_snapshot_registers_v2(),
+	 * but looks are deceiving.  The meaning of b31 is inverted
+	 * and the 2nd dword is end-offset instead of count.
+	 */
+	if (REG_COUNT(ptr) == 1) {
+		snapshot_sect_u32(sect, ptr[0]);
+	} else {
+		snapshot_sect_u32(sect, BIT(31) | ptr[0]);
+		snapshot_sect_u32(sect, ptr[1]);
+	}
+}
+
+static void gen8_legacy_snapshot_registers(struct msm_gpu *gpu,
+					   struct adreno_gpu_state *state,
+					   const struct gen8_reg_list *regs,
+					   u32 slice_id)
+{
+	int count;
+
+	/* Figure out how many registers we are going to dump */
+	count = adreno_snapshot_regs_count(regs->regs);
+
+	snapshot_sect (state, MVC_V3, 4 * count, {
+		.ctxt_id = 0,
+		.cluster_id = CLUSTER_NONE,
+		.pipe_id = PIPE_NONE,
+		.location_id = UINT_MAX,
+		.sp_id = UINT_MAX,
+		.usptp_id = UINT_MAX,
+		.slice_id = HEADER_SLICE_ID(regs->slice_region, slice_id),
+	}) {
+		unsigned long flags;
+
+		if (regs->sel)
+			gpu_write(gpu, regs->sel->host_reg, regs->sel->val);
+
+		if (regs->slice_region)
+			a8xx_aperture_acquire_slice(gpu, PIPE_NONE, slice_id, false, &flags);
+
+		/* Make sure the previous writes are posted before reading */
+		mb();
+
+		for (const u32 *ptr = regs->regs; ptr[0] != UINT_MAX; ptr += 2) {
+			snapshot_mvc_regs_v3_subheader(&sect, ptr);
+
+			for (int k = ptr[0]; k <= ptr[1]; k++) {
+				snapshot_sect_u32(&sect, reg_read(gpu, k));
+			}
+		}
+
+		if (regs->slice_region)
+			a8xx_aperture_release(gpu, flags);
+	}
+}
+
+static bool gen8_reglist_snapshot(struct msm_gpu *gpu, struct adreno_gpu_state *state,
+				  struct adreno_crashdumper *dumper)
+{
+	const struct gen8_snapshot_block_list *b = block_list(gpu);
+	u32 slice_mask = to_a6xx_gpu(to_adreno_gpu(gpu))->slice_mask;
+
+	if (!dumper) {
+		for (u32 i = 0; i < b->num_reg_list; i++) {
+			const struct gen8_reg_list *regs = &b->reg_list[i];
+			u32 j;
+
+			FOR_EACH_SLICE (j, regs->slice_region, slice_mask) {
+				u32 slice_id = SLICE_ID(regs->slice_region, j);
+				gen8_legacy_snapshot_registers(gpu, state, regs, slice_id);
+			}
+
+			/*
+			 * The downstream reglist contains registers in other memory regions
+			 * (cx_misc/cx_mem and cx_dbgc) and we need to plumb through their
+			 * offsets and map them to read them on the CPU. For now only read the
+			 * first two regions.
+			 */
+			if (i == 1)
+				break;
+		}
+		return true;
+	}
+
+	for (u32 i = 0; i < b->num_reg_list; i++) {
+		const struct gen8_reg_list *regs = &b->reg_list[i];
+		u32 j;
+
+		FOR_EACH_SLICE (j, regs->slice_region, slice_mask) {
+			u32 slice_id = SLICE_ID(regs->slice_region, j);
+			struct cd_script p = CD_INIT(dumper);
+
+			CD_WRITE(&p, REG_A8XX_CP_APERTURE_CNTL_CD,
+				 GEN8_CP_APERTURE_REG_VAL(j, 0, 0, 0));
+
+			/* Program the SEL_CNTL_CD register appropriately */
+			if (regs->sel)
+				CD_WRITE(&p, regs->sel->cd_reg, regs->sel->val);
+
+			for (const u32 *ptr = regs->regs; ptr[0] != UINT_MAX; ptr += 2) {
+				u32 count = REG_COUNT(ptr);
+
+				CD_READ(&p, ptr[0], count);
+			}
+
+			/* Marker for end of script */
+			CD_FINISH(&p);
+
+			/* Try to run the crash dumper and bail if it times out */
+			if (CD_RUN(&p, gpu))
+				return false;
+
+			u32 count = adreno_snapshot_regs_count(regs->regs);
+			snapshot_sect (state, MVC_V3, 4 * count, {
+				.ctxt_id = 0,
+				.cluster_id = CLUSTER_NONE,
+				.pipe_id = PIPE_NONE,
+				.location_id = UINT_MAX,
+				.sp_id = UINT_MAX,
+				.usptp_id = UINT_MAX,
+				.slice_id = HEADER_SLICE_ID(regs->slice_region, slice_id),
+			}) {
+				const u32 *src = dumper->ptr + A8XX_CD_DATA_OFFSET;
+
+				for (const u32 *ptr = regs->regs; ptr[0] != UINT_MAX; ptr += 2) {
+					u32 count = REG_COUNT(ptr);
+
+					snapshot_mvc_regs_v3_subheader(&sect, ptr);
+					snapshot_sect_data(&sect, src, count * sizeof(u32));
+
+					src += count;
+				}
+			}
+		}
+	}
+
+	return true;
+}
+
+static bool gen8_snapshot_indexed_registers(
+	struct msm_gpu *gpu, struct adreno_gpu_state *state, struct adreno_crashdumper *dumper,
+	u32 index, u32 data, u32 start, u32 count, u32 pipe_id, u32 slice_id)
+{
+	struct adreno_snapshot_indexed_regs_v2 hdr = {
+		.index_reg = index,
+		.data_reg = data,
+		.count = count,
+		.start = start,
+		.pipe_id = pipe_id,
+		.slice_id = slice_id,
+	};
+	u32 s = (slice_id == UINT_MAX) ? 0 : slice_id;
+
+	if (!dumper) {
+		snapshot_sect (state, INDEXED_REGS_V2, count * sizeof(u32), hdr) {
+			unsigned long flags;
+
+			a8xx_aperture_acquire_slice(gpu, pipe_id, s, true, &flags);
+
+			for (int i = 0; i < count; i++) {
+				/* Write the address register */
+				gpu_write(gpu, index, start + i);
+
+				/* Make sure the write finishes */
+				wmb();
+
+				snapshot_sect_u32(&sect, reg_read(gpu, data));
+
+				/* Make sure the read finishes */
+				rmb();
+			}
+
+			a8xx_aperture_release(gpu, flags);
+		}
+
+		return true;
+	}
+
+	struct cd_script p = CD_INIT(dumper);
+
+	CD_WRITE(&p, REG_A8XX_CP_APERTURE_CNTL_CD,
+		 GEN8_CP_APERTURE_REG_VAL(s, pipe_id, 0, 0));
+
+	for (int i = 0; i < count; i++) {
+		/* Write the address register */
+		CD_WRITE(&p, index, start + i);
+		CD_READ(&p, data, 1);
+	}
+
+	/* Marker for end of script */
+	CD_FINISH(&p);
+
+	/* Try to run the crash dumper and bail if it times out */
+	if (CD_RUN(&p, gpu))
+		return false;
+
+	snapshot_sect (state, INDEXED_REGS_V2, count * sizeof(u32), hdr) {
+		const u32 *src = dumper->ptr + A8XX_CD_DATA_OFFSET;
+		snapshot_sect_data(&sect, src, count * sizeof(u32));
+	}
+
+	return true;
+}
+
+static bool gen8_snapshot_cp_indexed_regs(struct msm_gpu *gpu,
+					  struct adreno_gpu_state *state,
+					  struct adreno_crashdumper *dumper)
+{
+	const struct gen8_snapshot_block_list *b = block_list(gpu);
+
+	for (int i = 0; i < b->index_registers_len; i++) {
+		bool ret = gen8_snapshot_indexed_registers(gpu, state, dumper,
+			b->index_registers[i].addr,
+			b->index_registers[i].data, 0,
+			b->index_registers[i].size,
+			b->index_registers[i].pipe_id, UINT_MAX);
+		if (!ret)
+			return false;
+	}
+
+	return true;
+}
+
+static bool gen8_snapshot_mempool(struct msm_gpu *gpu,
+				  struct adreno_gpu_state *state,
+				  struct adreno_crashdumper *dumper)
+{
+	const struct gen8_snapshot_block_list *b = block_list(gpu);
+	u32 slice_mask = to_a6xx_gpu(to_adreno_gpu(gpu))->slice_mask;
+	u32 first_slice = ffs(slice_mask);
+
+	for (int i = 0; i < b->mempool_index_registers_len; i++) {
+		const struct gen8_cp_indexed_reg *cp_indexed_reg =
+			&b->mempool_index_registers[i];
+		unsigned long flags;
+		u32 j;
+
+		FOR_EACH_SLICE (j, cp_indexed_reg->slice_region, slice_mask) {
+
+			/* TODO: Remove hardcoded values */
+			/* Set CP_CHICKEN_DBG[StabilizeMVC] to stabilize it while dumping */
+			a8xx_aperture_acquire_slice(gpu, cp_indexed_reg->pipe_id, first_slice, false, &flags);
+			gpu_rmw(gpu, REG_A8XX_CP_CHICKEN_DBG_PIPE, BIT(2), BIT(2));
+			a8xx_aperture_release(gpu, flags);
+
+			a8xx_aperture_acquire_slice(gpu, cp_indexed_reg->pipe_id, j, true, &flags);
+			gpu_rmw(gpu, REG_A8XX_CP_SLICE_CHICKEN_DBG_PIPE, BIT(2), BIT(2));
+			a8xx_aperture_release(gpu, flags);
+
+			gen8_snapshot_indexed_registers(gpu, state, dumper,
+				cp_indexed_reg->addr,
+				cp_indexed_reg->data, 0,
+				cp_indexed_reg->size,
+				cp_indexed_reg->pipe_id,
+				HEADER_SLICE_ID(cp_indexed_reg->slice_region, j));
+
+			/* Reset CP_CHICKEN_DBG[StabilizeMVC] once we are done */
+			a8xx_aperture_acquire_slice(gpu, cp_indexed_reg->pipe_id, first_slice, false, &flags);
+			gpu_rmw(gpu, REG_A8XX_CP_CHICKEN_DBG_PIPE, BIT(2), 0);
+			a8xx_aperture_release(gpu, flags);
+
+			a8xx_aperture_acquire_slice(gpu, cp_indexed_reg->pipe_id, j, true, &flags);
+			gpu_rmw(gpu, REG_A8XX_CP_SLICE_CHICKEN_DBG_PIPE, BIT(2), 0);
+			a8xx_aperture_release(gpu, flags);
+		}
+	}
+
+	/* Clear aperture register */
+	a8xx_aperture_clear(gpu);
+
+	return true;
+}
+
+static void gen8_legacy_snapshot_mvc(struct msm_gpu *gpu,
+				     struct adreno_gpu_state *state,
+				     const struct gen8_cluster_registers *cluster,
+				     u32 slice_id)
+{
+	int count;
+
+	/* Figure out how many registers we are going to dump */
+	count = adreno_snapshot_regs_count(cluster->regs);
+
+	snapshot_sect (state, MVC_V3, 4 * count, {
+		.ctxt_id = (cluster->context_id == STATE_FORCE_CTXT_1) ? 1 : 0,
+		.cluster_id = cluster->cluster_id,
+		.pipe_id = cluster->pipe_id,
+		.location_id = UINT_MAX,
+		.sp_id = UINT_MAX,
+		.usptp_id = UINT_MAX,
+		.slice_id = HEADER_SLICE_ID(cluster->slice_region, slice_id),
+	}) {
+		unsigned long flags;
+
+		/*
+		 * Set the AHB control for the Host to read from the
+		 * cluster/context for this iteration.
+		 */
+		a8xx_aperture_acquire_slice(gpu, cluster->pipe_id, slice_id, true, &flags);
+
+		if (cluster->sel)
+			gpu_write(gpu, cluster->sel->host_reg, cluster->sel->val);
+
+		/* Make sure the previous writes are posted before reading */
+		mb();
+
+		for (const u32 *ptr = cluster->regs; ptr[0] != UINT_MAX; ptr += 2) {
+			snapshot_mvc_regs_v3_subheader(&sect, ptr);
+
+			for (int k = ptr[0]; k <= ptr[1]; k++) {
+				snapshot_sect_u32(&sect, reg_read(gpu, k));
+			}
+		}
+
+		a8xx_aperture_release(gpu, flags);
+	}
+}
+
+static bool gen8_snapshot_mvc_regs(
+	struct msm_gpu *gpu, struct adreno_gpu_state *state, struct adreno_crashdumper *dumper,
+	const struct gen8_cluster_registers *clusters, size_t num_cluster)
+{
+	u32 slice_mask = to_a6xx_gpu(to_adreno_gpu(gpu))->slice_mask;
+
+	if (!dumper) {
+		for (int i = 0; i < num_cluster; i++) {
+			const struct gen8_cluster_registers *cluster = &clusters[i];
+			u32 j;
+
+			FOR_EACH_SLICE (j, cluster->slice_region, slice_mask) {
+				u32 slice_id = SLICE_ID(cluster->slice_region, j);
+				gen8_legacy_snapshot_mvc(gpu, state, cluster, slice_id);
+			}
+		}
+		return true;
+	}
+
+	for (int i = 0; i < num_cluster; i++) {
+		const struct gen8_cluster_registers *cluster = &clusters[i];
+		u32 j;
+
+		FOR_EACH_SLICE (j, cluster->slice_region, slice_mask) {
+			u32 slice_id = SLICE_ID(cluster->slice_region, j);
+			struct cd_script p = CD_INIT(dumper);
+
+			CD_WRITE(&p, REG_A8XX_CP_APERTURE_CNTL_CD,
+				 GEN8_CP_APERTURE_REG_VAL(j, cluster->pipe_id,
+							  cluster->cluster_id,
+							  cluster->context_id));
+
+			if (cluster->sel)
+				CD_WRITE(&p, cluster->sel->cd_reg, cluster->sel->val);
+
+			for (const u32 *ptr = cluster->regs; ptr[0] != UINT_MAX; ptr += 2) {
+				CD_READ(&p, ptr[0], REG_COUNT(ptr));
+			}
+
+			/* Marker for end of script */
+			CD_FINISH(&p);
+
+			/* Try to run the crash dumper and bail if it times out */
+			if (CD_RUN(&p, gpu))
+				return false;
+
+			u32 count = adreno_snapshot_regs_count(cluster->regs);
+
+			snapshot_sect (state, MVC_V3, 4 * count, {
+				.ctxt_id = (cluster->context_id == STATE_FORCE_CTXT_1) ? 1 : 0,
+				.cluster_id = cluster->cluster_id,
+				.pipe_id = cluster->pipe_id,
+				.location_id = UINT_MAX,
+				.sp_id = UINT_MAX,
+				.usptp_id = UINT_MAX,
+				.slice_id = HEADER_SLICE_ID(cluster->slice_region, slice_id),
+			}) {
+				const u32 *src = dumper->ptr + A8XX_CD_DATA_OFFSET;
+
+				for (const u32 *ptr = cluster->regs; ptr[0] != UINT_MAX; ptr += 2) {
+					u32 count = REG_COUNT(ptr);
+
+					snapshot_mvc_regs_v3_subheader(&sect, ptr);
+					snapshot_sect_data(&sect, src, count * sizeof(u32));
+
+					src += count;
+				}
+			}
+		}
+	}
+	return true;
+}
+
+static u32 gen8_read_dbgahb(struct msm_gpu *gpu, u32 regbase, u32 reg)
+{
+	return gpu_read(gpu, REG_A7XX_SP_AHB_READ_APERTURE + reg - regbase);
+}
+
+static void gen8_legacy_snapshot_cluster_dbgahb(
+	struct msm_gpu *gpu, struct adreno_gpu_state *state,
+	const struct gen8_sptp_cluster_registers *cluster,
+	u32 sp, u32 usptp, u32 slice_id)
+{
+	int count;
+
+	/* Figure out how many registers we are going to dump */
+	count = adreno_snapshot_regs_count(cluster->regs);
+
+	snapshot_sect (state, MVC_V3, 4 * count, {
+		.ctxt_id = cluster->context_id,
+		.cluster_id = cluster->cluster_id,
+		.pipe_id = cluster->pipe_id,
+		.location_id = cluster->location_id,
+		.sp_id = sp,
+		.usptp_id = usptp,
+		.slice_id = HEADER_SLICE_ID(cluster->slice_region, slice_id),
+	}) {
+		u32 read_sel = GEN8_SP_READ_SEL_VAL(
+			0, slice_id, cluster->location_id, cluster->pipe_id,
+			cluster->statetype, usptp, sp);
+
+		gpu_write(gpu, REG_A7XX_SP_READ_SEL, read_sel);
+
+		/*
+		 * An explicit barrier is needed so that reads do not happen before
+		 * the register write.
+		 */
+		mb();
+
+		for (const u32 *ptr = cluster->regs; ptr[0] != UINT_MAX; ptr += 2) {
+			snapshot_mvc_regs_v3_subheader(&sect, ptr);
+
+			for (int k = ptr[0]; k <= ptr[1]; k++) {
+				u32 val = gen8_read_dbgahb(gpu, cluster->regbase, k);
+				snapshot_sect_u32(&sect, val);
+			}
+		}
+	}
+}
+
+static bool gen8_snapshot_dbgahb_regs(struct msm_gpu *gpu,
+				      struct adreno_gpu_state *state,
+				      struct adreno_crashdumper *dumper)
+{
+	const struct gen8_snapshot_block_list *b = block_list(gpu);
+	u32 slice_mask = to_a6xx_gpu(to_adreno_gpu(gpu))->slice_mask;
+
+	if (!dumper) {
+		for (int i = 0; i < b->num_sptp_clusters; i++) {
+			const struct gen8_sptp_cluster_registers *cluster =
+				&b->sptp_clusters[i];
+
+			for (u32 sp = 0; sp < cluster->num_sps; sp++) {
+				for (u32 usptp = 0; usptp < cluster->num_usptps; usptp++) {
+					u32 j;
+
+					FOR_EACH_SLICE (j, cluster->slice_region, slice_mask) {
+						u32 slice_id = SLICE_ID(cluster->slice_region, j);
+
+						gen8_legacy_snapshot_cluster_dbgahb(
+							gpu, state, cluster,
+							sp, usptp, slice_id);
+					}
+				}
+			}
+		}
+
+		return true;
+	}
+
+	for (int i = 0; i < b->num_sptp_clusters; i++) {
+		const struct gen8_sptp_cluster_registers *cluster =
+			&b->sptp_clusters[i];
+
+		for (u32 sp = 0; sp < cluster->num_sps; sp++) {
+			for (u32 usptp = 0; usptp < cluster->num_usptps; usptp++) {
+				u32 j;
+
+				FOR_EACH_SLICE (j, cluster->slice_region, slice_mask) {
+					u32 slice_id = SLICE_ID(cluster->slice_region, j);
+
+					/* Build the crash script */
+					struct cd_script p = CD_INIT(dumper);
+
+					u32 read_sel = GEN8_SP_READ_SEL_VAL(
+						0, slice_id, cluster->location_id,
+						cluster->pipe_id, cluster->statetype,
+						usptp, sp);
+
+					/* Program the aperture */
+					CD_WRITE(&p, REG_A7XX_SP_READ_SEL, read_sel);
+
+					for (const u32 *ptr = cluster->regs; ptr[0] != UINT_MAX; ptr += 2) {
+						u32 count = REG_COUNT(ptr);
+						u32 reg = REG_A7XX_SP_AHB_READ_APERTURE + ptr[0] - cluster->regbase;
+
+						CD_READ(&p, reg, count);
+					}
+
+					/* Marker for end of script */
+					CD_FINISH(&p);
+
+					/* Try to run the crash dumper and bail if it times out */
+					if (CD_RUN(&p, gpu))
+						return false;
+
+					u32 count = adreno_snapshot_regs_count(cluster->regs);
+					snapshot_sect (state, MVC_V3, 4 * count, {
+						.ctxt_id = cluster->context_id,
+						.cluster_id = cluster->cluster_id,
+						.pipe_id = cluster->pipe_id,
+						.location_id = cluster->location_id,
+						.sp_id = sp,
+						.usptp_id = usptp,
+						.slice_id = HEADER_SLICE_ID(cluster->slice_region, slice_id),
+					}) {
+						const u32 *src = dumper->ptr + A8XX_CD_DATA_OFFSET;
+
+						for (const u32 *ptr = cluster->regs; ptr[0] != UINT_MAX; ptr += 2) {
+							u32 count = REG_COUNT(ptr);
+
+							snapshot_mvc_regs_v3_subheader(&sect, ptr);
+							snapshot_sect_data(&sect, src, count * sizeof(u32));
+
+							src += count;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return true;
+}
+
+static void gen8_legacy_snapshot_shader(struct msm_gpu *gpu, struct adreno_gpu_state *state,
+					const struct gen8_shader_block *block,
+					u32 sp, u32 ctxt, u32 slice_id)
+{
+	snapshot_sect (state, SHADER_V3, block->size * sizeof(u32), {
+		.type = block->statetype,
+		.slice_id = HEADER_SLICE_ID(block->slice_region, slice_id),
+		.sp_index = sp,
+		.usptp = block->usptp_id,
+		.pipe_id = block->pipeid,
+		.location = block->location,
+		.ctxt_id = ctxt,
+		.size = block->size,
+	}) {
+		u32 read_sel = GEN8_SP_READ_SEL_VAL(
+			ctxt, slice_id, block->location, block->pipeid,
+			block->statetype, block->usptp_id, sp);
+
+		gpu_write(gpu, REG_A7XX_SP_READ_SEL, read_sel);
+
+		/*
+		 * An explicit barrier is needed so that reads do not happen before
+		 * the register write.
+		 */
+		mb();
+
+		for (int i = 0; i < block->size; i++) {
+			u32 val = gpu_read(gpu, REG_A7XX_SP_AHB_READ_APERTURE + i);
+			snapshot_sect_u32(&sect, val);
+		}
+	}
+}
+
+static bool gen8_snapshot_shader(struct msm_gpu *gpu,
+				 struct adreno_gpu_state *state,
+				 struct adreno_crashdumper *dumper)
+{
+	const struct gen8_snapshot_block_list *b = block_list(gpu);
+	u32 slice_mask = to_a6xx_gpu(to_adreno_gpu(gpu))->slice_mask;
+
+	if (!dumper) {
+		for (int i = 0; i < b->num_shader_blocks; i++) {
+			const struct gen8_shader_block *block = &b->shader_blocks[i];
+			u32 j;
+
+			FOR_EACH_SLICE (j, block->slice_region, slice_mask) {
+				u32 slice_id = SLICE_ID(block->slice_region, j);
+
+				for (u32 sp = 0; sp < block->num_sps; sp++) {
+					for (u32 ctxt = 0; ctxt < block->num_ctx; ctxt++) {
+						gen8_legacy_snapshot_shader(
+							gpu, state, block, sp, ctxt, slice_id);
+					}
+				}
+			}
+		}
+
+		return true;
+	}
+
+	for (int i = 0; i < b->num_shader_blocks; i++) {
+		const struct gen8_shader_block *block = &b->shader_blocks[i];
+		u32 j;
+
+		FOR_EACH_SLICE (j, block->slice_region, slice_mask) {
+			u32 slice_id = SLICE_ID(block->slice_region, j);
+
+			for (u32 sp = 0; sp < block->num_sps; sp++) {
+				for (u32 ctxt = 0; ctxt < block->num_ctx; ctxt++) {
+					/* Build the crash script */
+					struct cd_script p = CD_INIT(dumper);
+
+					u32 read_sel = GEN8_SP_READ_SEL_VAL(
+						ctxt, slice_id, block->location, block->pipeid,
+						block->statetype, block->usptp_id, sp);
+
+					/* Program the aperture */
+					CD_WRITE(&p, REG_A7XX_SP_READ_SEL, read_sel);
+
+					/* Read all the data in one chunk */
+					CD_READ(&p, REG_A7XX_SP_AHB_READ_APERTURE, block->size);
+
+					/* Marker for end of script */
+					CD_FINISH(&p);
+
+					/* Try to run the crash dumper and bail if it times out */
+					if (CD_RUN(&p, gpu))
+						return false;
+
+					snapshot_sect (state, SHADER_V3, block->size * sizeof(u32), {
+						.type = block->statetype,
+						.slice_id = HEADER_SLICE_ID(block->slice_region, slice_id),
+						.sp_index = sp,
+						.usptp = block->usptp_id,
+						.pipe_id = block->pipeid,
+						.location = block->location,
+						.ctxt_id = ctxt,
+						.size = block->size,
+					}) {
+						const u32 *src = dumper->ptr + A8XX_CD_DATA_OFFSET;
+						snapshot_sect_data(&sect, src, block->size * sizeof(u32));
+					}
+				}
+			}
+		}
+	}
+
+	return true;
+}
+
+static void a8xx_snapshot_gmu_bo(struct adreno_gpu_state *state,
+				 struct a6xx_gmu_bo *bo,
+				 unsigned type)
+{
+	snapshot_sect (state, GMU_MEMORY, bo->size, {
+		.type = type,
+		.gmuaddr = bo->iova,
+	}) {
+		snapshot_sect_data(&sect, bo->virt, bo->size);
+	}
+}
+
+static void a8xx_snapshot_gpu_bo(struct adreno_gpu_state *state,
+				 struct drm_gem_object *bo,
+				 uint64_t iova)
+{
+	if (!bo)
+		return;
+
+	snapshot_sect (state, GPU_OBJECT_V2, bo->size, {
+		.type = SNAPSHOT_GPU_OBJECT_GLOBAL,
+		.gpuaddr = iova,
+		.size = bo->size / 4,  /* dwords */
+	}) {
+		void *ptr = msm_gem_get_vaddr(bo);
+		snapshot_sect_data(&sect, ptr, bo->size);
+		msm_gem_put_vaddr(bo);
+	}
+}
+
+static void a8xx_snapshot_gmu_hfi_history(struct msm_gpu *gpu,
+					  struct adreno_gpu_state *state)
+{
+	struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
+	struct a6xx_gpu *a6xx_gpu = to_a6xx_gpu(adreno_gpu);
+	struct a6xx_gmu *gmu = &a6xx_gpu->gmu;
+	unsigned i, j;
+
+	BUILD_BUG_ON(ARRAY_SIZE(gmu->queues) != ARRAY_SIZE(state->hfi_queue_history));
+
+	for (i = 0; i < ARRAY_SIZE(gmu->queues); i++) {
+		struct a6xx_hfi_queue *queue = &gmu->queues[i];
+		for (j = 0; j < HFI_HISTORY_SZ; j++) {
+			unsigned idx = (j + queue->history_idx) % HFI_HISTORY_SZ;
+			state->hfi_queue_history[i][j] = queue->history[idx];
+		}
+	}
+}
+
+#define GEN8_DEBUGBUS_BLOCK_SIZE 0x100
+
+#define GEN8_DEBUGBUS_BLOCK_IDX_1 128
+#define GEN8_DEBUGBUS_BLOCK_IDX_2 192
+
+static void gen8_cx_dbgc_debugbus_select(struct msm_gpu *gpu, u32 block_id, u32 index)
+{
+	u32 reg;
+
+	reg = A7XX_DBGC_CFG_DBGBUS_SEL_D_PING_INDEX(index) |
+		A7XX_DBGC_CFG_DBGBUS_SEL_D_PING_BLK_SEL(block_id);
+
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_SEL_A, reg);
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_SEL_B, reg);
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_SEL_C, reg);
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_SEL_D, reg);
+
+	/* Wait 1 us to make sure the data is flowing */
+	udelay(1);
+}
+
+static void gen8_debugbus_internal_read_setup(struct msm_gpu *gpu)
+{
+	/* Clear the DBGC logic/events */
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_CNTLM, 0x70000000);
+
+	/* Enable the internal buffer mode and program to tail mode */
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_OPL, 0x28000000);
+
+	/* Program the logic unit 0 to high when the side data is 1 */
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_BYTEL_0, 0x0000000c);
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_MASKL_0, 0x000000f0);
+
+	/* Enable the DBGC logic/events */
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_CNTLM, 0x07000000);
+}
+
+static void gen8_cx_gc_us_i_0_debugbus_read(
+	struct msm_gpu *gpu, u32 block_id, u32 index, u32 data[2])
+{
+	gen8_cx_dbgc_debugbus_select(gpu, block_id, index);
+
+	/*
+	 * Select lower 32 bits of internal buffer data for internal mux[1:0]
+	 * DBGC_CFG_DBGBUS_IDX.internalIndex0 = 24 (0x18)
+	 * DBGC_CFG_DBGBUS_IDX.internalIndex1 = 25 (0x19)
+	 */
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_IDX, 0x00001918);
+	udelay(1);
+
+	data[0] = gpu_read(gpu, REG_A6XX_DBGC_CFG_DBGBUS_TRACE_BUF3);
+
+	/*
+	 * Select upper 32 bits of internal buffer data for internal mux[1:0]
+	 * DBGC_CFG_DBGBUS_IDX.internalIndex0 = 26 (0x1a)
+	 * DBGC_CFG_DBGBUS_IDX.internalIndex1 = 27 (0x1b)
+	 */
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_IDX, 0x00001a1b);
+	udelay(1);
+
+	data[1] = gpu_read(gpu, REG_A6XX_DBGC_CFG_DBGBUS_TRACE_BUF3);
+}
+
+/* gen8_cx_dbgc_debug_bus_read() - Read data from trace bus */
+static void gen8_cx_debug_bus_read(
+	struct msm_gpu *gpu, u32 block_id, u32 index, u32 data[2])
+{
+	gen8_cx_dbgc_debugbus_select(gpu, block_id, index);
+
+	data[0] = gpu_read(gpu, REG_A6XX_DBGC_CFG_DBGBUS_TRACE_BUF2);
+	data[1] = gpu_read(gpu, REG_A6XX_DBGC_CFG_DBGBUS_TRACE_BUF1);
+}
+
+static void gen8_snapshot_cx_dbgc_debugbus_block(
+	struct msm_gpu *gpu, uint32_t block_id, size_t count,
+	struct adreno_snapshot_sect *sect)
+{
+	/* This block requires an out of order access due to a HW limitation.
+	 * (QCTDD12240539)
+	 */
+	if (block_id == DEBUGBUS_CX_GC_US_I_0) {
+		static u32 data[GEN8_DEBUGBUS_BLOCK_SIZE * 2];
+		gen8_debugbus_internal_read_setup(gpu);
+		for (int i = 0; i < GEN8_DEBUGBUS_BLOCK_IDX_1; i++)
+			gen8_cx_gc_us_i_0_debugbus_read(gpu, block_id, i, &data[i * 2]);
+		for (int i = GEN8_DEBUGBUS_BLOCK_IDX_2; i < GEN8_DEBUGBUS_BLOCK_SIZE; i++)
+			gen8_cx_gc_us_i_0_debugbus_read(gpu, block_id, i, &data[i * 2]);
+		for (int i = GEN8_DEBUGBUS_BLOCK_IDX_1; i < GEN8_DEBUGBUS_BLOCK_IDX_2; i++)
+			gen8_cx_gc_us_i_0_debugbus_read(gpu, block_id, i, &data[i * 2]);
+		snapshot_sect_arr(sect, data);
+	} else {
+		for (int i = 0; i < count; i++) {
+			u32 data[2];
+			gen8_cx_debug_bus_read(gpu, block_id, i, data);
+			snapshot_sect_arr(sect, data);
+		}
+	}
+}
+
+static void gen8_dbgc_side_debug_bus_read(struct msm_gpu *gpu,
+	u32 block_id, u32 index, struct adreno_snapshot_sect *sect)
+{
+	u32 reg, side_data;
+
+	gen8_cx_dbgc_debugbus_select(gpu, block_id, index);
+
+	reg = gpu_read(gpu, REG_A6XX_DBGC_CFG_DBGBUS_OVER);
+	side_data = FIELD_GET(GENMASK(27, 24), reg);
+
+	snapshot_sect_u32(sect, side_data);
+}
+
+static void gen8_snapshot_dbgc_side_debugbus_block(
+	struct msm_gpu *gpu, uint32_t block_id, size_t count,
+	struct adreno_snapshot_sect *sect)
+{
+	for (int i = 0; i < count; i++)
+		gen8_dbgc_side_debug_bus_read(gpu, block_id, i, sect);
+}
+
+static void gen8_get_debugbus(struct msm_gpu *gpu, struct adreno_gpu_state *state)
+{
+	const struct gen8_snapshot_block_list *b = block_list(gpu);
+
+	/* Set up the GX debug bus */
+
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_CNTLT,
+		A6XX_DBGC_CFG_DBGBUS_CNTLT_SEGT(0xf));
+
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_CNTLM,
+		A6XX_DBGC_CFG_DBGBUS_CNTLM_ENABLE(0xf));
+
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_IVTL_0, 0);
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_IVTL_1, 0);
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_IVTL_2, 0);
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_IVTL_3, 0);
+
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_BYTEL_0, 0x76543210);
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_BYTEL_1, 0xFEDCBA98);
+
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_MASKL_0, 0);
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_MASKL_1, 0);
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_MASKL_2, 0);
+	gpu_write(gpu, REG_A6XX_DBGC_CFG_DBGBUS_MASKL_3, 0);
+
+	/* Read the blocks: */
+
+	for (int i = 0; i < b->debugbus_blocks_len; i++) {
+		size_t count = GEN8_DEBUGBUS_BLOCK_SIZE;
+		uint32_t block_id = b->debugbus_blocks[i];
+
+		snapshot_sect (state, DEBUGBUS, 4 * 2 * count, {
+			/* debugbus section hdr: */
+			.id = block_id,
+			.count = count * 2,
+		}) {
+			gen8_snapshot_cx_dbgc_debugbus_block(gpu, block_id, count, &sect);
+		}
+
+		snapshot_sect (state, SIDE_DEBUGBUS, 4 * count, {
+			/* side_debugbus section hdr: */
+			.id = block_id,
+			.size = count,
+			.valid_data = 0x4,
+		}) {
+			gen8_snapshot_dbgc_side_debugbus_block(gpu, block_id, count, &sect);
+		}
+	}
+
+	for (int i = 0; i < b->gbif_debugbus_blocks_len; i++) {
+		size_t count = GEN8_DEBUGBUS_BLOCK_SIZE;
+		uint32_t block_id = b->gbif_debugbus_blocks[i];
+
+		snapshot_sect (state, DEBUGBUS, 4 * 2 * count, {
+			/* debugbus section hdr: */
+			.id = block_id,
+			.count = count * 2,
+		}) {
+			gen8_snapshot_cx_dbgc_debugbus_block(gpu, block_id, count, &sect);
+		}
+
+		snapshot_sect (state, SIDE_DEBUGBUS, 4 * count, {
+			/* side_debugbus section hdr: */
+			.id = block_id,
+			.size = count,
+			.valid_data = 0x4,
+		}) {
+			gen8_snapshot_dbgc_side_debugbus_block(gpu, block_id, count, &sect);
+		}
+	}
+}
+
+struct msm_gpu_state *a8xx_gpu_state_get(struct msm_gpu *gpu)
+{
+	const struct gen8_snapshot_block_list *b = block_list(gpu);
+	struct adreno_crashdumper _dumper = { 0 }, *dumper = NULL;
+	struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
+	struct a6xx_gpu *a6xx_gpu = to_a6xx_gpu(adreno_gpu);
+	struct adreno_gpu_state *state;
+	// TODO need to get status from SMMU on some gen8..
+	bool stalled = !!(gpu_read(gpu, REG_A8XX_RBBM_MISC_STATUS) &
+			A8XX_RBBM_MISC_STATUS_SMMU_STALLED_ON_FAULT);
+
+	if (!b)
+		return NULL;
+
+	state = kzalloc_obj(*state);
+	if (!state)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&state->objs);
+
+	/* Get the generic state from the adreno core */
+	adreno_gpu_state_get(gpu, &state->base);
+
+	adreno_snapshot_registers_v2(gpu, state, b->gmu_cx_unsliced_regs);
+
+	a8xx_snapshot_gmu_bo(state, &a6xx_gpu->gmu.log, SNAPSHOT_GMU_MEM_LOG);
+	a8xx_snapshot_gmu_bo(state, &a6xx_gpu->gmu.hfi, SNAPSHOT_GMU_MEM_HFI);
+	a8xx_snapshot_gmu_bo(state, &a6xx_gpu->gmu.debug, SNAPSHOT_GMU_MEM_DEBUG);
+
+	a8xx_snapshot_gmu_hfi_history(gpu, state);
+
+	/* If GX isn't on the rest of the data isn't going to be accessible */
+	if (!a6xx_gmu_gx_is_on(adreno_gpu))
+		return &state->base;
+
+	snapshot_sect (state, DEBUG, 4, {
+		.type = SNAPSHOT_DEBUG_SLICE_MASK,
+		.size = 1,
+	}) {
+		snapshot_sect_u32(&sect, a6xx_gpu->slice_mask);
+	}
+
+	gen8_get_debugbus(gpu, state);
+
+	/*
+	 * Try to initialize the crashdumper, if we are not dumping state
+	 * with the SMMU stalled.  The crashdumper needs memory access to
+	 * write out GPU state, so we need to skip this when the SMMU is
+	 * stalled in response to an iova fault
+	 */
+	if (!stalled && !gpu->needs_hw_init &&
+	    !a8xx_crashdumper_init(gpu, &_dumper)) {
+		dumper = &_dumper;
+	}
+
+	/* Clear aperture register */
+	a8xx_aperture_clear(gpu);
+
+	/*
+	 * Assert the isStatic bit before triggering snapshot.
+	 * BIT(0): GPU activity during snapshot dump
+	 * BIT(1): GPU UNSLICE activity during snapshot dump
+	 * Similarly, BIT(4) for slice-0, BIT(5) for slice-1 and so on.
+	 */
+	gpu_write(gpu, REG_A8XX_RBBM_SNAPSHOT_STATUS, BIT(0) | BIT(1) |
+			FIELD_PREP(GENMASK(7, 4), a6xx_gpu->slice_mask));
+
+	/* Dump the registers which get affected by crash dumper trigger */
+	for (int i = 0; i < b->num_pre_crashdumper_regs; i++) {
+		const struct gen8_reg_list *regs = &b->pre_crashdumper_regs[i];
+		u32 j;
+
+		FOR_EACH_SLICE (j, regs->slice_region, a6xx_gpu->slice_mask) {
+			u32 slice_id = SLICE_ID(regs->slice_region, j);
+			gen8_legacy_snapshot_registers(gpu, state, regs, slice_id);
+		}
+	}
+
+	/*
+	 * If crashdumper timed out while dumping this section skip everything
+	 * since even AHB acceses to the GPU might cause NoC errors.
+	 */
+	if (!gen8_reglist_snapshot(gpu, state, dumper))
+		goto out;
+
+	/* CP indexed regs data */
+	if (!gen8_snapshot_cp_indexed_regs(gpu, state, dumper))
+		goto out;
+
+	/* Mempool debug data */
+	if (!gen8_snapshot_mempool(gpu, state, dumper))
+		goto out;
+
+	/*
+	 * CP MVC register section
+	 * If crashdumper timed out while dumping any section below skip everything
+	 * since even AHB acceses to the GPU might cause NoC errors.
+	 */
+	if (!gen8_snapshot_mvc_regs(gpu, state, dumper, b->cp_clusters, b->num_cp_clusters))
+		goto out;
+
+	/* MVC register section */
+	if (!gen8_snapshot_mvc_regs(gpu, state, dumper, b->clusters, b->num_clusters))
+		goto out;
+
+	/* registers dumped through DBG AHB */
+	if (!gen8_snapshot_dbgahb_regs(gpu, state, dumper))
+		goto out;
+
+	/* Shader memory */
+	if (!gen8_snapshot_shader(gpu, state, dumper))
+		goto out;
+
+	/*
+	 * Capture preempt records after cp_clusters, to make this easier
+	 * to decode (ie. so we already know CP_CONTEXT_SWITCH_SMMU_INFO and
+	 * CP_CONTEXT_SWITCH_PRIV_NON_SECURE_RESTORE_ADDR) when crashdec sees
+	 * the logged GPU objs)
+	 */
+	for (unsigned i = 0; i < gpu->nr_rings; i++) {
+		a8xx_snapshot_gpu_bo(state, a6xx_gpu->preempt_bo[i],
+				     a6xx_gpu->preempt_iova[i]);
+		a8xx_snapshot_gpu_bo(state, a6xx_gpu->preempt_smmu_bo[i],
+				     a6xx_gpu->preempt_smmu_iova[i]);
+	}
+
+out:
+	gpu_write(gpu, REG_A8XX_RBBM_SNAPSHOT_STATUS, 0x0);
+
+	if (dumper)
+		msm_gem_kernel_put(dumper->bo, gpu->vm);
+
+	state->gpu_initialized = !gpu->needs_hw_init;
+
+	return  &state->base;
+}
+
+static void a8xx_gpu_state_destroy(struct kref *kref)
+{
+	struct adreno_state_memobj *obj, *tmp;
+	struct msm_gpu_state *state = container_of(kref,
+			struct msm_gpu_state, ref);
+	struct adreno_gpu_state *adreno_state = container_of(state,
+			struct adreno_gpu_state, base);
+
+	list_for_each_entry_safe (obj, tmp, &adreno_state->objs, node) {
+		list_del(&obj->node);
+		kvfree(obj->ptr);
+		kfree(obj);
+	}
+
+	adreno_gpu_state_destroy(state);
+	kfree(adreno_state);
+}
+
+int a8xx_gpu_state_put(struct msm_gpu_state *state)
+{
+	if (IS_ERR_OR_NULL(state))
+		return 1;
+
+	return kref_put(&state->ref, a8xx_gpu_state_destroy);
+}
+
+void a8xx_show(struct msm_gpu *gpu, struct msm_gpu_state *state,
+		struct drm_printer *p)
+{
+	struct adreno_gpu_state *adreno_state = container_of(state,
+			struct adreno_gpu_state, base);
+	struct adreno_state_memobj *obj;
+
+	if (IS_ERR_OR_NULL(state))
+		return;
+
+	drm_printf(p, "gpu-initialized: %d\n", adreno_state->gpu_initialized);
+
+	adreno_show(gpu, state, p);
+
+	drm_puts(p, "gmu-hfi:\n");
+	for (int i = 0; i < ARRAY_SIZE(adreno_state->hfi_queue_history); i++) {
+		drm_printf(p, "    queue-history[%u]:", i);
+		for (int j = 0; j < HFI_HISTORY_SZ; j++) {
+			drm_printf(p, " %d", adreno_state->hfi_queue_history[i][j]);
+		}
+		drm_printf(p, "\n");
+	}
+
+	/*
+	 * Parsing note: do not assume each snapshot obj is only a single
+	 * section, assume sub-allocation.
+	 */
+	drm_puts(p, "snapshot:\n");
+	list_for_each_entry (obj, &adreno_state->objs, node) {
+		drm_printf(p, "    size: %zu\n", obj->size);
+		adreno_show_object(p, &obj->ptr, obj->size, &obj->encoded);
+	}
+}
diff --git a/drivers/gpu/drm/msm/adreno/adreno_snapshot.h b/drivers/gpu/drm/msm/adreno/adreno_snapshot.h
index f9956e0bc1d5..e8ff102a628b 100644
--- a/drivers/gpu/drm/msm/adreno/adreno_snapshot.h
+++ b/drivers/gpu/drm/msm/adreno/adreno_snapshot.h
@@ -7,6 +7,8 @@
 
 #include <linux/types.h>
 
+#include "msm_gpu.h"
+
 /* Snapshot header */
 
 /* High word is static, low word is snapshot version ID */
@@ -150,6 +152,7 @@ struct adreno_snapshot_gmu_mem {
 	uint64_t gmuaddr;
 	uint64_t gpuaddr;
 } __packed;
+#define STRUCT_GMU_MEMORY adreno_snapshot_gmu_mem
 
 /* Register sub-section header */
 struct adreno_snapshot_regs {
@@ -172,6 +175,7 @@ struct adreno_snapshot_indexed_regs_v2 {
 	u32 pipe_id;	/* Id of pipe, BV, Br etc */
 	u32 slice_id;	/* Slice ID to be dumped */
 } __packed;
+#define STRUCT_INDEXED_REGS_V2 adreno_snapshot_indexed_regs_v2
 
 /* MVC register sub-section header */
 struct adreno_snapshot_mvc_regs {
@@ -195,6 +199,7 @@ struct adreno_snapshot_mvc_regs_v3 {
 	u32 sp_id;
 	u32 usptp_id;
 } __packed;
+#define STRUCT_MVC_V3 adreno_snapshot_mvc_regs_v3
 
 /* Debug data sub-section header */
 
@@ -222,17 +227,20 @@ struct adreno_snapshot_debug {
 	int type;    /* Type identifier for the attached tata */
 	int size;   /* Size of the section in dwords */
 } __packed;
+#define STRUCT_DEBUG adreno_snapshot_debug
 
 struct adreno_snapshot_debugbus {
 	int id;	   /* Debug bus ID */
 	int count; /* Number of dwords in the dump */
 } __packed;
+#define STRUCT_DEBUGBUS adreno_snapshot_debugbus
 
 struct adreno_snapshot_side_debugbus {
 	int id;   /* Debug bus ID */
 	int size; /* Number of dwords in the dump */
 	int valid_data;  /* Mask of valid bits of the side debugbus */
 } __packed;
+#define STRUCT_SIDE_DEBUGBUS adreno_snapshot_side_debugbus
 
 struct adreno_snapshot_shader {
 	int type;  /* SP/TP statetype */
@@ -259,6 +267,7 @@ struct adreno_snapshot_shader_v3 {
 	u32 ctxt_id; /* Context ID */
 	u32 size;  /* Number of dwords in the dump */
 } __packed;
+#define STRUCT_SHADER_V3 adreno_snapshot_shader_v3
 
 #define TRACE_BUF_NUM_SIG 4
 
@@ -302,6 +311,7 @@ struct adreno_snapshot_gpu_object_v2 {
 	__u64 ptbase;  /* Base for the pagetable the GPU address is valid in */
 	__u64 size;    /* Size of the object (in dwords) */
 } __packed;
+#define STRUCT_GPU_OBJECT_V2 adreno_snapshot_gpu_object_v2
 
 struct adreno_snapshot_eventlog {
 	/** @type: Type of the event log buffer */
@@ -319,3 +329,198 @@ struct adreno_snapshot_gmu_version {
 	u32 value;
 } __packed;
 
+struct adreno_snapshot_empty_header { u8 dummy; };
+#define STRUCT_REGS_V2 adreno_snapshot_debugbus
+
+#define REG_COUNT(_ptr) ((_ptr[1] - _ptr[0]) + 1)
+
+static int adreno_snapshot_regs_count(const u32 *ptr)
+{
+	unsigned int count = 0;
+	unsigned int group_count;
+
+	for ( ; ptr[0] != UINT_MAX; ptr += 2) {
+		group_count = REG_COUNT(ptr);
+		if (group_count == 1)
+			count += group_count + 1;
+		else
+			count += group_count + 2;
+	}
+	return count;
+}
+
+struct adreno_gpu_state {
+	struct msm_gpu_state base;
+
+	s32 hfi_queue_history[HFI_MAX_QUEUES][HFI_HISTORY_SZ];
+
+	/* list of struct adreno_state_memobj */
+	struct list_head objs;
+
+	bool gpu_initialized;
+};
+
+struct adreno_state_memobj {
+	struct list_head node;
+	void *ptr;	/* replaced by ascii85 encoded string in adreno_show_object() */
+	bool encoded;
+	size_t size;
+};
+
+/**
+ * A on-stack helper to build snapshot sections setting the size field when the
+ * helper goes out of scope, to simplify constructing snapshot sections, but
+ * without resorting to the callback approach that kgsl uses.
+ */
+struct adreno_snapshot_sect {
+	struct adreno_state_memobj *obj;
+	void *cur, *end;
+};
+
+static void __snapshot_sect_fini(struct adreno_snapshot_sect *s)
+{
+	struct adreno_snapshot_section_header *hdr = (void *)s->obj->ptr;
+	hdr->size = s->obj->size = ((u8 *)s->cur - (u8 *)s->obj->ptr);
+}
+
+static struct adreno_snapshot_sect
+__snapshot_sect_init(struct adreno_gpu_state *state, uint16_t id, size_t extra_sz,
+		     void *sect_hdr, size_t sect_hdr_sz, const char *func, int line)
+{
+	/* A few section types have no section header: */
+	switch (id) {
+	case ADRENO_SNAPSHOT_SECTION_REGS_V2:
+		sect_hdr_sz = 0;
+	default:
+		break;
+	}
+
+	pr_debug("%s:%d: snapshot_sect(id=%04x, extra_sz=%zu, sect_hdr_sz=%zu)\n",
+		 func, line, id, extra_sz, sect_hdr_sz);
+
+	size_t sz = sizeof(struct adreno_snapshot_section_header) + sect_hdr_sz + extra_sz;
+	struct adreno_state_memobj *obj = kzalloc_obj(*obj);
+	void *ptr = kvzalloc(sz, GFP_KERNEL);
+
+	if (!obj || !ptr) {
+		kfree(obj);
+		kvfree(ptr);
+		return (struct adreno_snapshot_sect){};
+	}
+
+	obj->ptr = ptr;
+
+	list_add_tail(&obj->node, &state->objs);
+
+	/* Common snapshot section header: */
+	struct adreno_snapshot_section_header *hdr = ptr;
+	hdr->magic = SNAPSHOT_SECTION_MAGIC;
+	hdr->id = id;
+	/* size filled out in __snapshot_sect_fini() */
+	ptr += sizeof(*hdr);
+
+	/* Section type header: */
+	memcpy(ptr, sect_hdr, sect_hdr_sz);
+	ptr += sect_hdr_sz;
+
+	return (struct adreno_snapshot_sect) {
+		.obj = obj,
+		.cur = ptr,
+		.end = ptr + extra_sz,
+	};
+}
+
+static void
+snapshot_sect_data(struct adreno_snapshot_sect *s, const void *data, size_t sz)
+{
+	/* overflow check: */
+	if (WARN_ON_ONCE(s->cur + sz > s->end))
+		return;
+
+	memcpy(s->cur, data, sz);
+	s->cur += sz;
+}
+
+#define snapshot_sect_arr(_s, _d) snapshot_sect_data(_s, _d, sizeof(_d))
+
+static void
+snapshot_sect_u32(struct adreno_snapshot_sect *s, u32 val)
+{
+	snapshot_sect_data(s, &val, sizeof(val));
+}
+
+#define __snapshot_sect(_label, _state, _id, _extra_sz, ...)		\
+	for (struct adreno_snapshot_sect sect = ({			\
+		struct STRUCT_ ## _id sect_hdr = __VA_ARGS__;		\
+		__snapshot_sect_init(_state, ADRENO_SNAPSHOT_SECTION_ ## _id,	\
+				     _extra_sz, &sect_hdr, 		\
+				     sizeof(sect_hdr), 			\
+				     __func__, __LINE__); });		\
+	     sect.obj; ({ goto _label; }))				\
+		if (false) {						\
+_label:									\
+			__snapshot_sect_fini(&sect); 			\
+			break;						\
+		} else
+
+#define snapshot_sect(_id, _extra_sz, ...) \
+	__snapshot_sect(__UNIQUE_ID(label), _id, _extra_sz, __VA_ARGS__)
+
+struct adreno_crashdumper {
+	void *ptr;
+	size_t data_offset;
+	struct drm_gem_object *bo;
+	u64 iova;
+};
+
+/**
+ * On-stack object to represent a single crashdumper invocation, tracking
+ * in/out ptrs and checking for overflow.
+ */
+struct cd_script {
+	struct adreno_crashdumper *dumper;
+	u64 *in;
+	u64 out;
+};
+
+/* These should be implemented by the gen specific backend: */
+static inline int __cd_run(struct msm_gpu *gpu, struct adreno_crashdumper *dumper);
+static inline bool __cd_check_overflow(struct cd_script *p);
+
+static inline struct cd_script CD_INIT(struct adreno_crashdumper *dumper)
+{
+	return (struct cd_script) {
+		.dumper = dumper,
+		.in = dumper->ptr,
+		.out = dumper->iova + dumper->data_offset,
+	};
+}
+
+static inline void CD_WRITE(struct cd_script *p, u32 reg, u32 val)
+{
+	*(p->in++) = val;
+	*(p->in++) = FIELD_PREP(GENMASK(63, 44), reg) | BIT(21) | BIT(0);
+	__cd_check_overflow(p);
+}
+
+static inline void CD_READ(struct cd_script *p, u32 reg, u32 dwords)
+{
+	*(p->in++) = p->out;
+	*(p->in++) = FIELD_PREP(GENMASK(63, 44), reg) | dwords;
+	p->out += sizeof(u32) * dwords;
+	__cd_check_overflow(p);
+}
+
+static void CD_FINISH(struct cd_script *p)
+{
+	*(p->in++) = 0;
+	*(p->in++) = 0;
+	__cd_check_overflow(p);
+}
+
+static int CD_RUN(struct cd_script *p, struct msm_gpu *gpu)
+{
+	if (!__cd_check_overflow(p))
+		return -ENOSPC;
+	return __cd_run(gpu, p->dumper);
+}
-- 
2.56.0


  parent reply	other threads:[~2026-10-20 20:48 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-20 20:46 [PATCH 00/12] drm/msm/a8xx: GPU state capture support Rob Clark
2026-10-20 20:46 ` [PATCH 01/12] drm/msm: Import adreno_snapshot.h Rob Clark
2026-10-20 20:46 ` [PATCH 02/12] drm/msm/a8xx: Export aperture helpers Rob Clark
2026-10-20 20:46 ` [PATCH 03/12] drm/msm/a8xx: Add a8xx_aperture_acquire_slice() Rob Clark
2026-10-20 20:46 ` [PATCH 04/12] drm/msm/a8xx: Allow nonctx reglist to include GMU regs Rob Clark
2026-10-21  8:38   ` Konrad Dybcio
2026-10-20 20:46 ` [PATCH 05/12] drm/msm/a8xx: Stop getting GMEM_PROTECT from nonctx list Rob Clark
2026-10-20 20:46 ` [PATCH 06/12] drm/msm/adreno: Fix PROTECT span length Rob Clark
2026-10-21  8:40   ` Konrad Dybcio
2026-10-20 20:46 ` [PATCH 07/12] drm/msm/adreno: add temporary __maybe_unused Rob Clark
2026-10-20 20:46 ` [PATCH 08/12] drm/msm/a8xx: Import generated headers Rob Clark
2026-10-20 20:46 ` Rob Clark [this message]
2026-10-20 23:13   ` [PATCH 09/12] drm/msm/a8xx: devcoredump state capture support sashiko-bot
2026-10-21  8:42   ` Konrad Dybcio
2026-10-20 20:46 ` [PATCH 10/12] drm/msm/a8xx: Switch to generated nonctx regs Rob Clark
2026-10-20 23:32   ` sashiko-bot
2026-10-21  8:43   ` Konrad Dybcio
2026-10-20 20:46 ` [PATCH 11/12] drm/msm/a8xx: Switch to generated protect regs Rob Clark
2026-10-20 23:41   ` sashiko-bot
2026-10-21  8:44   ` Konrad Dybcio
2026-10-20 20:46 ` [PATCH 12/12] drm/msm/adreno: Remove temporary __maybe_unused Rob Clark

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261020204728.28844-10-robin.clark@oss.qualcomm.com \
    --to=robin.clark@oss.qualcomm.com \
    --cc=abhinav.kumar@linux.dev \
    --cc=airlied@gmail.com \
    --cc=akhilpo@oss.qualcomm.com \
    --cc=akoskovich@pm.me \
    --cc=bvalluru@qti.qualcomm.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=freedreno@lists.freedesktop.org \
    --cc=jesszhan0024@gmail.com \
    --cc=jie.zhang@oss.qualcomm.com \
    --cc=konradybcio@kernel.org \
    --cc=linux-arm-msm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lumag@kernel.org \
    --cc=marijn.suijten@somainline.org \
    --cc=sean@poorly.run \
    --cc=simona@ffwll.ch \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.