Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 21461365 authored by Eric Anholt's avatar Eric Anholt
Browse files

drm/vc4: Add an interface for capturing the GPU state after a hang.



This can be parsed with vc4-gpu-tools tools for trying to figure out
what was going on.

v2: Use __u32-style types.

Signed-off-by: default avatarEric Anholt <eric@anholt.net>
parent b501bacc
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -80,6 +80,8 @@ static const struct drm_ioctl_desc vc4_drm_ioctls[] = {
	DRM_IOCTL_DEF_DRV(VC4_CREATE_BO, vc4_create_bo_ioctl, 0),
	DRM_IOCTL_DEF_DRV(VC4_MMAP_BO, vc4_mmap_bo_ioctl, 0),
	DRM_IOCTL_DEF_DRV(VC4_CREATE_SHADER_BO, vc4_create_shader_bo_ioctl, 0),
	DRM_IOCTL_DEF_DRV(VC4_GET_HANG_STATE, vc4_get_hang_state_ioctl,
			  DRM_ROOT_ONLY),
};

static struct drm_driver vc4_drm_driver = {
+4 −0
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ struct vc4_dev {

	struct drm_fbdev_cma *fbdev;

	struct vc4_hang_state *hang_state;

	/* The kernel-space BO cache.  Tracks buffers that have been
	 * unreferenced by all other users (refcounts of 0!) but not
	 * yet freed, so we can do cheap allocations.
@@ -361,6 +363,8 @@ int vc4_create_shader_bo_ioctl(struct drm_device *dev, void *data,
			       struct drm_file *file_priv);
int vc4_mmap_bo_ioctl(struct drm_device *dev, void *data,
		      struct drm_file *file_priv);
int vc4_get_hang_state_ioctl(struct drm_device *dev, void *data,
			     struct drm_file *file_priv);
int vc4_mmap(struct file *filp, struct vm_area_struct *vma);
int vc4_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma);
void *vc4_prime_vmap(struct drm_gem_object *obj);
+185 −0
Original line number Diff line number Diff line
@@ -40,6 +40,186 @@ vc4_queue_hangcheck(struct drm_device *dev)
		  round_jiffies_up(jiffies + msecs_to_jiffies(100)));
}

struct vc4_hang_state {
	struct drm_vc4_get_hang_state user_state;

	u32 bo_count;
	struct drm_gem_object **bo;
};

static void
vc4_free_hang_state(struct drm_device *dev, struct vc4_hang_state *state)
{
	unsigned int i;

	mutex_lock(&dev->struct_mutex);
	for (i = 0; i < state->user_state.bo_count; i++)
		drm_gem_object_unreference(state->bo[i]);
	mutex_unlock(&dev->struct_mutex);

	kfree(state);
}

int
vc4_get_hang_state_ioctl(struct drm_device *dev, void *data,
			 struct drm_file *file_priv)
{
	struct drm_vc4_get_hang_state *get_state = data;
	struct drm_vc4_get_hang_state_bo *bo_state;
	struct vc4_hang_state *kernel_state;
	struct drm_vc4_get_hang_state *state;
	struct vc4_dev *vc4 = to_vc4_dev(dev);
	unsigned long irqflags;
	u32 i;
	int ret;

	spin_lock_irqsave(&vc4->job_lock, irqflags);
	kernel_state = vc4->hang_state;
	if (!kernel_state) {
		spin_unlock_irqrestore(&vc4->job_lock, irqflags);
		return -ENOENT;
	}
	state = &kernel_state->user_state;

	/* If the user's array isn't big enough, just return the
	 * required array size.
	 */
	if (get_state->bo_count < state->bo_count) {
		get_state->bo_count = state->bo_count;
		spin_unlock_irqrestore(&vc4->job_lock, irqflags);
		return 0;
	}

	vc4->hang_state = NULL;
	spin_unlock_irqrestore(&vc4->job_lock, irqflags);

	/* Save the user's BO pointer, so we don't stomp it with the memcpy. */
	state->bo = get_state->bo;
	memcpy(get_state, state, sizeof(*state));

	bo_state = kcalloc(state->bo_count, sizeof(*bo_state), GFP_KERNEL);
	if (!bo_state) {
		ret = -ENOMEM;
		goto err_free;
	}

	for (i = 0; i < state->bo_count; i++) {
		struct vc4_bo *vc4_bo = to_vc4_bo(kernel_state->bo[i]);
		u32 handle;

		ret = drm_gem_handle_create(file_priv, kernel_state->bo[i],
					    &handle);

		if (ret) {
			state->bo_count = i - 1;
			goto err;
		}
		bo_state[i].handle = handle;
		bo_state[i].paddr = vc4_bo->base.paddr;
		bo_state[i].size = vc4_bo->base.base.size;
	}

	ret = copy_to_user((void __user *)(uintptr_t)get_state->bo,
			   bo_state,
			   state->bo_count * sizeof(*bo_state));
	kfree(bo_state);

err_free:

	vc4_free_hang_state(dev, kernel_state);

err:
	return ret;
}

static void
vc4_save_hang_state(struct drm_device *dev)
{
	struct vc4_dev *vc4 = to_vc4_dev(dev);
	struct drm_vc4_get_hang_state *state;
	struct vc4_hang_state *kernel_state;
	struct vc4_exec_info *exec;
	struct vc4_bo *bo;
	unsigned long irqflags;
	unsigned int i, unref_list_count;

	kernel_state = kcalloc(1, sizeof(*state), GFP_KERNEL);
	if (!kernel_state)
		return;

	state = &kernel_state->user_state;

	spin_lock_irqsave(&vc4->job_lock, irqflags);
	exec = vc4_first_job(vc4);
	if (!exec) {
		spin_unlock_irqrestore(&vc4->job_lock, irqflags);
		return;
	}

	unref_list_count = 0;
	list_for_each_entry(bo, &exec->unref_list, unref_head)
		unref_list_count++;

	state->bo_count = exec->bo_count + unref_list_count;
	kernel_state->bo = kcalloc(state->bo_count, sizeof(*kernel_state->bo),
				   GFP_ATOMIC);
	if (!kernel_state->bo) {
		spin_unlock_irqrestore(&vc4->job_lock, irqflags);
		return;
	}

	for (i = 0; i < exec->bo_count; i++) {
		drm_gem_object_reference(&exec->bo[i]->base);
		kernel_state->bo[i] = &exec->bo[i]->base;
	}

	list_for_each_entry(bo, &exec->unref_list, unref_head) {
		drm_gem_object_reference(&bo->base.base);
		kernel_state->bo[i] = &bo->base.base;
		i++;
	}

	state->start_bin = exec->ct0ca;
	state->start_render = exec->ct1ca;

	spin_unlock_irqrestore(&vc4->job_lock, irqflags);

	state->ct0ca = V3D_READ(V3D_CTNCA(0));
	state->ct0ea = V3D_READ(V3D_CTNEA(0));

	state->ct1ca = V3D_READ(V3D_CTNCA(1));
	state->ct1ea = V3D_READ(V3D_CTNEA(1));

	state->ct0cs = V3D_READ(V3D_CTNCS(0));
	state->ct1cs = V3D_READ(V3D_CTNCS(1));

	state->ct0ra0 = V3D_READ(V3D_CT00RA0);
	state->ct1ra0 = V3D_READ(V3D_CT01RA0);

	state->bpca = V3D_READ(V3D_BPCA);
	state->bpcs = V3D_READ(V3D_BPCS);
	state->bpoa = V3D_READ(V3D_BPOA);
	state->bpos = V3D_READ(V3D_BPOS);

	state->vpmbase = V3D_READ(V3D_VPMBASE);

	state->dbge = V3D_READ(V3D_DBGE);
	state->fdbgo = V3D_READ(V3D_FDBGO);
	state->fdbgb = V3D_READ(V3D_FDBGB);
	state->fdbgr = V3D_READ(V3D_FDBGR);
	state->fdbgs = V3D_READ(V3D_FDBGS);
	state->errstat = V3D_READ(V3D_ERRSTAT);

	spin_lock_irqsave(&vc4->job_lock, irqflags);
	if (vc4->hang_state) {
		spin_unlock_irqrestore(&vc4->job_lock, irqflags);
		vc4_free_hang_state(dev, kernel_state);
	} else {
		vc4->hang_state = kernel_state;
		spin_unlock_irqrestore(&vc4->job_lock, irqflags);
	}
}

static void
vc4_reset(struct drm_device *dev)
{
@@ -64,6 +244,8 @@ vc4_reset_work(struct work_struct *work)
	struct vc4_dev *vc4 =
		container_of(work, struct vc4_dev, hangcheck.reset_work);

	vc4_save_hang_state(vc4->dev);

	vc4_reset(vc4->dev);
}

@@ -679,4 +861,7 @@ vc4_gem_destroy(struct drm_device *dev)
	}

	vc4_bo_cache_destroy(dev);

	if (vc4->hang_state)
		vc4_free_hang_state(dev, vc4->hang_state);
}
+45 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@
#define DRM_VC4_CREATE_BO                         0x03
#define DRM_VC4_MMAP_BO                           0x04
#define DRM_VC4_CREATE_SHADER_BO                  0x05
#define DRM_VC4_GET_HANG_STATE                    0x06

