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, ©_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, ©_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