case DRM_V3D_PARAM_SUPPORTS_MULTISYNC_EXT:
                args->value = 1;
                return 0;
+       case DRM_V3D_PARAM_SUPPORTS_CPU_QUEUE:
+               args->value = 1;
+               return 0;
        default:
                DRM_DEBUG("Unknown parameter %d\n", args->param);
                return -EINVAL;
        DRM_IOCTL_DEF_DRV(V3D_PERFMON_CREATE, v3d_perfmon_create_ioctl, DRM_RENDER_ALLOW),
        DRM_IOCTL_DEF_DRV(V3D_PERFMON_DESTROY, v3d_perfmon_destroy_ioctl, DRM_RENDER_ALLOW),
        DRM_IOCTL_DEF_DRV(V3D_PERFMON_GET_VALUES, v3d_perfmon_get_values_ioctl, DRM_RENDER_ALLOW),
+       DRM_IOCTL_DEF_DRV(V3D_SUBMIT_CPU, v3d_submit_cpu_ioctl, DRM_RENDER_ALLOW | DRM_AUTH),
 };
 
 static const struct drm_driver v3d_drm_driver = {
 
 
 #define GMP_GRANULARITY (128 * 1024)
 
-#define V3D_MAX_QUEUES (V3D_CACHE_CLEAN + 1)
+#define V3D_MAX_QUEUES (V3D_CPU + 1)
 
 static inline char *v3d_queue_to_string(enum v3d_queue queue)
 {
        case V3D_TFU: return "tfu";
        case V3D_CSD: return "csd";
        case V3D_CACHE_CLEAN: return "cache_clean";
+       case V3D_CPU: return "cpu";
        }
        return "UNKNOWN";
 }
        struct v3d_render_job *render_job;
        struct v3d_tfu_job *tfu_job;
        struct v3d_csd_job *csd_job;
+       struct v3d_cpu_job *cpu_job;
 
        struct v3d_queue_state queue[V3D_MAX_QUEUES];
 
        struct drm_v3d_submit_csd args;
 };
 
+enum v3d_cpu_job_type {};
+
+struct v3d_cpu_job {
+       struct v3d_job base;
+
+       enum v3d_cpu_job_type job_type;
+};
+
+typedef void (*v3d_cpu_job_fn)(struct v3d_cpu_job *);
+
 struct v3d_submit_outsync {
        struct drm_syncobj *syncobj;
 };
                         struct drm_file *file_priv);
 int v3d_submit_csd_ioctl(struct drm_device *dev, void *data,
                         struct drm_file *file_priv);
+int v3d_submit_cpu_ioctl(struct drm_device *dev, void *data,
+                        struct drm_file *file_priv);
 
 /* v3d_irq.c */
 int v3d_irq_init(struct v3d_dev *v3d);
 
        return container_of(sched_job, struct v3d_csd_job, base.base);
 }
 
+static struct v3d_cpu_job *
+to_cpu_job(struct drm_sched_job *sched_job)
+{
+       return container_of(sched_job, struct v3d_cpu_job, base.base);
+}
+
 static void
 v3d_sched_job_free(struct drm_sched_job *sched_job)
 {
        return fence;
 }
 
+static const v3d_cpu_job_fn cpu_job_function[] = { };
+
+static struct dma_fence *
+v3d_cpu_job_run(struct drm_sched_job *sched_job)
+{
+       struct v3d_cpu_job *job = to_cpu_job(sched_job);
+       struct v3d_dev *v3d = job->base.v3d;
+       struct v3d_file_priv *file = job->base.file->driver_priv;
+       u64 runtime;
+
+       v3d->cpu_job = job;
+
+       if (job->job_type >= ARRAY_SIZE(cpu_job_function)) {
+               DRM_DEBUG_DRIVER("Unknown CPU job: %d\n", job->job_type);
+               return NULL;
+       }
+
+       file->start_ns[V3D_CPU] = local_clock();
+       v3d->queue[V3D_CPU].start_ns = file->start_ns[V3D_CPU];
+
+       cpu_job_function[job->job_type](job);
+
+       runtime = local_clock() - file->start_ns[V3D_CPU];
+
+       file->enabled_ns[V3D_CPU] += runtime;
+       v3d->queue[V3D_CPU].enabled_ns += runtime;
+
+       file->jobs_sent[V3D_CPU]++;
+       v3d->queue[V3D_CPU].jobs_sent++;
+
+       file->start_ns[V3D_CPU] = 0;
+       v3d->queue[V3D_CPU].start_ns = 0;
+
+       return NULL;
+}
+
 static struct dma_fence *
 v3d_cache_clean_job_run(struct drm_sched_job *sched_job)
 {
        .free_job = v3d_sched_job_free
 };
 
+static const struct drm_sched_backend_ops v3d_cpu_sched_ops = {
+       .run_job = v3d_cpu_job_run,
+       .timedout_job = v3d_generic_job_timedout,
+       .free_job = v3d_sched_job_free
+};
+
 int
 v3d_sched_init(struct v3d_dev *v3d)
 {
                        goto fail;
        }
 
+       ret = drm_sched_init(&v3d->queue[V3D_CPU].sched,
+                            &v3d_cpu_sched_ops, NULL,
+                            DRM_SCHED_PRIORITY_COUNT,
+                            1, job_hang_limit,
+                            msecs_to_jiffies(hang_limit_ms), NULL,
+                            NULL, "v3d_cpu", v3d->drm.dev);
+       if (ret)
+               goto fail;
+
        return 0;
 
 fail:
 
 
        return ret;
 }
