[PATCH 3/4] drm/msm/a8xx: add a memory-only GPU crash state

From: Nico Angelo Dela Cruz

Date: Sun Nov 01 2026 - 13:05:49 EST


a8xx had no gpu_state_get callback, so no devcoredump was ever
produced when the GPU hung or faulted.

Add a8xx_gpu_state_get(), a memory-only snapshot: the generic Adreno
state from adreno_gpu_state_get() (ring buffer contents and fences,
with rptr read from the hw_apriv shadow rather than a register), the
GMU log/HFI/debug buffers and the HFI queue history. No register is
read, because the GMU may be wedged and GX powered off when this runs
from the recover worker or the SMMU fault path. As a result a8xx
dumps always report "gpu-initialized: 0", as a6xx/a7xx dumps do when
GX is off.

The shared part moves out of a6xx_gpu_state_get() into
a6xx_gpu_state_get_gmu(), which also runs adreno_gpu_state_get().
It is a pure move except for a new gmu_regs parameter that gates
a6xx_get_gmu_registers(); a6xx/a7xx pass true, so their output is
unchanged. Reviewed with git diff --color-moved=dimmed-zebra: apart
from the moved lines, the new code is the helper's signature, the
"if (gmu_regs)" check, the locals the helper needs, and the call.

Tested on an Adreno 830: IGT msm_recovery gpu-fault (6 of 6 runs) and
natural GPU hangs produce dumps with the ringbuffer, gmu-log, gmu-hfi
and queue-history sections; capture takes 1.1 to 4.0 ms.

a6xx/a7xx: build and pure-move review only, Tested-by welcome.

Signed-off-by: Nico Angelo Dela Cruz <nicoangelodelacruz@xxxxxxxxx>
---
drivers/gpu/drm/msm/adreno/a6xx_gpu.c | 7 +++
drivers/gpu/drm/msm/adreno/a6xx_gpu.h | 1 +
drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c | 47 ++++++++++++++++-----
3 files changed, 45 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c
index 92e8c8abb..0f4b0af0a 100644
--- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.c
+++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.c
@@ -2918,9 +2918,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 = a6xx_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 = a6xx_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 79912a33e..03432abf9 100644
--- a/drivers/gpu/drm/msm/adreno/a6xx_gpu.h
+++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu.h
@@ -338,6 +338,7 @@ void a6xx_show(struct msm_gpu *gpu, struct msm_gpu_state *state,
struct drm_printer *p);

struct msm_gpu_state *a6xx_gpu_state_get(struct msm_gpu *gpu);
+struct msm_gpu_state *a8xx_gpu_state_get(struct msm_gpu *gpu);
int a6xx_gpu_state_put(struct msm_gpu_state *state);

void a6xx_bus_clear_pending_transactions(struct adreno_gpu *adreno_gpu, bool gx_off);
diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c
index 26c17861c..a3751358a 100644
--- a/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c
+++ b/drivers/gpu/drm/msm/adreno/a6xx_gpu_state.c
@@ -1629,25 +1629,19 @@ static void a7xx_get_indexed_registers(struct msm_gpu *gpu,
return;
}

-struct msm_gpu_state *a6xx_gpu_state_get(struct msm_gpu *gpu)
+static void a6xx_gpu_state_get_gmu(struct msm_gpu *gpu,
+ struct a6xx_gpu_state *a6xx_state, bool gmu_regs)
{
- struct a6xx_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 a6xx_gpu_state *a6xx_state = kzalloc_obj(*a6xx_state);
- bool stalled;
-
- if (!a6xx_state)
- return ERR_PTR(-ENOMEM);
-
- INIT_LIST_HEAD(&a6xx_state->objs);

/* Get the generic state from the adreno core */
adreno_gpu_state_get(gpu, &a6xx_state->base);

if (!adreno_has_gmu_wrapper(adreno_gpu) &&
!adreno_has_rgmu(adreno_gpu)) {
- a6xx_get_gmu_registers(gpu, a6xx_state);
+ if (gmu_regs)
+ a6xx_get_gmu_registers(gpu, a6xx_state);

a6xx_state->gmu_log = a6xx_snapshot_gmu_bo(a6xx_state, &a6xx_gpu->gmu.log);
a6xx_state->gmu_hfi = a6xx_snapshot_gmu_bo(a6xx_state, &a6xx_gpu->gmu.hfi);
@@ -1655,6 +1649,21 @@ struct msm_gpu_state *a6xx_gpu_state_get(struct msm_gpu *gpu)

a6xx_snapshot_gmu_hfi_history(gpu, a6xx_state);
}
+}
+
+struct msm_gpu_state *a6xx_gpu_state_get(struct msm_gpu *gpu)
+{
+ struct a6xx_crashdumper _dumper = { 0 }, *dumper = NULL;
+ struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
+ struct a6xx_gpu_state *a6xx_state = kzalloc_obj(*a6xx_state);
+ bool stalled;
+
+ if (!a6xx_state)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&a6xx_state->objs);
+
+ a6xx_gpu_state_get_gmu(gpu, a6xx_state, true);

/* If GX isn't on the rest of the data isn't going to be accessible */
if (!adreno_gpu->funcs->gx_is_on(adreno_gpu))
@@ -1714,6 +1723,24 @@ struct msm_gpu_state *a6xx_gpu_state_get(struct msm_gpu *gpu)
return &a6xx_state->base;
}

+/*
+ * Memory-only snapshot for a8xx: no register is read, as the GMU may be
+ * wedged and GX powered off by the time this runs.
+ */
+struct msm_gpu_state *a8xx_gpu_state_get(struct msm_gpu *gpu)
+{
+ struct a6xx_gpu_state *a6xx_state = kzalloc_obj(*a6xx_state);
+
+ if (!a6xx_state)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&a6xx_state->objs);
+
+ a6xx_gpu_state_get_gmu(gpu, a6xx_state, false);
+
+ return &a6xx_state->base;
+}
+
static void a6xx_gpu_state_destroy(struct kref *kref)
{
struct a6xx_state_memobj *obj, *tmp;
--
2.53.0