xref: /linux/drivers/gpu/host1x/job.c (revision e902585fc8b639f1a1258eaa6265e98994e34ef8)
19952f691SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
26579324aSTerje Bergstrom /*
36579324aSTerje Bergstrom  * Tegra host1x Job
46579324aSTerje Bergstrom  *
5f08ef2d1SArto Merilainen  * Copyright (c) 2010-2015, NVIDIA Corporation.
66579324aSTerje Bergstrom  */
76579324aSTerje Bergstrom 
86579324aSTerje Bergstrom #include <linux/dma-mapping.h>
96579324aSTerje Bergstrom #include <linux/err.h>
1035d747a8SThierry Reding #include <linux/host1x.h>
11273da5a0SThierry Reding #include <linux/iommu.h>
126579324aSTerje Bergstrom #include <linux/kref.h>
136579324aSTerje Bergstrom #include <linux/module.h>
146579324aSTerje Bergstrom #include <linux/scatterlist.h>
156579324aSTerje Bergstrom #include <linux/slab.h>
166579324aSTerje Bergstrom #include <linux/vmalloc.h>
176579324aSTerje Bergstrom #include <trace/events/host1x.h>
186579324aSTerje Bergstrom 
196579324aSTerje Bergstrom #include "channel.h"
206579324aSTerje Bergstrom #include "dev.h"
216579324aSTerje Bergstrom #include "job.h"
226579324aSTerje Bergstrom #include "syncpt.h"
236579324aSTerje Bergstrom 
24a47ac10eSDmitry Osipenko #define HOST1X_WAIT_SYNCPT_OFFSET 0x8
25a47ac10eSDmitry Osipenko 
266579324aSTerje Bergstrom struct host1x_job *host1x_job_alloc(struct host1x_channel *ch,
2724c94e16SThierry Reding 				    u32 num_cmdbufs, u32 num_relocs)
286579324aSTerje Bergstrom {
296579324aSTerje Bergstrom 	struct host1x_job *job = NULL;
3026c8de5eSDmitry Osipenko 	unsigned int num_unpins = num_relocs;
316579324aSTerje Bergstrom 	u64 total;
326579324aSTerje Bergstrom 	void *mem;
336579324aSTerje Bergstrom 
3426c8de5eSDmitry Osipenko 	if (!IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL))
3526c8de5eSDmitry Osipenko 		num_unpins += num_cmdbufs;
3626c8de5eSDmitry Osipenko 
376579324aSTerje Bergstrom 	/* Check that we're not going to overflow */
386579324aSTerje Bergstrom 	total = sizeof(struct host1x_job) +
39f5fda676SDan Carpenter 		(u64)num_relocs * sizeof(struct host1x_reloc) +
40f5fda676SDan Carpenter 		(u64)num_unpins * sizeof(struct host1x_job_unpin_data) +
41*e902585fSMikko Perttunen 		(u64)num_cmdbufs * sizeof(struct host1x_job_cmd) +
42f5fda676SDan Carpenter 		(u64)num_unpins * sizeof(dma_addr_t) +
43f5fda676SDan Carpenter 		(u64)num_unpins * sizeof(u32 *);
446579324aSTerje Bergstrom 	if (total > ULONG_MAX)
456579324aSTerje Bergstrom 		return NULL;
466579324aSTerje Bergstrom 
476579324aSTerje Bergstrom 	mem = job = kzalloc(total, GFP_KERNEL);
486579324aSTerje Bergstrom 	if (!job)
496579324aSTerje Bergstrom 		return NULL;
506579324aSTerje Bergstrom 
516579324aSTerje Bergstrom 	kref_init(&job->ref);
526579324aSTerje Bergstrom 	job->channel = ch;
536579324aSTerje Bergstrom 
546579324aSTerje Bergstrom 	/* Redistribute memory to the structs  */
556579324aSTerje Bergstrom 	mem += sizeof(struct host1x_job);
5606490bb9SThierry Reding 	job->relocs = num_relocs ? mem : NULL;
576579324aSTerje Bergstrom 	mem += num_relocs * sizeof(struct host1x_reloc);
586579324aSTerje Bergstrom 	job->unpins = num_unpins ? mem : NULL;
596579324aSTerje Bergstrom 	mem += num_unpins * sizeof(struct host1x_job_unpin_data);
60*e902585fSMikko Perttunen 	job->cmds = num_cmdbufs ? mem : NULL;
61*e902585fSMikko Perttunen 	mem += num_cmdbufs * sizeof(struct host1x_job_cmd);
626579324aSTerje Bergstrom 	job->addr_phys = num_unpins ? mem : NULL;
636579324aSTerje Bergstrom 
646579324aSTerje Bergstrom 	job->reloc_addr_phys = job->addr_phys;
656579324aSTerje Bergstrom 	job->gather_addr_phys = &job->addr_phys[num_relocs];
666579324aSTerje Bergstrom 
676579324aSTerje Bergstrom 	return job;
686579324aSTerje Bergstrom }
69fae798a1SThierry Reding EXPORT_SYMBOL(host1x_job_alloc);
706579324aSTerje Bergstrom 
716579324aSTerje Bergstrom struct host1x_job *host1x_job_get(struct host1x_job *job)
726579324aSTerje Bergstrom {
736579324aSTerje Bergstrom 	kref_get(&job->ref);
746579324aSTerje Bergstrom 	return job;
756579324aSTerje Bergstrom }
76fae798a1SThierry Reding EXPORT_SYMBOL(host1x_job_get);
776579324aSTerje Bergstrom 
786579324aSTerje Bergstrom static void job_free(struct kref *ref)
796579324aSTerje Bergstrom {
806579324aSTerje Bergstrom 	struct host1x_job *job = container_of(ref, struct host1x_job, ref);
816579324aSTerje Bergstrom 
8217a298e9SMikko Perttunen 	if (job->release)
8317a298e9SMikko Perttunen 		job->release(job);
8417a298e9SMikko Perttunen 
85c78f837aSMikko Perttunen 	if (job->waiter)
86c78f837aSMikko Perttunen 		host1x_intr_put_ref(job->syncpt->host, job->syncpt->id,
87c78f837aSMikko Perttunen 				    job->waiter, false);
88c78f837aSMikko Perttunen 
892aed4f5aSMikko Perttunen 	if (job->syncpt)
902aed4f5aSMikko Perttunen 		host1x_syncpt_put(job->syncpt);
912aed4f5aSMikko Perttunen 
926579324aSTerje Bergstrom 	kfree(job);
936579324aSTerje Bergstrom }
946579324aSTerje Bergstrom 
956579324aSTerje Bergstrom void host1x_job_put(struct host1x_job *job)
966579324aSTerje Bergstrom {
976579324aSTerje Bergstrom 	kref_put(&job->ref, job_free);
986579324aSTerje Bergstrom }
99fae798a1SThierry Reding EXPORT_SYMBOL(host1x_job_put);
1006579324aSTerje Bergstrom 
1016579324aSTerje Bergstrom void host1x_job_add_gather(struct host1x_job *job, struct host1x_bo *bo,
102326bbd79SThierry Reding 			   unsigned int words, unsigned int offset)
1036579324aSTerje Bergstrom {
104*e902585fSMikko Perttunen 	struct host1x_job_gather *gather = &job->cmds[job->num_cmds].gather;
1056579324aSTerje Bergstrom 
106326bbd79SThierry Reding 	gather->words = words;
107326bbd79SThierry Reding 	gather->bo = bo;
108326bbd79SThierry Reding 	gather->offset = offset;
109326bbd79SThierry Reding 
110*e902585fSMikko Perttunen 	job->num_cmds++;
1116579324aSTerje Bergstrom }
112fae798a1SThierry Reding EXPORT_SYMBOL(host1x_job_add_gather);
1136579324aSTerje Bergstrom 
114*e902585fSMikko Perttunen void host1x_job_add_wait(struct host1x_job *job, u32 id, u32 thresh,
115*e902585fSMikko Perttunen 			 bool relative, u32 next_class)
116*e902585fSMikko Perttunen {
117*e902585fSMikko Perttunen 	struct host1x_job_cmd *cmd = &job->cmds[job->num_cmds];
118*e902585fSMikko Perttunen 
119*e902585fSMikko Perttunen 	cmd->is_wait = true;
120*e902585fSMikko Perttunen 	cmd->wait.id = id;
121*e902585fSMikko Perttunen 	cmd->wait.threshold = thresh;
122*e902585fSMikko Perttunen 	cmd->wait.next_class = next_class;
123*e902585fSMikko Perttunen 	cmd->wait.relative = relative;
124*e902585fSMikko Perttunen 
125*e902585fSMikko Perttunen 	job->num_cmds++;
126*e902585fSMikko Perttunen }
127*e902585fSMikko Perttunen EXPORT_SYMBOL(host1x_job_add_wait);
128*e902585fSMikko Perttunen 
129404bfb78SMikko Perttunen static unsigned int pin_job(struct host1x *host, struct host1x_job *job)
1306579324aSTerje Bergstrom {
131af1cbfb9SThierry Reding 	struct host1x_client *client = job->client;
132af1cbfb9SThierry Reding 	struct device *dev = client->dev;
133fd323e9eSDmitry Osipenko 	struct host1x_job_gather *g;
134273da5a0SThierry Reding 	struct iommu_domain *domain;
135*e902585fSMikko Perttunen 	struct sg_table *sgt;
1366579324aSTerje Bergstrom 	unsigned int i;
137404bfb78SMikko Perttunen 	int err;
1386579324aSTerje Bergstrom 
139273da5a0SThierry Reding 	domain = iommu_get_domain_for_dev(dev);
1406579324aSTerje Bergstrom 	job->num_unpins = 0;
1416579324aSTerje Bergstrom 
1426579324aSTerje Bergstrom 	for (i = 0; i < job->num_relocs; i++) {
14306490bb9SThierry Reding 		struct host1x_reloc *reloc = &job->relocs[i];
144af1cbfb9SThierry Reding 		dma_addr_t phys_addr, *phys;
1456579324aSTerje Bergstrom 
146961e3beaSThierry Reding 		reloc->target.bo = host1x_bo_get(reloc->target.bo);
147404bfb78SMikko Perttunen 		if (!reloc->target.bo) {
148404bfb78SMikko Perttunen 			err = -EINVAL;
1496579324aSTerje Bergstrom 			goto unpin;
150404bfb78SMikko Perttunen 		}
1516579324aSTerje Bergstrom 
152273da5a0SThierry Reding 		/*
153273da5a0SThierry Reding 		 * If the client device is not attached to an IOMMU, the
154273da5a0SThierry Reding 		 * physical address of the buffer object can be used.
155273da5a0SThierry Reding 		 *
156273da5a0SThierry Reding 		 * Similarly, when an IOMMU domain is shared between all
157273da5a0SThierry Reding 		 * host1x clients, the IOVA is already available, so no
158273da5a0SThierry Reding 		 * need to map the buffer object again.
159273da5a0SThierry Reding 		 *
160273da5a0SThierry Reding 		 * XXX Note that this isn't always safe to do because it
161273da5a0SThierry Reding 		 * relies on an assumption that no cache maintenance is
162273da5a0SThierry Reding 		 * needed on the buffer objects.
163273da5a0SThierry Reding 		 */
164273da5a0SThierry Reding 		if (!domain || client->group)
165af1cbfb9SThierry Reding 			phys = &phys_addr;
166af1cbfb9SThierry Reding 		else
167af1cbfb9SThierry Reding 			phys = NULL;
168af1cbfb9SThierry Reding 
169af1cbfb9SThierry Reding 		sgt = host1x_bo_pin(dev, reloc->target.bo, phys);
17080327ce3SThierry Reding 		if (IS_ERR(sgt)) {
17180327ce3SThierry Reding 			err = PTR_ERR(sgt);
17280327ce3SThierry Reding 			goto unpin;
17380327ce3SThierry Reding 		}
1746579324aSTerje Bergstrom 
175af1cbfb9SThierry Reding 		if (sgt) {
176af1cbfb9SThierry Reding 			unsigned long mask = HOST1X_RELOC_READ |
177af1cbfb9SThierry Reding 					     HOST1X_RELOC_WRITE;
178af1cbfb9SThierry Reding 			enum dma_data_direction dir;
179af1cbfb9SThierry Reding 
180af1cbfb9SThierry Reding 			switch (reloc->flags & mask) {
181af1cbfb9SThierry Reding 			case HOST1X_RELOC_READ:
182af1cbfb9SThierry Reding 				dir = DMA_TO_DEVICE;
183af1cbfb9SThierry Reding 				break;
184af1cbfb9SThierry Reding 
185af1cbfb9SThierry Reding 			case HOST1X_RELOC_WRITE:
186af1cbfb9SThierry Reding 				dir = DMA_FROM_DEVICE;
187af1cbfb9SThierry Reding 				break;
188af1cbfb9SThierry Reding 
189af1cbfb9SThierry Reding 			case HOST1X_RELOC_READ | HOST1X_RELOC_WRITE:
190af1cbfb9SThierry Reding 				dir = DMA_BIDIRECTIONAL;
191af1cbfb9SThierry Reding 				break;
192af1cbfb9SThierry Reding 
193af1cbfb9SThierry Reding 			default:
194af1cbfb9SThierry Reding 				err = -EINVAL;
195af1cbfb9SThierry Reding 				goto unpin;
196af1cbfb9SThierry Reding 			}
197af1cbfb9SThierry Reding 
19867ed9f9dSMarek Szyprowski 			err = dma_map_sgtable(dev, sgt, dir, 0);
19967ed9f9dSMarek Szyprowski 			if (err)
200af1cbfb9SThierry Reding 				goto unpin;
201af1cbfb9SThierry Reding 
202af1cbfb9SThierry Reding 			job->unpins[job->num_unpins].dev = dev;
203af1cbfb9SThierry Reding 			job->unpins[job->num_unpins].dir = dir;
204af1cbfb9SThierry Reding 			phys_addr = sg_dma_address(sgt->sgl);
205af1cbfb9SThierry Reding 		}
206af1cbfb9SThierry Reding 
2076579324aSTerje Bergstrom 		job->addr_phys[job->num_unpins] = phys_addr;
208961e3beaSThierry Reding 		job->unpins[job->num_unpins].bo = reloc->target.bo;
2096579324aSTerje Bergstrom 		job->unpins[job->num_unpins].sgt = sgt;
2106579324aSTerje Bergstrom 		job->num_unpins++;
2116579324aSTerje Bergstrom 	}
2126579324aSTerje Bergstrom 
21326c8de5eSDmitry Osipenko 	/*
21426c8de5eSDmitry Osipenko 	 * We will copy gathers BO content later, so there is no need to
21526c8de5eSDmitry Osipenko 	 * hold and pin them.
21626c8de5eSDmitry Osipenko 	 */
21726c8de5eSDmitry Osipenko 	if (IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL))
21826c8de5eSDmitry Osipenko 		return 0;
21926c8de5eSDmitry Osipenko 
220*e902585fSMikko Perttunen 	for (i = 0; i < job->num_cmds; i++) {
221404bfb78SMikko Perttunen 		size_t gather_size = 0;
222404bfb78SMikko Perttunen 		struct scatterlist *sg;
2236579324aSTerje Bergstrom 		dma_addr_t phys_addr;
224404bfb78SMikko Perttunen 		unsigned long shift;
225404bfb78SMikko Perttunen 		struct iova *alloc;
226273da5a0SThierry Reding 		dma_addr_t *phys;
227404bfb78SMikko Perttunen 		unsigned int j;
2286579324aSTerje Bergstrom 
229*e902585fSMikko Perttunen 		if (job->cmds[i].is_wait)
230*e902585fSMikko Perttunen 			continue;
231*e902585fSMikko Perttunen 
232*e902585fSMikko Perttunen 		g = &job->cmds[i].gather;
233*e902585fSMikko Perttunen 
2346579324aSTerje Bergstrom 		g->bo = host1x_bo_get(g->bo);
235404bfb78SMikko Perttunen 		if (!g->bo) {
236404bfb78SMikko Perttunen 			err = -EINVAL;
2376579324aSTerje Bergstrom 			goto unpin;
238404bfb78SMikko Perttunen 		}
2396579324aSTerje Bergstrom 
240273da5a0SThierry Reding 		/**
241273da5a0SThierry Reding 		 * If the host1x is not attached to an IOMMU, there is no need
242273da5a0SThierry Reding 		 * to map the buffer object for the host1x, since the physical
243273da5a0SThierry Reding 		 * address can simply be used.
244273da5a0SThierry Reding 		 */
245273da5a0SThierry Reding 		if (!iommu_get_domain_for_dev(host->dev))
246273da5a0SThierry Reding 			phys = &phys_addr;
247273da5a0SThierry Reding 		else
248273da5a0SThierry Reding 			phys = NULL;
249273da5a0SThierry Reding 
250273da5a0SThierry Reding 		sgt = host1x_bo_pin(host->dev, g->bo, phys);
25180327ce3SThierry Reding 		if (IS_ERR(sgt)) {
25280327ce3SThierry Reding 			err = PTR_ERR(sgt);
253fd323e9eSDmitry Osipenko 			goto put;
25480327ce3SThierry Reding 		}
2556579324aSTerje Bergstrom 
25626c8de5eSDmitry Osipenko 		if (host->domain) {
25767ed9f9dSMarek Szyprowski 			for_each_sgtable_sg(sgt, sg, j)
258404bfb78SMikko Perttunen 				gather_size += sg->length;
259404bfb78SMikko Perttunen 			gather_size = iova_align(&host->iova, gather_size);
260404bfb78SMikko Perttunen 
261404bfb78SMikko Perttunen 			shift = iova_shift(&host->iova);
262404bfb78SMikko Perttunen 			alloc = alloc_iova(&host->iova, gather_size >> shift,
263404bfb78SMikko Perttunen 					   host->iova_end >> shift, true);
264404bfb78SMikko Perttunen 			if (!alloc) {
265404bfb78SMikko Perttunen 				err = -ENOMEM;
266fd323e9eSDmitry Osipenko 				goto put;
267404bfb78SMikko Perttunen 			}
268404bfb78SMikko Perttunen 
26967ed9f9dSMarek Szyprowski 			err = iommu_map_sgtable(host->domain,
270404bfb78SMikko Perttunen 					iova_dma_addr(&host->iova, alloc),
27167ed9f9dSMarek Szyprowski 					sgt, IOMMU_READ);
272404bfb78SMikko Perttunen 			if (err == 0) {
273404bfb78SMikko Perttunen 				__free_iova(&host->iova, alloc);
274404bfb78SMikko Perttunen 				err = -EINVAL;
275fd323e9eSDmitry Osipenko 				goto put;
276404bfb78SMikko Perttunen 			}
277404bfb78SMikko Perttunen 
278404bfb78SMikko Perttunen 			job->unpins[job->num_unpins].size = gather_size;
279af1cbfb9SThierry Reding 			phys_addr = iova_dma_addr(&host->iova, alloc);
280273da5a0SThierry Reding 		} else if (sgt) {
28167ed9f9dSMarek Szyprowski 			err = dma_map_sgtable(host->dev, sgt, DMA_TO_DEVICE, 0);
28267ed9f9dSMarek Szyprowski 			if (err)
283fd323e9eSDmitry Osipenko 				goto put;
284404bfb78SMikko Perttunen 
28598ae41adSThierry Reding 			job->unpins[job->num_unpins].dir = DMA_TO_DEVICE;
286af1cbfb9SThierry Reding 			job->unpins[job->num_unpins].dev = host->dev;
287af1cbfb9SThierry Reding 			phys_addr = sg_dma_address(sgt->sgl);
288af1cbfb9SThierry Reding 		}
289404bfb78SMikko Perttunen 
290af1cbfb9SThierry Reding 		job->addr_phys[job->num_unpins] = phys_addr;
291af1cbfb9SThierry Reding 		job->gather_addr_phys[i] = phys_addr;
292af1cbfb9SThierry Reding 
2936579324aSTerje Bergstrom 		job->unpins[job->num_unpins].bo = g->bo;
2946579324aSTerje Bergstrom 		job->unpins[job->num_unpins].sgt = sgt;
2956579324aSTerje Bergstrom 		job->num_unpins++;
2966579324aSTerje Bergstrom 	}
2976579324aSTerje Bergstrom 
298404bfb78SMikko Perttunen 	return 0;
2996579324aSTerje Bergstrom 
300fd323e9eSDmitry Osipenko put:
301fd323e9eSDmitry Osipenko 	host1x_bo_put(g->bo);
3026579324aSTerje Bergstrom unpin:
3036579324aSTerje Bergstrom 	host1x_job_unpin(job);
304404bfb78SMikko Perttunen 	return err;
3056579324aSTerje Bergstrom }
3066579324aSTerje Bergstrom 
30747f89c10SDmitry Osipenko static int do_relocs(struct host1x_job *job, struct host1x_job_gather *g)
3086579324aSTerje Bergstrom {
3097a8139c5SDaniel Vetter 	void *cmdbuf_addr = NULL;
31047f89c10SDmitry Osipenko 	struct host1x_bo *cmdbuf = g->bo;
311d4ad3ad9SThierry Reding 	unsigned int i;
3126579324aSTerje Bergstrom 
3136579324aSTerje Bergstrom 	/* pin & patch the relocs for one gather */
3143364cd28SArto Merilainen 	for (i = 0; i < job->num_relocs; i++) {
31506490bb9SThierry Reding 		struct host1x_reloc *reloc = &job->relocs[i];
3166579324aSTerje Bergstrom 		u32 reloc_addr = (job->reloc_addr_phys[i] +
317961e3beaSThierry Reding 				  reloc->target.offset) >> reloc->shift;
3186579324aSTerje Bergstrom 		u32 *target;
3196579324aSTerje Bergstrom 
3206579324aSTerje Bergstrom 		/* skip all other gathers */
321961e3beaSThierry Reding 		if (cmdbuf != reloc->cmdbuf.bo)
3226579324aSTerje Bergstrom 			continue;
3236579324aSTerje Bergstrom 
32447f89c10SDmitry Osipenko 		if (IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL)) {
32547f89c10SDmitry Osipenko 			target = (u32 *)job->gather_copy_mapped +
32647f89c10SDmitry Osipenko 					reloc->cmdbuf.offset / sizeof(u32) +
32747f89c10SDmitry Osipenko 						g->offset / sizeof(u32);
32847f89c10SDmitry Osipenko 			goto patch_reloc;
32947f89c10SDmitry Osipenko 		}
33047f89c10SDmitry Osipenko 
3317a8139c5SDaniel Vetter 		if (!cmdbuf_addr) {
3327a8139c5SDaniel Vetter 			cmdbuf_addr = host1x_bo_mmap(cmdbuf);
3336579324aSTerje Bergstrom 
3347a8139c5SDaniel Vetter 			if (unlikely(!cmdbuf_addr)) {
3356579324aSTerje Bergstrom 				pr_err("Could not map cmdbuf for relocation\n");
3366579324aSTerje Bergstrom 				return -ENOMEM;
3376579324aSTerje Bergstrom 			}
3386579324aSTerje Bergstrom 		}
3396579324aSTerje Bergstrom 
3407a8139c5SDaniel Vetter 		target = cmdbuf_addr + reloc->cmdbuf.offset;
34147f89c10SDmitry Osipenko patch_reloc:
3426579324aSTerje Bergstrom 		*target = reloc_addr;
3436579324aSTerje Bergstrom 	}
3446579324aSTerje Bergstrom 
3457a8139c5SDaniel Vetter 	if (cmdbuf_addr)
3467a8139c5SDaniel Vetter 		host1x_bo_munmap(cmdbuf, cmdbuf_addr);
3476579324aSTerje Bergstrom 
3486579324aSTerje Bergstrom 	return 0;
3496579324aSTerje Bergstrom }
3506579324aSTerje Bergstrom 
3515060d8ecSArto Merilainen static bool check_reloc(struct host1x_reloc *reloc, struct host1x_bo *cmdbuf,
3526579324aSTerje Bergstrom 			unsigned int offset)
3536579324aSTerje Bergstrom {
3546579324aSTerje Bergstrom 	offset *= sizeof(u32);
3556579324aSTerje Bergstrom 
356961e3beaSThierry Reding 	if (reloc->cmdbuf.bo != cmdbuf || reloc->cmdbuf.offset != offset)
3575060d8ecSArto Merilainen 		return false;
3586579324aSTerje Bergstrom 
359571cbf70SDmitry Osipenko 	/* relocation shift value validation isn't implemented yet */
360571cbf70SDmitry Osipenko 	if (reloc->shift)
361571cbf70SDmitry Osipenko 		return false;
362571cbf70SDmitry Osipenko 
3635060d8ecSArto Merilainen 	return true;
3646579324aSTerje Bergstrom }
3656579324aSTerje Bergstrom 
3666579324aSTerje Bergstrom struct host1x_firewall {
3676579324aSTerje Bergstrom 	struct host1x_job *job;
3686579324aSTerje Bergstrom 	struct device *dev;
3696579324aSTerje Bergstrom 
3706579324aSTerje Bergstrom 	unsigned int num_relocs;
3716579324aSTerje Bergstrom 	struct host1x_reloc *reloc;
3726579324aSTerje Bergstrom 
373d7fbcf47SThierry Reding 	struct host1x_bo *cmdbuf;
3746579324aSTerje Bergstrom 	unsigned int offset;
3756579324aSTerje Bergstrom 
3766579324aSTerje Bergstrom 	u32 words;
3776579324aSTerje Bergstrom 	u32 class;
3786579324aSTerje Bergstrom 	u32 reg;
3796579324aSTerje Bergstrom 	u32 mask;
3806579324aSTerje Bergstrom 	u32 count;
3816579324aSTerje Bergstrom };
3826579324aSTerje Bergstrom 
383d77563ffSThierry Reding static int check_register(struct host1x_firewall *fw, unsigned long offset)
384d77563ffSThierry Reding {
3850f563a4bSDmitry Osipenko 	if (!fw->job->is_addr_reg)
3860f563a4bSDmitry Osipenko 		return 0;
3870f563a4bSDmitry Osipenko 
388d77563ffSThierry Reding 	if (fw->job->is_addr_reg(fw->dev, fw->class, offset)) {
389d77563ffSThierry Reding 		if (!fw->num_relocs)
390d77563ffSThierry Reding 			return -EINVAL;
391d77563ffSThierry Reding 
392d77563ffSThierry Reding 		if (!check_reloc(fw->reloc, fw->cmdbuf, fw->offset))
393d77563ffSThierry Reding 			return -EINVAL;
394d77563ffSThierry Reding 
395d77563ffSThierry Reding 		fw->num_relocs--;
396d77563ffSThierry Reding 		fw->reloc++;
397d77563ffSThierry Reding 	}
398d77563ffSThierry Reding 
399d77563ffSThierry Reding 	return 0;
400d77563ffSThierry Reding }
401d77563ffSThierry Reding 
4020f563a4bSDmitry Osipenko static int check_class(struct host1x_firewall *fw, u32 class)
4030f563a4bSDmitry Osipenko {
4040f563a4bSDmitry Osipenko 	if (!fw->job->is_valid_class) {
4050f563a4bSDmitry Osipenko 		if (fw->class != class)
4060f563a4bSDmitry Osipenko 			return -EINVAL;
4070f563a4bSDmitry Osipenko 	} else {
4080f563a4bSDmitry Osipenko 		if (!fw->job->is_valid_class(fw->class))
4090f563a4bSDmitry Osipenko 			return -EINVAL;
4100f563a4bSDmitry Osipenko 	}
4110f563a4bSDmitry Osipenko 
4120f563a4bSDmitry Osipenko 	return 0;
4130f563a4bSDmitry Osipenko }
4140f563a4bSDmitry Osipenko 
4156579324aSTerje Bergstrom static int check_mask(struct host1x_firewall *fw)
4166579324aSTerje Bergstrom {
4176579324aSTerje Bergstrom 	u32 mask = fw->mask;
4186579324aSTerje Bergstrom 	u32 reg = fw->reg;
419d77563ffSThierry Reding 	int ret;
4206579324aSTerje Bergstrom 
4216579324aSTerje Bergstrom 	while (mask) {
4226579324aSTerje Bergstrom 		if (fw->words == 0)
4236579324aSTerje Bergstrom 			return -EINVAL;
4246579324aSTerje Bergstrom 
4256579324aSTerje Bergstrom 		if (mask & 1) {
426d77563ffSThierry Reding 			ret = check_register(fw, reg);
427d77563ffSThierry Reding 			if (ret < 0)
428d77563ffSThierry Reding 				return ret;
429d77563ffSThierry Reding 
4306579324aSTerje Bergstrom 			fw->words--;
4316579324aSTerje Bergstrom 			fw->offset++;
4326579324aSTerje Bergstrom 		}
4336579324aSTerje Bergstrom 		mask >>= 1;
4346579324aSTerje Bergstrom 		reg++;
4356579324aSTerje Bergstrom 	}
4366579324aSTerje Bergstrom 
4376579324aSTerje Bergstrom 	return 0;
4386579324aSTerje Bergstrom }
4396579324aSTerje Bergstrom 
4406579324aSTerje Bergstrom static int check_incr(struct host1x_firewall *fw)
4416579324aSTerje Bergstrom {
4426579324aSTerje Bergstrom 	u32 count = fw->count;
4436579324aSTerje Bergstrom 	u32 reg = fw->reg;
444d77563ffSThierry Reding 	int ret;
4456579324aSTerje Bergstrom 
44664c173d3STerje Bergstrom 	while (count) {
4476579324aSTerje Bergstrom 		if (fw->words == 0)
4486579324aSTerje Bergstrom 			return -EINVAL;
4496579324aSTerje Bergstrom 
450d77563ffSThierry Reding 		ret = check_register(fw, reg);
451d77563ffSThierry Reding 		if (ret < 0)
452d77563ffSThierry Reding 			return ret;
453d77563ffSThierry Reding 
4546579324aSTerje Bergstrom 		reg++;
4556579324aSTerje Bergstrom 		fw->words--;
4566579324aSTerje Bergstrom 		fw->offset++;
4576579324aSTerje Bergstrom 		count--;
4586579324aSTerje Bergstrom 	}
4596579324aSTerje Bergstrom 
4606579324aSTerje Bergstrom 	return 0;
4616579324aSTerje Bergstrom }
4626579324aSTerje Bergstrom 
4636579324aSTerje Bergstrom static int check_nonincr(struct host1x_firewall *fw)
4646579324aSTerje Bergstrom {
4656579324aSTerje Bergstrom 	u32 count = fw->count;
466d77563ffSThierry Reding 	int ret;
4676579324aSTerje Bergstrom 
4686579324aSTerje Bergstrom 	while (count) {
4696579324aSTerje Bergstrom 		if (fw->words == 0)
4706579324aSTerje Bergstrom 			return -EINVAL;
4716579324aSTerje Bergstrom 
472d77563ffSThierry Reding 		ret = check_register(fw, fw->reg);
473d77563ffSThierry Reding 		if (ret < 0)
474d77563ffSThierry Reding 			return ret;
475d77563ffSThierry Reding 
4766579324aSTerje Bergstrom 		fw->words--;
4776579324aSTerje Bergstrom 		fw->offset++;
4786579324aSTerje Bergstrom 		count--;
4796579324aSTerje Bergstrom 	}
4806579324aSTerje Bergstrom 
4816579324aSTerje Bergstrom 	return 0;
4826579324aSTerje Bergstrom }
4836579324aSTerje Bergstrom 
484afac0e43STerje Bergstrom static int validate(struct host1x_firewall *fw, struct host1x_job_gather *g)
4856579324aSTerje Bergstrom {
4863364cd28SArto Merilainen 	u32 *cmdbuf_base = (u32 *)fw->job->gather_copy_mapped +
4873364cd28SArto Merilainen 		(g->offset / sizeof(u32));
4880f563a4bSDmitry Osipenko 	u32 job_class = fw->class;
4896579324aSTerje Bergstrom 	int err = 0;
4906579324aSTerje Bergstrom 
491afac0e43STerje Bergstrom 	fw->words = g->words;
492d7fbcf47SThierry Reding 	fw->cmdbuf = g->bo;
493afac0e43STerje Bergstrom 	fw->offset = 0;
4946579324aSTerje Bergstrom 
495afac0e43STerje Bergstrom 	while (fw->words && !err) {
496afac0e43STerje Bergstrom 		u32 word = cmdbuf_base[fw->offset];
4976579324aSTerje Bergstrom 		u32 opcode = (word & 0xf0000000) >> 28;
4986579324aSTerje Bergstrom 
499afac0e43STerje Bergstrom 		fw->mask = 0;
500afac0e43STerje Bergstrom 		fw->reg = 0;
501afac0e43STerje Bergstrom 		fw->count = 0;
502afac0e43STerje Bergstrom 		fw->words--;
503afac0e43STerje Bergstrom 		fw->offset++;
5046579324aSTerje Bergstrom 
5056579324aSTerje Bergstrom 		switch (opcode) {
5066579324aSTerje Bergstrom 		case 0:
507afac0e43STerje Bergstrom 			fw->class = word >> 6 & 0x3ff;
508afac0e43STerje Bergstrom 			fw->mask = word & 0x3f;
509afac0e43STerje Bergstrom 			fw->reg = word >> 16 & 0xfff;
5100f563a4bSDmitry Osipenko 			err = check_class(fw, job_class);
5110f563a4bSDmitry Osipenko 			if (!err)
512afac0e43STerje Bergstrom 				err = check_mask(fw);
5136579324aSTerje Bergstrom 			if (err)
5146579324aSTerje Bergstrom 				goto out;
5156579324aSTerje Bergstrom 			break;
5166579324aSTerje Bergstrom 		case 1:
517afac0e43STerje Bergstrom 			fw->reg = word >> 16 & 0xfff;
518afac0e43STerje Bergstrom 			fw->count = word & 0xffff;
519afac0e43STerje Bergstrom 			err = check_incr(fw);
5206579324aSTerje Bergstrom 			if (err)
5216579324aSTerje Bergstrom 				goto out;
5226579324aSTerje Bergstrom 			break;
5236579324aSTerje Bergstrom 
5246579324aSTerje Bergstrom 		case 2:
525afac0e43STerje Bergstrom 			fw->reg = word >> 16 & 0xfff;
526afac0e43STerje Bergstrom 			fw->count = word & 0xffff;
527afac0e43STerje Bergstrom 			err = check_nonincr(fw);
5286579324aSTerje Bergstrom 			if (err)
5296579324aSTerje Bergstrom 				goto out;
5306579324aSTerje Bergstrom 			break;
5316579324aSTerje Bergstrom 
5326579324aSTerje Bergstrom 		case 3:
533afac0e43STerje Bergstrom 			fw->mask = word & 0xffff;
534afac0e43STerje Bergstrom 			fw->reg = word >> 16 & 0xfff;
535afac0e43STerje Bergstrom 			err = check_mask(fw);
5366579324aSTerje Bergstrom 			if (err)
5376579324aSTerje Bergstrom 				goto out;
5386579324aSTerje Bergstrom 			break;
5396579324aSTerje Bergstrom 		case 4:
5406579324aSTerje Bergstrom 		case 14:
5416579324aSTerje Bergstrom 			break;
5426579324aSTerje Bergstrom 		default:
5436579324aSTerje Bergstrom 			err = -EINVAL;
5446579324aSTerje Bergstrom 			break;
5456579324aSTerje Bergstrom 		}
5466579324aSTerje Bergstrom 	}
5476579324aSTerje Bergstrom 
5486579324aSTerje Bergstrom out:
5496579324aSTerje Bergstrom 	return err;
5506579324aSTerje Bergstrom }
5516579324aSTerje Bergstrom 
552b78e70c0SThierry Reding static inline int copy_gathers(struct device *host, struct host1x_job *job,
553b78e70c0SThierry Reding 			       struct device *dev)
5546579324aSTerje Bergstrom {
5553364cd28SArto Merilainen 	struct host1x_firewall fw;
5566579324aSTerje Bergstrom 	size_t size = 0;
5576579324aSTerje Bergstrom 	size_t offset = 0;
558d4ad3ad9SThierry Reding 	unsigned int i;
5596579324aSTerje Bergstrom 
5603364cd28SArto Merilainen 	fw.job = job;
5613364cd28SArto Merilainen 	fw.dev = dev;
56206490bb9SThierry Reding 	fw.reloc = job->relocs;
5633364cd28SArto Merilainen 	fw.num_relocs = job->num_relocs;
5643833d16fSDmitry Osipenko 	fw.class = job->class;
5653364cd28SArto Merilainen 
566*e902585fSMikko Perttunen 	for (i = 0; i < job->num_cmds; i++) {
567*e902585fSMikko Perttunen 		struct host1x_job_gather *g;
568*e902585fSMikko Perttunen 
569*e902585fSMikko Perttunen 		if (job->cmds[i].is_wait)
570*e902585fSMikko Perttunen 			continue;
571*e902585fSMikko Perttunen 
572*e902585fSMikko Perttunen 		g = &job->cmds[i].gather;
5736df633d0SThierry Reding 
5746579324aSTerje Bergstrom 		size += g->words * sizeof(u32);
5756579324aSTerje Bergstrom 	}
5766579324aSTerje Bergstrom 
57743240bbdSDmitry Osipenko 	/*
57843240bbdSDmitry Osipenko 	 * Try a non-blocking allocation from a higher priority pools first,
57943240bbdSDmitry Osipenko 	 * as awaiting for the allocation here is a major performance hit.
58043240bbdSDmitry Osipenko 	 */
581b78e70c0SThierry Reding 	job->gather_copy_mapped = dma_alloc_wc(host, size, &job->gather_copy,
58243240bbdSDmitry Osipenko 					       GFP_NOWAIT);
58343240bbdSDmitry Osipenko 
58443240bbdSDmitry Osipenko 	/* the higher priority allocation failed, try the generic-blocking */
58543240bbdSDmitry Osipenko 	if (!job->gather_copy_mapped)
586b78e70c0SThierry Reding 		job->gather_copy_mapped = dma_alloc_wc(host, size,
58743240bbdSDmitry Osipenko 						       &job->gather_copy,
5886579324aSTerje Bergstrom 						       GFP_KERNEL);
58943240bbdSDmitry Osipenko 	if (!job->gather_copy_mapped)
590745cecc0SDan Carpenter 		return -ENOMEM;
5916579324aSTerje Bergstrom 
5926579324aSTerje Bergstrom 	job->gather_copy_size = size;
5936579324aSTerje Bergstrom 
594*e902585fSMikko Perttunen 	for (i = 0; i < job->num_cmds; i++) {
595*e902585fSMikko Perttunen 		struct host1x_job_gather *g;
5966579324aSTerje Bergstrom 		void *gather;
5976579324aSTerje Bergstrom 
598*e902585fSMikko Perttunen 		if (job->cmds[i].is_wait)
599*e902585fSMikko Perttunen 			continue;
600*e902585fSMikko Perttunen 		g = &job->cmds[i].gather;
601*e902585fSMikko Perttunen 
6023364cd28SArto Merilainen 		/* Copy the gather */
6036579324aSTerje Bergstrom 		gather = host1x_bo_mmap(g->bo);
6046579324aSTerje Bergstrom 		memcpy(job->gather_copy_mapped + offset, gather + g->offset,
6056579324aSTerje Bergstrom 		       g->words * sizeof(u32));
6066579324aSTerje Bergstrom 		host1x_bo_munmap(g->bo, gather);
6076579324aSTerje Bergstrom 
6083364cd28SArto Merilainen 		/* Store the location in the buffer */
6096579324aSTerje Bergstrom 		g->base = job->gather_copy;
6106579324aSTerje Bergstrom 		g->offset = offset;
6113364cd28SArto Merilainen 
6123364cd28SArto Merilainen 		/* Validate the job */
6133364cd28SArto Merilainen 		if (validate(&fw, g))
6143364cd28SArto Merilainen 			return -EINVAL;
6156579324aSTerje Bergstrom 
6166579324aSTerje Bergstrom 		offset += g->words * sizeof(u32);
6176579324aSTerje Bergstrom 	}
6186579324aSTerje Bergstrom 
61924c94e16SThierry Reding 	/* No relocs should remain at this point */
62024c94e16SThierry Reding 	if (fw.num_relocs)
621a9ff9995SErik Faye-Lund 		return -EINVAL;
622a9ff9995SErik Faye-Lund 
6236579324aSTerje Bergstrom 	return 0;
6246579324aSTerje Bergstrom }
6256579324aSTerje Bergstrom 
6266579324aSTerje Bergstrom int host1x_job_pin(struct host1x_job *job, struct device *dev)
6276579324aSTerje Bergstrom {
6286579324aSTerje Bergstrom 	int err;
6296579324aSTerje Bergstrom 	unsigned int i, j;
6306579324aSTerje Bergstrom 	struct host1x *host = dev_get_drvdata(dev->parent);
6316579324aSTerje Bergstrom 
6326579324aSTerje Bergstrom 	/* pin memory */
633404bfb78SMikko Perttunen 	err = pin_job(host, job);
634404bfb78SMikko Perttunen 	if (err)
6356579324aSTerje Bergstrom 		goto out;
6366579324aSTerje Bergstrom 
63747f89c10SDmitry Osipenko 	if (IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL)) {
638b78e70c0SThierry Reding 		err = copy_gathers(host->dev, job, dev);
63947f89c10SDmitry Osipenko 		if (err)
64047f89c10SDmitry Osipenko 			goto out;
64147f89c10SDmitry Osipenko 	}
64247f89c10SDmitry Osipenko 
6436579324aSTerje Bergstrom 	/* patch gathers */
644*e902585fSMikko Perttunen 	for (i = 0; i < job->num_cmds; i++) {
645*e902585fSMikko Perttunen 		struct host1x_job_gather *g;
646*e902585fSMikko Perttunen 
647*e902585fSMikko Perttunen 		if (job->cmds[i].is_wait)
648*e902585fSMikko Perttunen 			continue;
649*e902585fSMikko Perttunen 		g = &job->cmds[i].gather;
6506579324aSTerje Bergstrom 
6516579324aSTerje Bergstrom 		/* process each gather mem only once */
6526579324aSTerje Bergstrom 		if (g->handled)
6536579324aSTerje Bergstrom 			continue;
6546579324aSTerje Bergstrom 
65547f89c10SDmitry Osipenko 		/* copy_gathers() sets gathers base if firewall is enabled */
65647f89c10SDmitry Osipenko 		if (!IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL))
6576579324aSTerje Bergstrom 			g->base = job->gather_addr_phys[i];
6586579324aSTerje Bergstrom 
659*e902585fSMikko Perttunen 		for (j = i + 1; j < job->num_cmds; j++) {
660*e902585fSMikko Perttunen 			if (!job->cmds[j].is_wait &&
661*e902585fSMikko Perttunen 			    job->cmds[j].gather.bo == g->bo) {
662*e902585fSMikko Perttunen 				job->cmds[j].gather.handled = true;
663*e902585fSMikko Perttunen 				job->cmds[j].gather.base = g->base;
664f08ef2d1SArto Merilainen 			}
665f08ef2d1SArto Merilainen 		}
6666579324aSTerje Bergstrom 
66747f89c10SDmitry Osipenko 		err = do_relocs(job, g);
6683364cd28SArto Merilainen 		if (err)
66947f89c10SDmitry Osipenko 			break;
6706579324aSTerje Bergstrom 	}
6716579324aSTerje Bergstrom 
6726579324aSTerje Bergstrom out:
673e5855aa3SDmitry Osipenko 	if (err)
674e5855aa3SDmitry Osipenko 		host1x_job_unpin(job);
6756579324aSTerje Bergstrom 	wmb();
6766579324aSTerje Bergstrom 
6776579324aSTerje Bergstrom 	return err;
6786579324aSTerje Bergstrom }
679fae798a1SThierry Reding EXPORT_SYMBOL(host1x_job_pin);
6806579324aSTerje Bergstrom 
6816579324aSTerje Bergstrom void host1x_job_unpin(struct host1x_job *job)
6826579324aSTerje Bergstrom {
683404bfb78SMikko Perttunen 	struct host1x *host = dev_get_drvdata(job->channel->dev->parent);
6846579324aSTerje Bergstrom 	unsigned int i;
6856579324aSTerje Bergstrom 
6866579324aSTerje Bergstrom 	for (i = 0; i < job->num_unpins; i++) {
6876579324aSTerje Bergstrom 		struct host1x_job_unpin_data *unpin = &job->unpins[i];
688af1cbfb9SThierry Reding 		struct device *dev = unpin->dev ?: host->dev;
689af1cbfb9SThierry Reding 		struct sg_table *sgt = unpin->sgt;
6906df633d0SThierry Reding 
691ec589232SDmitry Osipenko 		if (!IS_ENABLED(CONFIG_TEGRA_HOST1X_FIREWALL) &&
692ec589232SDmitry Osipenko 		    unpin->size && host->domain) {
693404bfb78SMikko Perttunen 			iommu_unmap(host->domain, job->addr_phys[i],
694404bfb78SMikko Perttunen 				    unpin->size);
695404bfb78SMikko Perttunen 			free_iova(&host->iova,
696404bfb78SMikko Perttunen 				iova_pfn(&host->iova, job->addr_phys[i]));
697404bfb78SMikko Perttunen 		}
698404bfb78SMikko Perttunen 
699af1cbfb9SThierry Reding 		if (unpin->dev && sgt)
70067ed9f9dSMarek Szyprowski 			dma_unmap_sgtable(unpin->dev, sgt, unpin->dir, 0);
701af1cbfb9SThierry Reding 
702af1cbfb9SThierry Reding 		host1x_bo_unpin(dev, unpin->bo, sgt);
7036579324aSTerje Bergstrom 		host1x_bo_put(unpin->bo);
7046579324aSTerje Bergstrom 	}
7050b8070d1SThierry Reding 
7066579324aSTerje Bergstrom 	job->num_unpins = 0;
7076579324aSTerje Bergstrom 
7086579324aSTerje Bergstrom 	if (job->gather_copy_size)
709b78e70c0SThierry Reding 		dma_free_wc(host->dev, job->gather_copy_size,
710f6e45661SLuis R. Rodriguez 			    job->gather_copy_mapped, job->gather_copy);
7116579324aSTerje Bergstrom }
712fae798a1SThierry Reding EXPORT_SYMBOL(host1x_job_unpin);
7136579324aSTerje Bergstrom 
7146579324aSTerje Bergstrom /*
7156579324aSTerje Bergstrom  * Debug routine used to dump job entries
7166579324aSTerje Bergstrom  */
7176579324aSTerje Bergstrom void host1x_job_dump(struct device *dev, struct host1x_job *job)
7186579324aSTerje Bergstrom {
7192aed4f5aSMikko Perttunen 	dev_dbg(dev, "    SYNCPT_ID   %d\n", job->syncpt->id);
7206579324aSTerje Bergstrom 	dev_dbg(dev, "    SYNCPT_VAL  %d\n", job->syncpt_end);
7216579324aSTerje Bergstrom 	dev_dbg(dev, "    FIRST_GET   0x%x\n", job->first_get);
7226579324aSTerje Bergstrom 	dev_dbg(dev, "    TIMEOUT     %d\n", job->timeout);
7236579324aSTerje Bergstrom 	dev_dbg(dev, "    NUM_SLOTS   %d\n", job->num_slots);
7246579324aSTerje Bergstrom 	dev_dbg(dev, "    NUM_HANDLES %d\n", job->num_unpins);
7256579324aSTerje Bergstrom }
726