drm/msm: add a5xx specific debugfs
Add some debugfs to dump out PFP and ME microcontroller state, as well as some of the queues (MEQ and ROQ). Also add a debugfs file to trigger a GPU reset (and reloading the firmware on next submit). Signed-off-by: Rob Clark <robdclark@gmail.com>
This commit is contained in:
parent
3f0689e663
commit
331dc0bc19
|
@ -62,6 +62,8 @@ msm-y := \
|
|||
msm_ringbuffer.o \
|
||||
msm_submitqueue.o
|
||||
|
||||
msm-$(CONFIG_DEBUG_FS) += adreno/a5xx_debugfs.o
|
||||
|
||||
msm-$(CONFIG_DRM_FBDEV_EMULATION) += msm_fbdev.o
|
||||
msm-$(CONFIG_COMMON_CLK) += mdp/mdp4/mdp4_lvds_pll.o
|
||||
msm-$(CONFIG_COMMON_CLK) += hdmi/hdmi_pll_8960.o
|
||||
|
|
188
drivers/gpu/drm/msm/adreno/a5xx_debugfs.c
Normal file
188
drivers/gpu/drm/msm/adreno/a5xx_debugfs.c
Normal file
|
@ -0,0 +1,188 @@
|
|||
/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <drm/drm_print.h>
|
||||
|
||||
#include "a5xx_gpu.h"
|
||||
|
||||
static int pfp_print(struct msm_gpu *gpu, struct drm_printer *p)
|
||||
{
|
||||
int i;
|
||||
|
||||
drm_printf(p, "PFP state:\n");
|
||||
|
||||
for (i = 0; i < 36; i++) {
|
||||
gpu_write(gpu, REG_A5XX_CP_PFP_STAT_ADDR, i);
|
||||
drm_printf(p, " %02x: %08x\n", i,
|
||||
gpu_read(gpu, REG_A5XX_CP_PFP_STAT_DATA));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int me_print(struct msm_gpu *gpu, struct drm_printer *p)
|
||||
{
|
||||
int i;
|
||||
|
||||
drm_printf(p, "ME state:\n");
|
||||
|
||||
for (i = 0; i < 29; i++) {
|
||||
gpu_write(gpu, REG_A5XX_CP_ME_STAT_ADDR, i);
|
||||
drm_printf(p, " %02x: %08x\n", i,
|
||||
gpu_read(gpu, REG_A5XX_CP_ME_STAT_DATA));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meq_print(struct msm_gpu *gpu, struct drm_printer *p)
|
||||
{
|
||||
int i;
|
||||
|
||||
drm_printf(p, "MEQ state:\n");
|
||||
gpu_write(gpu, REG_A5XX_CP_MEQ_DBG_ADDR, 0);
|
||||
|
||||
for (i = 0; i < 64; i++) {
|
||||
drm_printf(p, " %02x: %08x\n", i,
|
||||
gpu_read(gpu, REG_A5XX_CP_MEQ_DBG_DATA));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int roq_print(struct msm_gpu *gpu, struct drm_printer *p)
|
||||
{
|
||||
int i;
|
||||
|
||||
drm_printf(p, "ROQ state:\n");
|
||||
gpu_write(gpu, REG_A5XX_CP_ROQ_DBG_ADDR, 0);
|
||||
|
||||
for (i = 0; i < 512 / 4; i++) {
|
||||
uint32_t val[4];
|
||||
int j;
|
||||
for (j = 0; j < 4; j++)
|
||||
val[j] = gpu_read(gpu, REG_A5XX_CP_ROQ_DBG_DATA);
|
||||
drm_printf(p, " %02x: %08x %08x %08x %08x\n", i,
|
||||
val[0], val[1], val[2], val[3]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int show(struct seq_file *m, void *arg)
|
||||
{
|
||||
struct drm_info_node *node = (struct drm_info_node *) m->private;
|
||||
struct drm_device *dev = node->minor->dev;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct drm_printer p = drm_seq_file_printer(m);
|
||||
int (*show)(struct msm_gpu *gpu, struct drm_printer *p) =
|
||||
node->info_ent->data;
|
||||
|
||||
return show(priv->gpu, &p);
|
||||
}
|
||||
|
||||
#define ENT(n) { .name = #n, .show = show, .data = n ##_print }
|
||||
static struct drm_info_list a5xx_debugfs_list[] = {
|
||||
ENT(pfp),
|
||||
ENT(me),
|
||||
ENT(meq),
|
||||
ENT(roq),
|
||||
};
|
||||
|
||||
/* for debugfs files that can be written to, we can't use drm helper: */
|
||||
static int
|
||||
reset_set(void *data, u64 val)
|
||||
{
|
||||
struct drm_device *dev = data;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
struct msm_gpu *gpu = priv->gpu;
|
||||
struct adreno_gpu *adreno_gpu = to_adreno_gpu(gpu);
|
||||
struct a5xx_gpu *a5xx_gpu = to_a5xx_gpu(adreno_gpu);
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EINVAL;
|
||||
|
||||
/* TODO do we care about trying to make sure the GPU is idle?
|
||||
* Since this is just a debug feature limited to CAP_SYS_ADMIN,
|
||||
* maybe it is fine to let the user keep both pieces if they
|
||||
* try to reset an active GPU.
|
||||
*/
|
||||
|
||||
mutex_lock(&dev->struct_mutex);
|
||||
|
||||
if (adreno_gpu->pm4) {
|
||||
release_firmware(adreno_gpu->pm4);
|
||||
adreno_gpu->pm4 = NULL;
|
||||
}
|
||||
|
||||
if (adreno_gpu->pfp) {
|
||||
release_firmware(adreno_gpu->pfp);
|
||||
adreno_gpu->pfp = NULL;
|
||||
}
|
||||
if (a5xx_gpu->pm4_bo) {
|
||||
if (a5xx_gpu->pm4_iova)
|
||||
msm_gem_put_iova(a5xx_gpu->pm4_bo, gpu->aspace);
|
||||
drm_gem_object_unreference(a5xx_gpu->pm4_bo);
|
||||
a5xx_gpu->pm4_bo = NULL;
|
||||
}
|
||||
|
||||
if (a5xx_gpu->pfp_bo) {
|
||||
if (a5xx_gpu->pfp_iova)
|
||||
msm_gem_put_iova(a5xx_gpu->pfp_bo, gpu->aspace);
|
||||
drm_gem_object_unreference(a5xx_gpu->pfp_bo);
|
||||
a5xx_gpu->pfp_bo = NULL;
|
||||
}
|
||||
|
||||
gpu->needs_hw_init = true;
|
||||
|
||||
pm_runtime_get_sync(&gpu->pdev->dev);
|
||||
gpu->funcs->recover(gpu);
|
||||
|
||||
pm_runtime_put_sync(&gpu->pdev->dev);
|
||||
mutex_unlock(&dev->struct_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SIMPLE_ATTRIBUTE(reset_fops, NULL, reset_set, "%llx\n");
|
||||
|
||||
|
||||
int a5xx_debugfs_init(struct msm_gpu *gpu, struct drm_minor *minor)
|
||||
{
|
||||
struct drm_device *dev = minor->dev;
|
||||
struct dentry *ent;
|
||||
int ret;
|
||||
|
||||
if (!minor)
|
||||
return 0;
|
||||
|
||||
ret = drm_debugfs_create_files(a5xx_debugfs_list,
|
||||
ARRAY_SIZE(a5xx_debugfs_list),
|
||||
minor->debugfs_root, minor);
|
||||
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "could not install a5xx_debugfs_list\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ent = debugfs_create_file("reset", S_IWUGO,
|
||||
minor->debugfs_root,
|
||||
dev, &reset_fops);
|
||||
if (!ent)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1195,6 +1195,7 @@ static const struct adreno_gpu_funcs funcs = {
|
|||
.destroy = a5xx_destroy,
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
.show = a5xx_show,
|
||||
.debugfs_init = a5xx_debugfs_init,
|
||||
#endif
|
||||
.gpu_busy = a5xx_gpu_busy,
|
||||
},
|
||||
|
|
|
@ -49,6 +49,10 @@ struct a5xx_gpu {
|
|||
|
||||
#define to_a5xx_gpu(x) container_of(x, struct a5xx_gpu, base)
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
int a5xx_debugfs_init(struct msm_gpu *gpu, struct drm_minor *minor);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* In order to do lockless preemption we use a simple state machine to progress
|
||||
* through the process.
|
||||
|
|
|
@ -150,6 +150,12 @@ struct msm_gpu *adreno_load_gpu(struct drm_device *dev)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
if (gpu->funcs->debugfs_init) {
|
||||
gpu->funcs->debugfs_init(gpu, dev->primary);
|
||||
gpu->funcs->debugfs_init(gpu, dev->render);
|
||||
gpu->funcs->debugfs_init(gpu, dev->control);
|
||||
}
|
||||
|
||||
return gpu;
|
||||
}
|
||||
|
||||
|
|
|
@ -161,8 +161,11 @@ int msm_debugfs_init(struct drm_minor *minor)
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (priv->kms->funcs->debugfs_init)
|
||||
if (priv->kms->funcs->debugfs_init) {
|
||||
ret = priv->kms->funcs->debugfs_init(priv->kms, minor);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -65,6 +65,8 @@ struct msm_gpu_funcs {
|
|||
#ifdef CONFIG_DEBUG_FS
|
||||
/* show GPU status in debugfs: */
|
||||
void (*show)(struct msm_gpu *gpu, struct seq_file *m);
|
||||
/* for generation specific debugfs: */
|
||||
int (*debugfs_init)(struct msm_gpu *gpu, struct drm_minor *minor);
|
||||
#endif
|
||||
int (*gpu_busy)(struct msm_gpu *gpu, uint64_t *value);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue