drm/vc4: Add an interface for capturing the GPU state after a hang.
authorEric Anholt <eric@anholt.net>
Fri, 30 Oct 2015 17:09:02 +0000 (10:09 -0700)
committerEric Anholt <eric@anholt.net>
Tue, 8 Dec 2015 04:49:49 +0000 (20:49 -0800)
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: Eric Anholt <eric@anholt.net>
drivers/gpu/drm/vc4/vc4_drv.c
drivers/gpu/drm/vc4/vc4_drv.h
drivers/gpu/drm/vc4/vc4_gem.c
include/uapi/drm/vc4_drm.h

index 2cfee595945590e020945f11724e265135ced2d0..97226b677bf487cab36489e3b33fe4283c902566 100644 (file)
@@ -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 = {
index f9927d87369f8338fd4f9d5587747cc15d0a6153..080865ec2bae67c7ff7b04a97fdb5956096cc156 100644 (file)
@@ -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);
index 5fb0556e001e4b1b83226157902d38569aa9fc4f..39f29e7593342d9555dd51cbc2790a56abe06179 100644 (file)
@@ -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);
 }
index fe4161bc93ae03c2dd537f34ce88154bd49eab62..eeb37e394f1347ef0c87ec2305604a0510d4dd37 100644 (file)
@@ -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_ */