diff options
Diffstat (limited to 'drivers/gpu/drm/imagination/pvr_job.c')
| -rw-r--r-- | drivers/gpu/drm/imagination/pvr_job.c | 786 | 
1 files changed, 786 insertions, 0 deletions
| diff --git a/drivers/gpu/drm/imagination/pvr_job.c b/drivers/gpu/drm/imagination/pvr_job.c new file mode 100644 index 000000000000..78c2f3c6dce0 --- /dev/null +++ b/drivers/gpu/drm/imagination/pvr_job.c @@ -0,0 +1,786 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Copyright (c) 2023 Imagination Technologies Ltd. */ + +#include "pvr_context.h" +#include "pvr_device.h" +#include "pvr_drv.h" +#include "pvr_gem.h" +#include "pvr_hwrt.h" +#include "pvr_job.h" +#include "pvr_mmu.h" +#include "pvr_power.h" +#include "pvr_rogue_fwif.h" +#include "pvr_rogue_fwif_client.h" +#include "pvr_stream.h" +#include "pvr_stream_defs.h" +#include "pvr_sync.h" + +#include <drm/drm_exec.h> +#include <drm/drm_gem.h> +#include <linux/types.h> +#include <uapi/drm/pvr_drm.h> + +static void pvr_job_release(struct kref *kref) +{ +	struct pvr_job *job = container_of(kref, struct pvr_job, ref_count); + +	xa_erase(&job->pvr_dev->job_ids, job->id); + +	pvr_hwrt_data_put(job->hwrt); +	pvr_context_put(job->ctx); + +	WARN_ON(job->paired_job); + +	pvr_queue_job_cleanup(job); +	pvr_job_release_pm_ref(job); + +	kfree(job->cmd); +	kfree(job); +} + +/** + * pvr_job_put() - Release reference on job + * @job: Target job. + */ +void +pvr_job_put(struct pvr_job *job) +{ +	if (job) +		kref_put(&job->ref_count, pvr_job_release); +} + +/** + * pvr_job_process_stream() - Build job FW structure from stream + * @pvr_dev: Device pointer. + * @cmd_defs: Stream definition. + * @stream: Pointer to command stream. + * @stream_size: Size of command stream, in bytes. + * @job: Pointer to job. + * + * Caller is responsible for freeing the output structure. + * + * Returns: + *  * 0 on success, + *  * -%ENOMEM on out of memory, or + *  * -%EINVAL on malformed stream. + */ +static int +pvr_job_process_stream(struct pvr_device *pvr_dev, const struct pvr_stream_cmd_defs *cmd_defs, +		       void *stream, u32 stream_size, struct pvr_job *job) +{ +	int err; + +	job->cmd = kzalloc(cmd_defs->dest_size, GFP_KERNEL); +	if (!job->cmd) +		return -ENOMEM; + +	job->cmd_len = cmd_defs->dest_size; + +	err = pvr_stream_process(pvr_dev, cmd_defs, stream, stream_size, job->cmd); +	if (err) +		kfree(job->cmd); + +	return err; +} + +static int pvr_fw_cmd_init(struct pvr_device *pvr_dev, struct pvr_job *job, +			   const struct pvr_stream_cmd_defs *stream_def, +			   u64 stream_userptr, u32 stream_len) +{ +	void *stream; +	int err; + +	stream = kzalloc(stream_len, GFP_KERNEL); +	if (!stream) +		return -ENOMEM; + +	if (copy_from_user(stream, u64_to_user_ptr(stream_userptr), stream_len)) { +		err = -EFAULT; +		goto err_free_stream; +	} + +	err = pvr_job_process_stream(pvr_dev, stream_def, stream, stream_len, job); + +err_free_stream: +	kfree(stream); + +	return err; +} + +static u32 +convert_geom_flags(u32 in_flags) +{ +	u32 out_flags = 0; + +	if (in_flags & DRM_PVR_SUBMIT_JOB_GEOM_CMD_FIRST) +		out_flags |= ROGUE_GEOM_FLAGS_FIRSTKICK; +	if (in_flags & DRM_PVR_SUBMIT_JOB_GEOM_CMD_LAST) +		out_flags |= ROGUE_GEOM_FLAGS_LASTKICK; +	if (in_flags & DRM_PVR_SUBMIT_JOB_GEOM_CMD_SINGLE_CORE) +		out_flags |= ROGUE_GEOM_FLAGS_SINGLE_CORE; + +	return out_flags; +} + +static u32 +convert_frag_flags(u32 in_flags) +{ +	u32 out_flags = 0; + +	if (in_flags & DRM_PVR_SUBMIT_JOB_FRAG_CMD_SINGLE_CORE) +		out_flags |= ROGUE_FRAG_FLAGS_SINGLE_CORE; +	if (in_flags & DRM_PVR_SUBMIT_JOB_FRAG_CMD_DEPTHBUFFER) +		out_flags |= ROGUE_FRAG_FLAGS_DEPTHBUFFER; +	if (in_flags & DRM_PVR_SUBMIT_JOB_FRAG_CMD_STENCILBUFFER) +		out_flags |= ROGUE_FRAG_FLAGS_STENCILBUFFER; +	if (in_flags & DRM_PVR_SUBMIT_JOB_FRAG_CMD_PREVENT_CDM_OVERLAP) +		out_flags |= ROGUE_FRAG_FLAGS_PREVENT_CDM_OVERLAP; +	if (in_flags & DRM_PVR_SUBMIT_JOB_FRAG_CMD_SCRATCHBUFFER) +		out_flags |= ROGUE_FRAG_FLAGS_SCRATCHBUFFER; +	if (in_flags & DRM_PVR_SUBMIT_JOB_FRAG_CMD_GET_VIS_RESULTS) +		out_flags |= ROGUE_FRAG_FLAGS_GET_VIS_RESULTS; +	if (in_flags & DRM_PVR_SUBMIT_JOB_FRAG_CMD_DISABLE_PIXELMERGE) +		out_flags |= ROGUE_FRAG_FLAGS_DISABLE_PIXELMERGE; + +	return out_flags; +} + +static int +pvr_geom_job_fw_cmd_init(struct pvr_job *job, +			 struct drm_pvr_job *args) +{ +	struct rogue_fwif_cmd_geom *cmd; +	int err; + +	if (args->flags & ~DRM_PVR_SUBMIT_JOB_GEOM_CMD_FLAGS_MASK) +		return -EINVAL; + +	if (job->ctx->type != DRM_PVR_CTX_TYPE_RENDER) +		return -EINVAL; + +	if (!job->hwrt) +		return -EINVAL; + +	job->fw_ccb_cmd_type = ROGUE_FWIF_CCB_CMD_TYPE_GEOM; +	err = pvr_fw_cmd_init(job->pvr_dev, job, &pvr_cmd_geom_stream, +			      args->cmd_stream, args->cmd_stream_len); +	if (err) +		return err; + +	cmd = job->cmd; +	cmd->cmd_shared.cmn.frame_num = 0; +	cmd->flags = convert_geom_flags(args->flags); +	pvr_fw_object_get_fw_addr(job->hwrt->fw_obj, &cmd->cmd_shared.hwrt_data_fw_addr); +	return 0; +} + +static int +pvr_frag_job_fw_cmd_init(struct pvr_job *job, +			 struct drm_pvr_job *args) +{ +	struct rogue_fwif_cmd_frag *cmd; +	int err; + +	if (args->flags & ~DRM_PVR_SUBMIT_JOB_FRAG_CMD_FLAGS_MASK) +		return -EINVAL; + +	if (job->ctx->type != DRM_PVR_CTX_TYPE_RENDER) +		return -EINVAL; + +	if (!job->hwrt) +		return -EINVAL; + +	job->fw_ccb_cmd_type = (args->flags & DRM_PVR_SUBMIT_JOB_FRAG_CMD_PARTIAL_RENDER) ? +			       ROGUE_FWIF_CCB_CMD_TYPE_FRAG_PR : +			       ROGUE_FWIF_CCB_CMD_TYPE_FRAG; +	err = pvr_fw_cmd_init(job->pvr_dev, job, &pvr_cmd_frag_stream, +			      args->cmd_stream, args->cmd_stream_len); +	if (err) +		return err; + +	cmd = job->cmd; +	cmd->cmd_shared.cmn.frame_num = 0; +	cmd->flags = convert_frag_flags(args->flags); +	pvr_fw_object_get_fw_addr(job->hwrt->fw_obj, &cmd->cmd_shared.hwrt_data_fw_addr); +	return 0; +} + +static u32 +convert_compute_flags(u32 in_flags) +{ +	u32 out_flags = 0; + +	if (in_flags & DRM_PVR_SUBMIT_JOB_COMPUTE_CMD_PREVENT_ALL_OVERLAP) +		out_flags |= ROGUE_COMPUTE_FLAG_PREVENT_ALL_OVERLAP; +	if (in_flags & DRM_PVR_SUBMIT_JOB_COMPUTE_CMD_SINGLE_CORE) +		out_flags |= ROGUE_COMPUTE_FLAG_SINGLE_CORE; + +	return out_flags; +} + +static int +pvr_compute_job_fw_cmd_init(struct pvr_job *job, +			    struct drm_pvr_job *args) +{ +	struct rogue_fwif_cmd_compute *cmd; +	int err; + +	if (args->flags & ~DRM_PVR_SUBMIT_JOB_COMPUTE_CMD_FLAGS_MASK) +		return -EINVAL; + +	if (job->ctx->type != DRM_PVR_CTX_TYPE_COMPUTE) +		return -EINVAL; + +	job->fw_ccb_cmd_type = ROGUE_FWIF_CCB_CMD_TYPE_CDM; +	err = pvr_fw_cmd_init(job->pvr_dev, job, &pvr_cmd_compute_stream, +			      args->cmd_stream, args->cmd_stream_len); +	if (err) +		return err; + +	cmd = job->cmd; +	cmd->common.frame_num = 0; +	cmd->flags = convert_compute_flags(args->flags); +	return 0; +} + +static u32 +convert_transfer_flags(u32 in_flags) +{ +	u32 out_flags = 0; + +	if (in_flags & DRM_PVR_SUBMIT_JOB_TRANSFER_CMD_SINGLE_CORE) +		out_flags |= ROGUE_TRANSFER_FLAGS_SINGLE_CORE; + +	return out_flags; +} + +static int +pvr_transfer_job_fw_cmd_init(struct pvr_job *job, +			     struct drm_pvr_job *args) +{ +	struct rogue_fwif_cmd_transfer *cmd; +	int err; + +	if (args->flags & ~DRM_PVR_SUBMIT_JOB_TRANSFER_CMD_FLAGS_MASK) +		return -EINVAL; + +	if (job->ctx->type != DRM_PVR_CTX_TYPE_TRANSFER_FRAG) +		return -EINVAL; + +	job->fw_ccb_cmd_type = ROGUE_FWIF_CCB_CMD_TYPE_TQ_3D; +	err = pvr_fw_cmd_init(job->pvr_dev, job, &pvr_cmd_transfer_stream, +			      args->cmd_stream, args->cmd_stream_len); +	if (err) +		return err; + +	cmd = job->cmd; +	cmd->common.frame_num = 0; +	cmd->flags = convert_transfer_flags(args->flags); +	return 0; +} + +static int +pvr_job_fw_cmd_init(struct pvr_job *job, +		    struct drm_pvr_job *args) +{ +	switch (args->type) { +	case DRM_PVR_JOB_TYPE_GEOMETRY: +		return pvr_geom_job_fw_cmd_init(job, args); + +	case DRM_PVR_JOB_TYPE_FRAGMENT: +		return pvr_frag_job_fw_cmd_init(job, args); + +	case DRM_PVR_JOB_TYPE_COMPUTE: +		return pvr_compute_job_fw_cmd_init(job, args); + +	case DRM_PVR_JOB_TYPE_TRANSFER_FRAG: +		return pvr_transfer_job_fw_cmd_init(job, args); + +	default: +		return -EINVAL; +	} +} + +/** + * struct pvr_job_data - Helper container for pairing jobs with the + * sync_ops supplied for them by the user. + */ +struct pvr_job_data { +	/** @job: Pointer to the job. */ +	struct pvr_job *job; + +	/** @sync_ops: Pointer to the sync_ops associated with @job. */ +	struct drm_pvr_sync_op *sync_ops; + +	/** @sync_op_count: Number of members of @sync_ops. */ +	u32 sync_op_count; +}; + +/** + * prepare_job_syncs() - Prepare all sync objects for a single job. + * @pvr_file: PowerVR file. + * @job_data: Precreated job and sync_ops array. + * @signal_array: xarray to receive signal sync objects. + * + * Returns: + *  * 0 on success, or + *  * Any error code returned by pvr_sync_signal_array_collect_ops(), + *    pvr_sync_add_deps_to_job(), drm_sched_job_add_resv_dependencies() or + *    pvr_sync_signal_array_update_fences(). + */ +static int +prepare_job_syncs(struct pvr_file *pvr_file, +		  struct pvr_job_data *job_data, +		  struct xarray *signal_array) +{ +	struct dma_fence *done_fence; +	int err = pvr_sync_signal_array_collect_ops(signal_array, +						    from_pvr_file(pvr_file), +						    job_data->sync_op_count, +						    job_data->sync_ops); + +	if (err) +		return err; + +	err = pvr_sync_add_deps_to_job(pvr_file, &job_data->job->base, +				       job_data->sync_op_count, +				       job_data->sync_ops, signal_array); +	if (err) +		return err; + +	if (job_data->job->hwrt) { +		/* The geometry job writes the HWRT region headers, which are +		 * then read by the fragment job. +		 */ +		struct drm_gem_object *obj = +			gem_from_pvr_gem(job_data->job->hwrt->fw_obj->gem); +		enum dma_resv_usage usage = +			dma_resv_usage_rw(job_data->job->type == +					  DRM_PVR_JOB_TYPE_GEOMETRY); + +		dma_resv_lock(obj->resv, NULL); +		err = drm_sched_job_add_resv_dependencies(&job_data->job->base, +							  obj->resv, usage); +		dma_resv_unlock(obj->resv); +		if (err) +			return err; +	} + +	/* We need to arm the job to get the job done fence. */ +	done_fence = pvr_queue_job_arm(job_data->job); + +	err = pvr_sync_signal_array_update_fences(signal_array, +						  job_data->sync_op_count, +						  job_data->sync_ops, +						  done_fence); +	return err; +} + +/** + * prepare_job_syncs_for_each() - Prepare all sync objects for an array of jobs. + * @pvr_file: PowerVR file. + * @job_data: Array of precreated jobs and their sync_ops. + * @job_count: Number of jobs. + * @signal_array: xarray to receive signal sync objects. + * + * Returns: + *  * 0 on success, or + *  * Any error code returned by pvr_vm_bind_job_prepare_syncs(). + */ +static int +prepare_job_syncs_for_each(struct pvr_file *pvr_file, +			   struct pvr_job_data *job_data, +			   u32 *job_count, +			   struct xarray *signal_array) +{ +	for (u32 i = 0; i < *job_count; i++) { +		int err = prepare_job_syncs(pvr_file, &job_data[i], +					    signal_array); + +		if (err) { +			*job_count = i; +			return err; +		} +	} + +	return 0; +} + +static struct pvr_job * +create_job(struct pvr_device *pvr_dev, +	   struct pvr_file *pvr_file, +	   struct drm_pvr_job *args) +{ +	struct pvr_job *job = NULL; +	int err; + +	if (!args->cmd_stream || !args->cmd_stream_len) +		return ERR_PTR(-EINVAL); + +	if (args->type != DRM_PVR_JOB_TYPE_GEOMETRY && +	    args->type != DRM_PVR_JOB_TYPE_FRAGMENT && +	    (args->hwrt.set_handle || args->hwrt.data_index)) +		return ERR_PTR(-EINVAL); + +	job = kzalloc(sizeof(*job), GFP_KERNEL); +	if (!job) +		return ERR_PTR(-ENOMEM); + +	kref_init(&job->ref_count); +	job->type = args->type; +	job->pvr_dev = pvr_dev; + +	err = xa_alloc(&pvr_dev->job_ids, &job->id, job, xa_limit_32b, GFP_KERNEL); +	if (err) +		goto err_put_job; + +	job->ctx = pvr_context_lookup(pvr_file, args->context_handle); +	if (!job->ctx) { +		err = -EINVAL; +		goto err_put_job; +	} + +	if (args->hwrt.set_handle) { +		job->hwrt = pvr_hwrt_data_lookup(pvr_file, args->hwrt.set_handle, +						 args->hwrt.data_index); +		if (!job->hwrt) { +			err = -EINVAL; +			goto err_put_job; +		} +	} + +	err = pvr_job_fw_cmd_init(job, args); +	if (err) +		goto err_put_job; + +	err = pvr_queue_job_init(job); +	if (err) +		goto err_put_job; + +	return job; + +err_put_job: +	pvr_job_put(job); +	return ERR_PTR(err); +} + +/** + * pvr_job_data_fini() - Cleanup all allocs used to set up job submission. + * @job_data: Job data array. + * @job_count: Number of members of @job_data. + */ +static void +pvr_job_data_fini(struct pvr_job_data *job_data, u32 job_count) +{ +	for (u32 i = 0; i < job_count; i++) { +		pvr_job_put(job_data[i].job); +		kvfree(job_data[i].sync_ops); +	} +} + +/** + * pvr_job_data_init() - Init an array of created jobs, associating them with + * the appropriate sync_ops args, which will be copied in. + * @pvr_dev: Target PowerVR device. + * @pvr_file: Pointer to PowerVR file structure. + * @job_args: Job args array copied from user. + * @job_count: Number of members of @job_args. + * @job_data_out: Job data array. + */ +static int pvr_job_data_init(struct pvr_device *pvr_dev, +			     struct pvr_file *pvr_file, +			     struct drm_pvr_job *job_args, +			     u32 *job_count, +			     struct pvr_job_data *job_data_out) +{ +	int err = 0, i = 0; + +	for (; i < *job_count; i++) { +		job_data_out[i].job = +			create_job(pvr_dev, pvr_file, &job_args[i]); +		err = PTR_ERR_OR_ZERO(job_data_out[i].job); + +		if (err) { +			*job_count = i; +			job_data_out[i].job = NULL; +			goto err_cleanup; +		} + +		err = PVR_UOBJ_GET_ARRAY(job_data_out[i].sync_ops, +					 &job_args[i].sync_ops); +		if (err) { +			*job_count = i; + +			/* Ensure the job created above is also cleaned up. */ +			i++; +			goto err_cleanup; +		} + +		job_data_out[i].sync_op_count = job_args[i].sync_ops.count; +	} + +	return 0; + +err_cleanup: +	pvr_job_data_fini(job_data_out, i); + +	return err; +} + +static void +push_jobs(struct pvr_job_data *job_data, u32 job_count) +{ +	for (u32 i = 0; i < job_count; i++) +		pvr_queue_job_push(job_data[i].job); +} + +static int +prepare_fw_obj_resv(struct drm_exec *exec, struct pvr_fw_object *fw_obj) +{ +	return drm_exec_prepare_obj(exec, gem_from_pvr_gem(fw_obj->gem), 1); +} + +static int +jobs_lock_all_objs(struct drm_exec *exec, struct pvr_job_data *job_data, +		   u32 job_count) +{ +	for (u32 i = 0; i < job_count; i++) { +		struct pvr_job *job = job_data[i].job; + +		/* Grab a lock on a the context, to guard against +		 * concurrent submission to the same queue. +		 */ +		int err = drm_exec_lock_obj(exec, +					    gem_from_pvr_gem(job->ctx->fw_obj->gem)); + +		if (err) +			return err; + +		if (job->hwrt) { +			err = prepare_fw_obj_resv(exec, +						  job->hwrt->fw_obj); +			if (err) +				return err; +		} +	} + +	return 0; +} + +static int +prepare_job_resvs_for_each(struct drm_exec *exec, struct pvr_job_data *job_data, +			   u32 job_count) +{ +	drm_exec_until_all_locked(exec) { +		int err = jobs_lock_all_objs(exec, job_data, job_count); + +		drm_exec_retry_on_contention(exec); +		if (err) +			return err; +	} + +	return 0; +} + +static void +update_job_resvs(struct pvr_job *job) +{ +	if (job->hwrt) { +		enum dma_resv_usage usage = job->type == DRM_PVR_JOB_TYPE_GEOMETRY ? +					    DMA_RESV_USAGE_WRITE : DMA_RESV_USAGE_READ; +		struct drm_gem_object *obj = gem_from_pvr_gem(job->hwrt->fw_obj->gem); + +		dma_resv_add_fence(obj->resv, &job->base.s_fence->finished, usage); +	} +} + +static void +update_job_resvs_for_each(struct pvr_job_data *job_data, u32 job_count) +{ +	for (u32 i = 0; i < job_count; i++) +		update_job_resvs(job_data[i].job); +} + +static bool can_combine_jobs(struct pvr_job *a, struct pvr_job *b) +{ +	struct pvr_job *geom_job = a, *frag_job = b; +	struct dma_fence *fence; +	unsigned long index; + +	/* Geometry and fragment jobs can be combined if they are queued to the +	 * same context and targeting the same HWRT. +	 */ +	if (a->type != DRM_PVR_JOB_TYPE_GEOMETRY || +	    b->type != DRM_PVR_JOB_TYPE_FRAGMENT || +	    a->ctx != b->ctx || +	    a->hwrt != b->hwrt) +		return false; + +	xa_for_each(&frag_job->base.dependencies, index, fence) { +		/* We combine when we see an explicit geom -> frag dep. */ +		if (&geom_job->base.s_fence->scheduled == fence) +			return true; +	} + +	return false; +} + +static struct dma_fence * +get_last_queued_job_scheduled_fence(struct pvr_queue *queue, +				    struct pvr_job_data *job_data, +				    u32 cur_job_pos) +{ +	/* We iterate over the current job array in reverse order to grab the +	 * last to-be-queued job targeting the same queue. +	 */ +	for (u32 i = cur_job_pos; i > 0; i--) { +		struct pvr_job *job = job_data[i - 1].job; + +		if (job->ctx == queue->ctx && job->type == queue->type) +			return dma_fence_get(&job->base.s_fence->scheduled); +	} + +	/* If we didn't find any, we just return the last queued job scheduled +	 * fence attached to the queue. +	 */ +	return dma_fence_get(queue->last_queued_job_scheduled_fence); +} + +static int +pvr_jobs_link_geom_frag(struct pvr_job_data *job_data, u32 *job_count) +{ +	for (u32 i = 0; i < *job_count - 1; i++) { +		struct pvr_job *geom_job = job_data[i].job; +		struct pvr_job *frag_job = job_data[i + 1].job; +		struct pvr_queue *frag_queue; +		struct dma_fence *f; + +		if (!can_combine_jobs(job_data[i].job, job_data[i + 1].job)) +			continue; + +		/* The fragment job will be submitted by the geometry queue. We +		 * need to make sure it comes after all the other fragment jobs +		 * queued before it. +		 */ +		frag_queue = pvr_context_get_queue_for_job(frag_job->ctx, +							   frag_job->type); +		f = get_last_queued_job_scheduled_fence(frag_queue, job_data, +							i); +		if (f) { +			int err = drm_sched_job_add_dependency(&geom_job->base, +							       f); +			if (err) { +				*job_count = i; +				return err; +			} +		} + +		/* The KCCB slot will be reserved by the geometry job, so we can +		 * drop the KCCB fence on the fragment job. +		 */ +		pvr_kccb_fence_put(frag_job->kccb_fence); +		frag_job->kccb_fence = NULL; + +		geom_job->paired_job = frag_job; +		frag_job->paired_job = geom_job; + +		/* Skip the fragment job we just paired to the geometry job. */ +		i++; +	} + +	return 0; +} + +/** + * pvr_submit_jobs() - Submit jobs to the GPU + * @pvr_dev: Target PowerVR device. + * @pvr_file: Pointer to PowerVR file structure. + * @args: Ioctl args. + * + * This initial implementation is entirely synchronous; on return the GPU will + * be idle. This will not be the case for future implementations. + * + * Returns: + *  * 0 on success, + *  * -%EFAULT if arguments can not be copied from user space, or + *  * -%EINVAL on invalid arguments, or + *  * Any other error. + */ +int +pvr_submit_jobs(struct pvr_device *pvr_dev, struct pvr_file *pvr_file, +		struct drm_pvr_ioctl_submit_jobs_args *args) +{ +	struct pvr_job_data *job_data = NULL; +	struct drm_pvr_job *job_args; +	struct xarray signal_array; +	u32 jobs_alloced = 0; +	struct drm_exec exec; +	int err; + +	if (!args->jobs.count) +		return -EINVAL; + +	err = PVR_UOBJ_GET_ARRAY(job_args, &args->jobs); +	if (err) +		return err; + +	job_data = kvmalloc_array(args->jobs.count, sizeof(*job_data), +				  GFP_KERNEL | __GFP_ZERO); +	if (!job_data) { +		err = -ENOMEM; +		goto out_free; +	} + +	err = pvr_job_data_init(pvr_dev, pvr_file, job_args, &args->jobs.count, +				job_data); +	if (err) +		goto out_free; + +	jobs_alloced = args->jobs.count; + +	/* +	 * Flush MMU if needed - this has been deferred until now to avoid +	 * overuse of this expensive operation. +	 */ +	err = pvr_mmu_flush_exec(pvr_dev, false); +	if (err) +		goto out_job_data_cleanup; + +	drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT | DRM_EXEC_IGNORE_DUPLICATES, 0); + +	xa_init_flags(&signal_array, XA_FLAGS_ALLOC); + +	err = prepare_job_syncs_for_each(pvr_file, job_data, &args->jobs.count, +					 &signal_array); +	if (err) +		goto out_exec_fini; + +	err = prepare_job_resvs_for_each(&exec, job_data, args->jobs.count); +	if (err) +		goto out_exec_fini; + +	err = pvr_jobs_link_geom_frag(job_data, &args->jobs.count); +	if (err) +		goto out_exec_fini; + +	/* Anything after that point must succeed because we start exposing job +	 * finished fences to the outside world. +	 */ +	update_job_resvs_for_each(job_data, args->jobs.count); +	push_jobs(job_data, args->jobs.count); +	pvr_sync_signal_array_push_fences(&signal_array); +	err = 0; + +out_exec_fini: +	drm_exec_fini(&exec); +	pvr_sync_signal_array_cleanup(&signal_array); + +out_job_data_cleanup: +	pvr_job_data_fini(job_data, jobs_alloced); + +out_free: +	kvfree(job_data); +	kvfree(job_args); + +	return err; +} | 