+
+static const unsigned int cpu_job_bo_handle_count[] = { };
+
+/**
+ * v3d_submit_cpu_ioctl() - Submits a CPU job to the V3D.
+ * @dev: DRM device
+ * @data: ioctl argument
+ * @file_priv: DRM file for this fd
+ *
+ * Userspace specifies the CPU job type and data required to perform its
+ * operations through the drm_v3d_extension struct.
+ */
+int
+v3d_submit_cpu_ioctl(struct drm_device *dev, void *data,
+                    struct drm_file *file_priv)
+{
+       struct v3d_dev *v3d = to_v3d_dev(dev);
+       struct drm_v3d_submit_cpu *args = data;
+       struct v3d_submit_ext se = {0};
+       struct v3d_cpu_job *cpu_job = NULL;
+       struct ww_acquire_ctx acquire_ctx;
+       int ret;
+
+       if (args->flags && !(args->flags & DRM_V3D_SUBMIT_EXTENSION)) {
+               DRM_INFO("Invalid flags: %d\n", args->flags);
+               return -EINVAL;
+       }
+
+       ret = v3d_job_allocate((void *)&cpu_job, sizeof(*cpu_job));
+       if (ret)
+               return ret;
+
+       if (args->flags & DRM_V3D_SUBMIT_EXTENSION) {
+               ret = v3d_get_extensions(file_priv, args->extensions, &se);
+               if (ret) {
+                       DRM_DEBUG("Failed to get extensions.\n");
+                       goto fail;
+               }
+       }
+
+       /* Every CPU job must have a CPU job user extension */
+       if (!cpu_job->job_type) {
+               DRM_DEBUG("CPU job must have a CPU job user extension.\n");
+               goto fail;
+       }
+
+       if (args->bo_handle_count != cpu_job_bo_handle_count[cpu_job->job_type]) {
+               DRM_DEBUG("This CPU job was not submitted with the proper number of BOs.\n");
+               goto fail;
+       }
+
+       ret = v3d_job_init(v3d, file_priv, &cpu_job->base,
+                          v3d_job_free, 0, &se, V3D_CPU);
+       if (ret)
+               goto fail;
+
+       if (args->bo_handle_count) {
+               ret = v3d_lookup_bos(dev, file_priv, &cpu_job->base,
+                                    args->bo_handles, args->bo_handle_count);
+               if (ret)
+                       goto fail;
+
+               ret = v3d_lock_bo_reservations(&cpu_job->base, &acquire_ctx);
+               if (ret)
+                       goto fail;
+       }
+
+       mutex_lock(&v3d->sched_lock);
+       v3d_push_job(&cpu_job->base);
+       mutex_unlock(&v3d->sched_lock);
+
+       v3d_attach_fences_and_unlock_reservation(file_priv,
+                                                &cpu_job->base,
+                                                &acquire_ctx, 0,
+                                                NULL, cpu_job->base.done_fence);
+
+       v3d_job_put(&cpu_job->base);
+
+       return 0;
+
+fail:
+       v3d_job_cleanup((void *)cpu_job);
+       v3d_put_multisync_post_deps(&se);
+
+       return ret;
+}
 
 #define DRM_V3D_PERFMON_CREATE                    0x08
 #define DRM_V3D_PERFMON_DESTROY                   0x09
 #define DRM_V3D_PERFMON_GET_VALUES                0x0a
+#define DRM_V3D_SUBMIT_CPU                        0x0b
 
 #define DRM_IOCTL_V3D_SUBMIT_CL           DRM_IOWR(DRM_COMMAND_BASE + DRM_V3D_SUBMIT_CL, struct drm_v3d_submit_cl)
 #define DRM_IOCTL_V3D_WAIT_BO             DRM_IOWR(DRM_COMMAND_BASE + DRM_V3D_WAIT_BO, struct drm_v3d_wait_bo)
                                                   struct drm_v3d_perfmon_destroy)
 #define DRM_IOCTL_V3D_PERFMON_GET_VALUES  DRM_IOWR(DRM_COMMAND_BASE + DRM_V3D_PERFMON_GET_VALUES, \
                                                   struct drm_v3d_perfmon_get_values)
+#define DRM_IOCTL_V3D_SUBMIT_CPU          DRM_IOW(DRM_COMMAND_BASE + DRM_V3D_SUBMIT_CPU, struct drm_v3d_submit_cpu)
 
 #define DRM_V3D_SUBMIT_CL_FLUSH_CACHE             0x01
 #define DRM_V3D_SUBMIT_EXTENSION                 0x02
        V3D_TFU,
        V3D_CSD,
        V3D_CACHE_CLEAN,
+       V3D_CPU,
 };
 
 /**
        DRM_V3D_PARAM_SUPPORTS_CACHE_FLUSH,
        DRM_V3D_PARAM_SUPPORTS_PERFMON,
        DRM_V3D_PARAM_SUPPORTS_MULTISYNC_EXT,
+       DRM_V3D_PARAM_SUPPORTS_CPU_QUEUE,
 };
 
 struct drm_v3d_get_param {
        __u32 pad;
 };
 
+struct drm_v3d_submit_cpu {
+       /* Pointer to a u32 array of the BOs that are referenced by the job. */
+       __u64 bo_handles;
+
+       /* Number of BO handles passed in (size is that times 4). */
+       __u32 bo_handle_count;
+
+       __u32 flags;
+
+       /* Pointer to an array of ioctl extensions*/
+       __u64 extensions;
+};
+
 enum {
        V3D_PERFCNT_FEP_VALID_PRIMTS_NO_PIXELS,
        V3D_PERFCNT_FEP_VALID_PRIMS,