mirror of https://lore.kernel.org/lkml/
 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: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-20 20:46 [PATCH 00/12] drm/msm/a8xx: GPU " 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-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-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 20:46 ` [PATCH 10/12] drm/msm/a8xx: Switch to generated nonctx regs Rob Clark
2026-10-20 20:46 ` [PATCH 11/12] drm/msm/a8xx: Switch to generated protect regs Rob Clark
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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®