162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/* Copyright (c) 2020 NVIDIA Corporation */
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/dma-fence-array.h>
562306a36Sopenharmony_ci#include <linux/dma-mapping.h>
662306a36Sopenharmony_ci#include <linux/file.h>
762306a36Sopenharmony_ci#include <linux/host1x.h>
862306a36Sopenharmony_ci#include <linux/iommu.h>
962306a36Sopenharmony_ci#include <linux/kref.h>
1062306a36Sopenharmony_ci#include <linux/list.h>
1162306a36Sopenharmony_ci#include <linux/nospec.h>
1262306a36Sopenharmony_ci#include <linux/pm_runtime.h>
1362306a36Sopenharmony_ci#include <linux/scatterlist.h>
1462306a36Sopenharmony_ci#include <linux/slab.h>
1562306a36Sopenharmony_ci#include <linux/sync_file.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <drm/drm_drv.h>
1862306a36Sopenharmony_ci#include <drm/drm_file.h>
1962306a36Sopenharmony_ci#include <drm/drm_syncobj.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include "drm.h"
2262306a36Sopenharmony_ci#include "gem.h"
2362306a36Sopenharmony_ci#include "submit.h"
2462306a36Sopenharmony_ci#include "uapi.h"
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define SUBMIT_ERR(context, fmt, ...) \
2762306a36Sopenharmony_ci	dev_err_ratelimited(context->client->base.dev, \
2862306a36Sopenharmony_ci		"%s: job submission failed: " fmt "\n", \
2962306a36Sopenharmony_ci		current->comm, ##__VA_ARGS__)
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistruct gather_bo {
3262306a36Sopenharmony_ci	struct host1x_bo base;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	struct kref ref;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	struct device *dev;
3762306a36Sopenharmony_ci	u32 *gather_data;
3862306a36Sopenharmony_ci	dma_addr_t gather_data_dma;
3962306a36Sopenharmony_ci	size_t gather_data_words;
4062306a36Sopenharmony_ci};
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic struct host1x_bo *gather_bo_get(struct host1x_bo *host_bo)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	struct gather_bo *bo = container_of(host_bo, struct gather_bo, base);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	kref_get(&bo->ref);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	return host_bo;
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic void gather_bo_release(struct kref *ref)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	struct gather_bo *bo = container_of(ref, struct gather_bo, ref);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	dma_free_attrs(bo->dev, bo->gather_data_words * 4, bo->gather_data, bo->gather_data_dma,
5662306a36Sopenharmony_ci		       0);
5762306a36Sopenharmony_ci	kfree(bo);
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic void gather_bo_put(struct host1x_bo *host_bo)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct gather_bo *bo = container_of(host_bo, struct gather_bo, base);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	kref_put(&bo->ref, gather_bo_release);
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic struct host1x_bo_mapping *
6862306a36Sopenharmony_cigather_bo_pin(struct device *dev, struct host1x_bo *bo, enum dma_data_direction direction)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	struct gather_bo *gather = container_of(bo, struct gather_bo, base);
7162306a36Sopenharmony_ci	struct host1x_bo_mapping *map;
7262306a36Sopenharmony_ci	int err;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	map = kzalloc(sizeof(*map), GFP_KERNEL);
7562306a36Sopenharmony_ci	if (!map)
7662306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	kref_init(&map->ref);
7962306a36Sopenharmony_ci	map->bo = host1x_bo_get(bo);
8062306a36Sopenharmony_ci	map->direction = direction;
8162306a36Sopenharmony_ci	map->dev = dev;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	map->sgt = kzalloc(sizeof(*map->sgt), GFP_KERNEL);
8462306a36Sopenharmony_ci	if (!map->sgt) {
8562306a36Sopenharmony_ci		err = -ENOMEM;
8662306a36Sopenharmony_ci		goto free;
8762306a36Sopenharmony_ci	}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	err = dma_get_sgtable(gather->dev, map->sgt, gather->gather_data, gather->gather_data_dma,
9062306a36Sopenharmony_ci			      gather->gather_data_words * 4);
9162306a36Sopenharmony_ci	if (err)
9262306a36Sopenharmony_ci		goto free_sgt;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	err = dma_map_sgtable(dev, map->sgt, direction, 0);
9562306a36Sopenharmony_ci	if (err)
9662306a36Sopenharmony_ci		goto free_sgt;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	map->phys = sg_dma_address(map->sgt->sgl);
9962306a36Sopenharmony_ci	map->size = gather->gather_data_words * 4;
10062306a36Sopenharmony_ci	map->chunks = err;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	return map;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_cifree_sgt:
10562306a36Sopenharmony_ci	sg_free_table(map->sgt);
10662306a36Sopenharmony_ci	kfree(map->sgt);
10762306a36Sopenharmony_cifree:
10862306a36Sopenharmony_ci	kfree(map);
10962306a36Sopenharmony_ci	return ERR_PTR(err);
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic void gather_bo_unpin(struct host1x_bo_mapping *map)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	if (!map)
11562306a36Sopenharmony_ci		return;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	dma_unmap_sgtable(map->dev, map->sgt, map->direction, 0);
11862306a36Sopenharmony_ci	sg_free_table(map->sgt);
11962306a36Sopenharmony_ci	kfree(map->sgt);
12062306a36Sopenharmony_ci	host1x_bo_put(map->bo);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	kfree(map);
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic void *gather_bo_mmap(struct host1x_bo *host_bo)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	struct gather_bo *bo = container_of(host_bo, struct gather_bo, base);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	return bo->gather_data;
13062306a36Sopenharmony_ci}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cistatic void gather_bo_munmap(struct host1x_bo *host_bo, void *addr)
13362306a36Sopenharmony_ci{
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistatic const struct host1x_bo_ops gather_bo_ops = {
13762306a36Sopenharmony_ci	.get = gather_bo_get,
13862306a36Sopenharmony_ci	.put = gather_bo_put,
13962306a36Sopenharmony_ci	.pin = gather_bo_pin,
14062306a36Sopenharmony_ci	.unpin = gather_bo_unpin,
14162306a36Sopenharmony_ci	.mmap = gather_bo_mmap,
14262306a36Sopenharmony_ci	.munmap = gather_bo_munmap,
14362306a36Sopenharmony_ci};
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic struct tegra_drm_mapping *
14662306a36Sopenharmony_citegra_drm_mapping_get(struct tegra_drm_context *context, u32 id)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	struct tegra_drm_mapping *mapping;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	xa_lock(&context->mappings);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	mapping = xa_load(&context->mappings, id);
15362306a36Sopenharmony_ci	if (mapping)
15462306a36Sopenharmony_ci		kref_get(&mapping->ref);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	xa_unlock(&context->mappings);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	return mapping;
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic void *alloc_copy_user_array(void __user *from, size_t count, size_t size)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	size_t copy_len;
16462306a36Sopenharmony_ci	void *data;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	if (check_mul_overflow(count, size, &copy_len))
16762306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	if (copy_len > 0x4000)
17062306a36Sopenharmony_ci		return ERR_PTR(-E2BIG);
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	data = vmemdup_user(from, copy_len);
17362306a36Sopenharmony_ci	if (IS_ERR(data))
17462306a36Sopenharmony_ci		return ERR_CAST(data);
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	return data;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic int submit_copy_gather_data(struct gather_bo **pbo, struct device *dev,
18062306a36Sopenharmony_ci				   struct tegra_drm_context *context,
18162306a36Sopenharmony_ci				   struct drm_tegra_channel_submit *args)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci	struct gather_bo *bo;
18462306a36Sopenharmony_ci	size_t copy_len;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (args->gather_data_words == 0) {
18762306a36Sopenharmony_ci		SUBMIT_ERR(context, "gather_data_words cannot be zero");
18862306a36Sopenharmony_ci		return -EINVAL;
18962306a36Sopenharmony_ci	}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	if (check_mul_overflow((size_t)args->gather_data_words, (size_t)4, &copy_len)) {
19262306a36Sopenharmony_ci		SUBMIT_ERR(context, "gather_data_words is too large");
19362306a36Sopenharmony_ci		return -EINVAL;
19462306a36Sopenharmony_ci	}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	bo = kzalloc(sizeof(*bo), GFP_KERNEL);
19762306a36Sopenharmony_ci	if (!bo) {
19862306a36Sopenharmony_ci		SUBMIT_ERR(context, "failed to allocate memory for bo info");
19962306a36Sopenharmony_ci		return -ENOMEM;
20062306a36Sopenharmony_ci	}
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	host1x_bo_init(&bo->base, &gather_bo_ops);
20362306a36Sopenharmony_ci	kref_init(&bo->ref);
20462306a36Sopenharmony_ci	bo->dev = dev;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	bo->gather_data = dma_alloc_attrs(dev, copy_len, &bo->gather_data_dma,
20762306a36Sopenharmony_ci					  GFP_KERNEL | __GFP_NOWARN, 0);
20862306a36Sopenharmony_ci	if (!bo->gather_data) {
20962306a36Sopenharmony_ci		SUBMIT_ERR(context, "failed to allocate memory for gather data");
21062306a36Sopenharmony_ci		kfree(bo);
21162306a36Sopenharmony_ci		return -ENOMEM;
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	if (copy_from_user(bo->gather_data, u64_to_user_ptr(args->gather_data_ptr), copy_len)) {
21562306a36Sopenharmony_ci		SUBMIT_ERR(context, "failed to copy gather data from userspace");
21662306a36Sopenharmony_ci		dma_free_attrs(dev, copy_len, bo->gather_data, bo->gather_data_dma, 0);
21762306a36Sopenharmony_ci		kfree(bo);
21862306a36Sopenharmony_ci		return -EFAULT;
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	bo->gather_data_words = args->gather_data_words;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	*pbo = bo;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	return 0;
22662306a36Sopenharmony_ci}
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_cistatic int submit_write_reloc(struct tegra_drm_context *context, struct gather_bo *bo,
22962306a36Sopenharmony_ci			      struct drm_tegra_submit_buf *buf, struct tegra_drm_mapping *mapping)
23062306a36Sopenharmony_ci{
23162306a36Sopenharmony_ci	/* TODO check that target_offset is within bounds */
23262306a36Sopenharmony_ci	dma_addr_t iova = mapping->iova + buf->reloc.target_offset;
23362306a36Sopenharmony_ci	u32 written_ptr;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
23662306a36Sopenharmony_ci	if (buf->flags & DRM_TEGRA_SUBMIT_RELOC_SECTOR_LAYOUT)
23762306a36Sopenharmony_ci		iova |= BIT_ULL(39);
23862306a36Sopenharmony_ci#endif
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	written_ptr = iova >> buf->reloc.shift;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	if (buf->reloc.gather_offset_words >= bo->gather_data_words) {
24362306a36Sopenharmony_ci		SUBMIT_ERR(context,
24462306a36Sopenharmony_ci			   "relocation has too large gather offset (%u vs gather length %zu)",
24562306a36Sopenharmony_ci			   buf->reloc.gather_offset_words, bo->gather_data_words);
24662306a36Sopenharmony_ci		return -EINVAL;
24762306a36Sopenharmony_ci	}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	buf->reloc.gather_offset_words = array_index_nospec(buf->reloc.gather_offset_words,
25062306a36Sopenharmony_ci							    bo->gather_data_words);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	bo->gather_data[buf->reloc.gather_offset_words] = written_ptr;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	return 0;
25562306a36Sopenharmony_ci}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_cistatic int submit_process_bufs(struct tegra_drm_context *context, struct gather_bo *bo,
25862306a36Sopenharmony_ci			       struct drm_tegra_channel_submit *args,
25962306a36Sopenharmony_ci			       struct tegra_drm_submit_data *job_data)
26062306a36Sopenharmony_ci{
26162306a36Sopenharmony_ci	struct tegra_drm_used_mapping *mappings;
26262306a36Sopenharmony_ci	struct drm_tegra_submit_buf *bufs;
26362306a36Sopenharmony_ci	int err;
26462306a36Sopenharmony_ci	u32 i;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	bufs = alloc_copy_user_array(u64_to_user_ptr(args->bufs_ptr), args->num_bufs,
26762306a36Sopenharmony_ci				     sizeof(*bufs));
26862306a36Sopenharmony_ci	if (IS_ERR(bufs)) {
26962306a36Sopenharmony_ci		SUBMIT_ERR(context, "failed to copy bufs array from userspace");
27062306a36Sopenharmony_ci		return PTR_ERR(bufs);
27162306a36Sopenharmony_ci	}
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	mappings = kcalloc(args->num_bufs, sizeof(*mappings), GFP_KERNEL);
27462306a36Sopenharmony_ci	if (!mappings) {
27562306a36Sopenharmony_ci		SUBMIT_ERR(context, "failed to allocate memory for mapping info");
27662306a36Sopenharmony_ci		err = -ENOMEM;
27762306a36Sopenharmony_ci		goto done;
27862306a36Sopenharmony_ci	}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	for (i = 0; i < args->num_bufs; i++) {
28162306a36Sopenharmony_ci		struct drm_tegra_submit_buf *buf = &bufs[i];
28262306a36Sopenharmony_ci		struct tegra_drm_mapping *mapping;
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci		if (buf->flags & ~DRM_TEGRA_SUBMIT_RELOC_SECTOR_LAYOUT) {
28562306a36Sopenharmony_ci			SUBMIT_ERR(context, "invalid flag specified for buffer");
28662306a36Sopenharmony_ci			err = -EINVAL;
28762306a36Sopenharmony_ci			goto drop_refs;
28862306a36Sopenharmony_ci		}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci		mapping = tegra_drm_mapping_get(context, buf->mapping);
29162306a36Sopenharmony_ci		if (!mapping) {
29262306a36Sopenharmony_ci			SUBMIT_ERR(context, "invalid mapping ID '%u' for buffer", buf->mapping);
29362306a36Sopenharmony_ci			err = -EINVAL;
29462306a36Sopenharmony_ci			goto drop_refs;
29562306a36Sopenharmony_ci		}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci		err = submit_write_reloc(context, bo, buf, mapping);
29862306a36Sopenharmony_ci		if (err) {
29962306a36Sopenharmony_ci			tegra_drm_mapping_put(mapping);
30062306a36Sopenharmony_ci			goto drop_refs;
30162306a36Sopenharmony_ci		}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci		mappings[i].mapping = mapping;
30462306a36Sopenharmony_ci		mappings[i].flags = buf->flags;
30562306a36Sopenharmony_ci	}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	job_data->used_mappings = mappings;
30862306a36Sopenharmony_ci	job_data->num_used_mappings = i;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	err = 0;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	goto done;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_cidrop_refs:
31562306a36Sopenharmony_ci	while (i--)
31662306a36Sopenharmony_ci		tegra_drm_mapping_put(mappings[i].mapping);
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	kfree(mappings);
31962306a36Sopenharmony_ci	job_data->used_mappings = NULL;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_cidone:
32262306a36Sopenharmony_ci	kvfree(bufs);
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	return err;
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_cistatic int submit_get_syncpt(struct tegra_drm_context *context, struct host1x_job *job,
32862306a36Sopenharmony_ci			     struct xarray *syncpoints, struct drm_tegra_channel_submit *args)
32962306a36Sopenharmony_ci{
33062306a36Sopenharmony_ci	struct host1x_syncpt *sp;
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	if (args->syncpt.flags) {
33362306a36Sopenharmony_ci		SUBMIT_ERR(context, "invalid flag specified for syncpt");
33462306a36Sopenharmony_ci		return -EINVAL;
33562306a36Sopenharmony_ci	}
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci	/* Syncpt ref will be dropped on job release */
33862306a36Sopenharmony_ci	sp = xa_load(syncpoints, args->syncpt.id);
33962306a36Sopenharmony_ci	if (!sp) {
34062306a36Sopenharmony_ci		SUBMIT_ERR(context, "syncpoint specified in syncpt was not allocated");
34162306a36Sopenharmony_ci		return -EINVAL;
34262306a36Sopenharmony_ci	}
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	job->syncpt = host1x_syncpt_get(sp);
34562306a36Sopenharmony_ci	job->syncpt_incrs = args->syncpt.increments;
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci	return 0;
34862306a36Sopenharmony_ci}
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_cistatic int submit_job_add_gather(struct host1x_job *job, struct tegra_drm_context *context,
35162306a36Sopenharmony_ci				 struct drm_tegra_submit_cmd_gather_uptr *cmd,
35262306a36Sopenharmony_ci				 struct gather_bo *bo, u32 *offset,
35362306a36Sopenharmony_ci				 struct tegra_drm_submit_data *job_data,
35462306a36Sopenharmony_ci				 u32 *class)
35562306a36Sopenharmony_ci{
35662306a36Sopenharmony_ci	u32 next_offset;
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	if (cmd->reserved[0] || cmd->reserved[1] || cmd->reserved[2]) {
35962306a36Sopenharmony_ci		SUBMIT_ERR(context, "non-zero reserved field in GATHER_UPTR command");
36062306a36Sopenharmony_ci		return -EINVAL;
36162306a36Sopenharmony_ci	}
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	/* Check for maximum gather size */
36462306a36Sopenharmony_ci	if (cmd->words > 16383) {
36562306a36Sopenharmony_ci		SUBMIT_ERR(context, "too many words in GATHER_UPTR command");
36662306a36Sopenharmony_ci		return -EINVAL;
36762306a36Sopenharmony_ci	}
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	if (check_add_overflow(*offset, cmd->words, &next_offset)) {
37062306a36Sopenharmony_ci		SUBMIT_ERR(context, "too many total words in job");
37162306a36Sopenharmony_ci		return -EINVAL;
37262306a36Sopenharmony_ci	}
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	if (next_offset > bo->gather_data_words) {
37562306a36Sopenharmony_ci		SUBMIT_ERR(context, "GATHER_UPTR command overflows gather data");
37662306a36Sopenharmony_ci		return -EINVAL;
37762306a36Sopenharmony_ci	}
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	if (tegra_drm_fw_validate(context->client, bo->gather_data, *offset,
38062306a36Sopenharmony_ci				  cmd->words, job_data, class)) {
38162306a36Sopenharmony_ci		SUBMIT_ERR(context, "job was rejected by firewall");
38262306a36Sopenharmony_ci		return -EINVAL;
38362306a36Sopenharmony_ci	}
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	host1x_job_add_gather(job, &bo->base, cmd->words, *offset * 4);
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	*offset = next_offset;
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	return 0;
39062306a36Sopenharmony_ci}
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_cistatic struct host1x_job *
39362306a36Sopenharmony_cisubmit_create_job(struct tegra_drm_context *context, struct gather_bo *bo,
39462306a36Sopenharmony_ci		  struct drm_tegra_channel_submit *args, struct tegra_drm_submit_data *job_data,
39562306a36Sopenharmony_ci		  struct xarray *syncpoints)
39662306a36Sopenharmony_ci{
39762306a36Sopenharmony_ci	struct drm_tegra_submit_cmd *cmds;
39862306a36Sopenharmony_ci	u32 i, gather_offset = 0, class;
39962306a36Sopenharmony_ci	struct host1x_job *job;
40062306a36Sopenharmony_ci	int err;
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	/* Set initial class for firewall. */
40362306a36Sopenharmony_ci	class = context->client->base.class;
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	cmds = alloc_copy_user_array(u64_to_user_ptr(args->cmds_ptr), args->num_cmds,
40662306a36Sopenharmony_ci				     sizeof(*cmds));
40762306a36Sopenharmony_ci	if (IS_ERR(cmds)) {
40862306a36Sopenharmony_ci		SUBMIT_ERR(context, "failed to copy cmds array from userspace");
40962306a36Sopenharmony_ci		return ERR_CAST(cmds);
41062306a36Sopenharmony_ci	}
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	job = host1x_job_alloc(context->channel, args->num_cmds, 0, true);
41362306a36Sopenharmony_ci	if (!job) {
41462306a36Sopenharmony_ci		SUBMIT_ERR(context, "failed to allocate memory for job");
41562306a36Sopenharmony_ci		job = ERR_PTR(-ENOMEM);
41662306a36Sopenharmony_ci		goto done;
41762306a36Sopenharmony_ci	}
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	err = submit_get_syncpt(context, job, syncpoints, args);
42062306a36Sopenharmony_ci	if (err < 0)
42162306a36Sopenharmony_ci		goto free_job;
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	job->client = &context->client->base;
42462306a36Sopenharmony_ci	job->class = context->client->base.class;
42562306a36Sopenharmony_ci	job->serialize = true;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	for (i = 0; i < args->num_cmds; i++) {
42862306a36Sopenharmony_ci		struct drm_tegra_submit_cmd *cmd = &cmds[i];
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci		if (cmd->flags) {
43162306a36Sopenharmony_ci			SUBMIT_ERR(context, "unknown flags given for cmd");
43262306a36Sopenharmony_ci			err = -EINVAL;
43362306a36Sopenharmony_ci			goto free_job;
43462306a36Sopenharmony_ci		}
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci		if (cmd->type == DRM_TEGRA_SUBMIT_CMD_GATHER_UPTR) {
43762306a36Sopenharmony_ci			err = submit_job_add_gather(job, context, &cmd->gather_uptr, bo,
43862306a36Sopenharmony_ci						    &gather_offset, job_data, &class);
43962306a36Sopenharmony_ci			if (err)
44062306a36Sopenharmony_ci				goto free_job;
44162306a36Sopenharmony_ci		} else if (cmd->type == DRM_TEGRA_SUBMIT_CMD_WAIT_SYNCPT) {
44262306a36Sopenharmony_ci			if (cmd->wait_syncpt.reserved[0] || cmd->wait_syncpt.reserved[1]) {
44362306a36Sopenharmony_ci				SUBMIT_ERR(context, "non-zero reserved value");
44462306a36Sopenharmony_ci				err = -EINVAL;
44562306a36Sopenharmony_ci				goto free_job;
44662306a36Sopenharmony_ci			}
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci			host1x_job_add_wait(job, cmd->wait_syncpt.id, cmd->wait_syncpt.value,
44962306a36Sopenharmony_ci					    false, class);
45062306a36Sopenharmony_ci		} else if (cmd->type == DRM_TEGRA_SUBMIT_CMD_WAIT_SYNCPT_RELATIVE) {
45162306a36Sopenharmony_ci			if (cmd->wait_syncpt.reserved[0] || cmd->wait_syncpt.reserved[1]) {
45262306a36Sopenharmony_ci				SUBMIT_ERR(context, "non-zero reserved value");
45362306a36Sopenharmony_ci				err = -EINVAL;
45462306a36Sopenharmony_ci				goto free_job;
45562306a36Sopenharmony_ci			}
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci			if (cmd->wait_syncpt.id != args->syncpt.id) {
45862306a36Sopenharmony_ci				SUBMIT_ERR(context, "syncpoint ID in CMD_WAIT_SYNCPT_RELATIVE is not used by the job");
45962306a36Sopenharmony_ci				err = -EINVAL;
46062306a36Sopenharmony_ci				goto free_job;
46162306a36Sopenharmony_ci			}
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci			host1x_job_add_wait(job, cmd->wait_syncpt.id, cmd->wait_syncpt.value,
46462306a36Sopenharmony_ci					    true, class);
46562306a36Sopenharmony_ci		} else {
46662306a36Sopenharmony_ci			SUBMIT_ERR(context, "unknown cmd type");
46762306a36Sopenharmony_ci			err = -EINVAL;
46862306a36Sopenharmony_ci			goto free_job;
46962306a36Sopenharmony_ci		}
47062306a36Sopenharmony_ci	}
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	if (gather_offset == 0) {
47362306a36Sopenharmony_ci		SUBMIT_ERR(context, "job must have at least one gather");
47462306a36Sopenharmony_ci		err = -EINVAL;
47562306a36Sopenharmony_ci		goto free_job;
47662306a36Sopenharmony_ci	}
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	goto done;
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_cifree_job:
48162306a36Sopenharmony_ci	host1x_job_put(job);
48262306a36Sopenharmony_ci	job = ERR_PTR(err);
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_cidone:
48562306a36Sopenharmony_ci	kvfree(cmds);
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci	return job;
48862306a36Sopenharmony_ci}
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_cistatic void release_job(struct host1x_job *job)
49162306a36Sopenharmony_ci{
49262306a36Sopenharmony_ci	struct tegra_drm_client *client = container_of(job->client, struct tegra_drm_client, base);
49362306a36Sopenharmony_ci	struct tegra_drm_submit_data *job_data = job->user_data;
49462306a36Sopenharmony_ci	u32 i;
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	if (job->memory_context)
49762306a36Sopenharmony_ci		host1x_memory_context_put(job->memory_context);
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	for (i = 0; i < job_data->num_used_mappings; i++)
50062306a36Sopenharmony_ci		tegra_drm_mapping_put(job_data->used_mappings[i].mapping);
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	kfree(job_data->used_mappings);
50362306a36Sopenharmony_ci	kfree(job_data);
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	pm_runtime_mark_last_busy(client->base.dev);
50662306a36Sopenharmony_ci	pm_runtime_put_autosuspend(client->base.dev);
50762306a36Sopenharmony_ci}
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ciint tegra_drm_ioctl_channel_submit(struct drm_device *drm, void *data,
51062306a36Sopenharmony_ci				   struct drm_file *file)
51162306a36Sopenharmony_ci{
51262306a36Sopenharmony_ci	struct tegra_drm_file *fpriv = file->driver_priv;
51362306a36Sopenharmony_ci	struct drm_tegra_channel_submit *args = data;
51462306a36Sopenharmony_ci	struct tegra_drm_submit_data *job_data;
51562306a36Sopenharmony_ci	struct drm_syncobj *syncobj = NULL;
51662306a36Sopenharmony_ci	struct tegra_drm_context *context;
51762306a36Sopenharmony_ci	struct host1x_job *job;
51862306a36Sopenharmony_ci	struct gather_bo *bo;
51962306a36Sopenharmony_ci	u32 i;
52062306a36Sopenharmony_ci	int err;
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci	mutex_lock(&fpriv->lock);
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	context = xa_load(&fpriv->contexts, args->context);
52562306a36Sopenharmony_ci	if (!context) {
52662306a36Sopenharmony_ci		mutex_unlock(&fpriv->lock);
52762306a36Sopenharmony_ci		pr_err_ratelimited("%s: %s: invalid channel context '%#x'", __func__,
52862306a36Sopenharmony_ci				   current->comm, args->context);
52962306a36Sopenharmony_ci		return -EINVAL;
53062306a36Sopenharmony_ci	}
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	if (args->syncobj_in) {
53362306a36Sopenharmony_ci		struct dma_fence *fence;
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci		err = drm_syncobj_find_fence(file, args->syncobj_in, 0, 0, &fence);
53662306a36Sopenharmony_ci		if (err) {
53762306a36Sopenharmony_ci			SUBMIT_ERR(context, "invalid syncobj_in '%#x'", args->syncobj_in);
53862306a36Sopenharmony_ci			goto unlock;
53962306a36Sopenharmony_ci		}
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci		err = dma_fence_wait_timeout(fence, true, msecs_to_jiffies(10000));
54262306a36Sopenharmony_ci		dma_fence_put(fence);
54362306a36Sopenharmony_ci		if (err) {
54462306a36Sopenharmony_ci			SUBMIT_ERR(context, "wait for syncobj_in timed out");
54562306a36Sopenharmony_ci			goto unlock;
54662306a36Sopenharmony_ci		}
54762306a36Sopenharmony_ci	}
54862306a36Sopenharmony_ci
54962306a36Sopenharmony_ci	if (args->syncobj_out) {
55062306a36Sopenharmony_ci		syncobj = drm_syncobj_find(file, args->syncobj_out);
55162306a36Sopenharmony_ci		if (!syncobj) {
55262306a36Sopenharmony_ci			SUBMIT_ERR(context, "invalid syncobj_out '%#x'", args->syncobj_out);
55362306a36Sopenharmony_ci			err = -ENOENT;
55462306a36Sopenharmony_ci			goto unlock;
55562306a36Sopenharmony_ci		}
55662306a36Sopenharmony_ci	}
55762306a36Sopenharmony_ci
55862306a36Sopenharmony_ci	/* Allocate gather BO and copy gather words in. */
55962306a36Sopenharmony_ci	err = submit_copy_gather_data(&bo, drm->dev, context, args);
56062306a36Sopenharmony_ci	if (err)
56162306a36Sopenharmony_ci		goto unlock;
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci	job_data = kzalloc(sizeof(*job_data), GFP_KERNEL);
56462306a36Sopenharmony_ci	if (!job_data) {
56562306a36Sopenharmony_ci		SUBMIT_ERR(context, "failed to allocate memory for job data");
56662306a36Sopenharmony_ci		err = -ENOMEM;
56762306a36Sopenharmony_ci		goto put_bo;
56862306a36Sopenharmony_ci	}
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	/* Get data buffer mappings and do relocation patching. */
57162306a36Sopenharmony_ci	err = submit_process_bufs(context, bo, args, job_data);
57262306a36Sopenharmony_ci	if (err)
57362306a36Sopenharmony_ci		goto free_job_data;
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ci	/* Allocate host1x_job and add gathers and waits to it. */
57662306a36Sopenharmony_ci	job = submit_create_job(context, bo, args, job_data, &fpriv->syncpoints);
57762306a36Sopenharmony_ci	if (IS_ERR(job)) {
57862306a36Sopenharmony_ci		err = PTR_ERR(job);
57962306a36Sopenharmony_ci		goto free_job_data;
58062306a36Sopenharmony_ci	}
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_ci	/* Map gather data for Host1x. */
58362306a36Sopenharmony_ci	err = host1x_job_pin(job, context->client->base.dev);
58462306a36Sopenharmony_ci	if (err) {
58562306a36Sopenharmony_ci		SUBMIT_ERR(context, "failed to pin job: %d", err);
58662306a36Sopenharmony_ci		goto put_job;
58762306a36Sopenharmony_ci	}
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci	if (context->client->ops->get_streamid_offset) {
59062306a36Sopenharmony_ci		err = context->client->ops->get_streamid_offset(
59162306a36Sopenharmony_ci			context->client, &job->engine_streamid_offset);
59262306a36Sopenharmony_ci		if (err) {
59362306a36Sopenharmony_ci			SUBMIT_ERR(context, "failed to get streamid offset: %d", err);
59462306a36Sopenharmony_ci			goto unpin_job;
59562306a36Sopenharmony_ci		}
59662306a36Sopenharmony_ci	}
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci	if (context->memory_context && context->client->ops->can_use_memory_ctx) {
59962306a36Sopenharmony_ci		bool supported;
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci		err = context->client->ops->can_use_memory_ctx(context->client, &supported);
60262306a36Sopenharmony_ci		if (err) {
60362306a36Sopenharmony_ci			SUBMIT_ERR(context, "failed to detect if engine can use memory context: %d", err);
60462306a36Sopenharmony_ci			goto unpin_job;
60562306a36Sopenharmony_ci		}
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ci		if (supported) {
60862306a36Sopenharmony_ci			job->memory_context = context->memory_context;
60962306a36Sopenharmony_ci			host1x_memory_context_get(job->memory_context);
61062306a36Sopenharmony_ci		}
61162306a36Sopenharmony_ci	} else if (context->client->ops->get_streamid_offset) {
61262306a36Sopenharmony_ci		/*
61362306a36Sopenharmony_ci		 * Job submission will need to temporarily change stream ID,
61462306a36Sopenharmony_ci		 * so need to tell it what to change it back to.
61562306a36Sopenharmony_ci		 */
61662306a36Sopenharmony_ci		if (!tegra_dev_iommu_get_stream_id(context->client->base.dev,
61762306a36Sopenharmony_ci						   &job->engine_fallback_streamid))
61862306a36Sopenharmony_ci			job->engine_fallback_streamid = TEGRA_STREAM_ID_BYPASS;
61962306a36Sopenharmony_ci	}
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_ci	/* Boot engine. */
62262306a36Sopenharmony_ci	err = pm_runtime_resume_and_get(context->client->base.dev);
62362306a36Sopenharmony_ci	if (err < 0) {
62462306a36Sopenharmony_ci		SUBMIT_ERR(context, "could not power up engine: %d", err);
62562306a36Sopenharmony_ci		goto put_memory_context;
62662306a36Sopenharmony_ci	}
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	job->user_data = job_data;
62962306a36Sopenharmony_ci	job->release = release_job;
63062306a36Sopenharmony_ci	job->timeout = 10000;
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_ci	/*
63362306a36Sopenharmony_ci	 * job_data is now part of job reference counting, so don't release
63462306a36Sopenharmony_ci	 * it from here.
63562306a36Sopenharmony_ci	 */
63662306a36Sopenharmony_ci	job_data = NULL;
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_ci	/* Submit job to hardware. */
63962306a36Sopenharmony_ci	err = host1x_job_submit(job);
64062306a36Sopenharmony_ci	if (err) {
64162306a36Sopenharmony_ci		SUBMIT_ERR(context, "host1x job submission failed: %d", err);
64262306a36Sopenharmony_ci		goto unpin_job;
64362306a36Sopenharmony_ci	}
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci	/* Return postfences to userspace and add fences to DMA reservations. */
64662306a36Sopenharmony_ci	args->syncpt.value = job->syncpt_end;
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	if (syncobj) {
64962306a36Sopenharmony_ci		struct dma_fence *fence = host1x_fence_create(job->syncpt, job->syncpt_end, true);
65062306a36Sopenharmony_ci		if (IS_ERR(fence)) {
65162306a36Sopenharmony_ci			err = PTR_ERR(fence);
65262306a36Sopenharmony_ci			SUBMIT_ERR(context, "failed to create postfence: %d", err);
65362306a36Sopenharmony_ci		}
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci		drm_syncobj_replace_fence(syncobj, fence);
65662306a36Sopenharmony_ci	}
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_ci	goto put_job;
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ciput_memory_context:
66162306a36Sopenharmony_ci	if (job->memory_context)
66262306a36Sopenharmony_ci		host1x_memory_context_put(job->memory_context);
66362306a36Sopenharmony_ciunpin_job:
66462306a36Sopenharmony_ci	host1x_job_unpin(job);
66562306a36Sopenharmony_ciput_job:
66662306a36Sopenharmony_ci	host1x_job_put(job);
66762306a36Sopenharmony_cifree_job_data:
66862306a36Sopenharmony_ci	if (job_data && job_data->used_mappings) {
66962306a36Sopenharmony_ci		for (i = 0; i < job_data->num_used_mappings; i++)
67062306a36Sopenharmony_ci			tegra_drm_mapping_put(job_data->used_mappings[i].mapping);
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci		kfree(job_data->used_mappings);
67362306a36Sopenharmony_ci	}
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_ci	kfree(job_data);
67662306a36Sopenharmony_ciput_bo:
67762306a36Sopenharmony_ci	gather_bo_put(&bo->base);
67862306a36Sopenharmony_ciunlock:
67962306a36Sopenharmony_ci	if (syncobj)
68062306a36Sopenharmony_ci		drm_syncobj_put(syncobj);
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_ci	mutex_unlock(&fpriv->lock);
68362306a36Sopenharmony_ci	return err;
68462306a36Sopenharmony_ci}
685