#define DRM_IOCTL_VC4_SUBMIT_CL           DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_SUBMIT_CL, struct drm_vc4_submit_cl)
#define DRM_IOCTL_VC4_WAIT_SEQNO          DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_WAIT_SEQNO, struct drm_vc4_wait_seqno)
@@ -39,6 +40,7 @@
#define DRM_IOCTL_VC4_CREATE_BO           DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_CREATE_BO, struct drm_vc4_create_bo)
#define DRM_IOCTL_VC4_MMAP_BO             DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_MMAP_BO, struct drm_vc4_mmap_bo)
#define DRM_IOCTL_VC4_CREATE_SHADER_BO    DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_CREATE_SHADER_BO, struct drm_vc4_create_shader_bo)
#define DRM_IOCTL_VC4_GET_HANG_STATE      DRM_IOWR(DRM_COMMAND_BASE + DRM_VC4_GET_HANG_STATE, struct drm_vc4_get_hang_state)

struct drm_vc4_submit_rcl_surface {
	__u32 hindex; /* Handle index, or ~0 if not present. */
@@ -231,4 +233,47 @@ struct drm_vc4_create_shader_bo {
	__u32 pad;
};

struct drm_vc4_get_hang_state_bo {
	__u32 handle;
	__u32 paddr;
	__u32 size;
	__u32 pad;
};

/**
 * struct drm_vc4_hang_state - ioctl argument for collecting state
 * from a GPU hang for analysis.
*/
struct drm_vc4_get_hang_state {
	/** Pointer to array of struct drm_vc4_get_hang_state_bo. */
	__u64 bo;
	/**
	 * On input, the size of the bo array.  Output is the number
	 * of bos to be returned.
	 */
	__u32 bo_count;

	__u32 start_bin, start_render;

	__u32 ct0ca, ct0ea;
	__u32 ct1ca, ct1ea;
	__u32 ct0cs, ct1cs;
	__u32 ct0ra0, ct1ra0;

	__u32 bpca, bpcs;
	__u32 bpoa, bpos;

	__u32 vpmbase;

	__u32 dbge;
	__u32 fdbgo;
	__u32 fdbgb;
	__u32 fdbgr;
	__u32 fdbgs;
	__u32 errstat;

	/* Pad that we may save more registers into in the future. */
	__u32 pad[16];
};

#endif /* _UAPI_VC4_DRM_H_ */