xref: /openbmc/linux/drivers/gpu/drm/msm/msm_gem_submit.c (revision 4da722ca19f30f7db250db808d1ab1703607a932)
1 /*
2  * Copyright (C) 2013 Red Hat
3  * Author: Rob Clark <robdclark@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 as published by
7  * the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
12  * more details.
13  *
14  * You should have received a copy of the GNU General Public License along with
15  * this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <linux/sync_file.h>
19 
20 #include "msm_drv.h"
21 #include "msm_gpu.h"
22 #include "msm_gem.h"
23 
24 /*
25  * Cmdstream submission:
26  */
27 
28 /* make sure these don't conflict w/ MSM_SUBMIT_BO_x */
29 #define BO_VALID    0x8000   /* is current addr in cmdstream correct/valid? */
30 #define BO_LOCKED   0x4000
31 #define BO_PINNED   0x2000
32 
33 static struct msm_gem_submit *submit_create(struct drm_device *dev,
34 		struct msm_gpu *gpu, uint32_t nr_bos, uint32_t nr_cmds)
35 {
36 	struct msm_gem_submit *submit;
37 	uint64_t sz = sizeof(*submit) + (nr_bos * sizeof(submit->bos[0])) +
38 		(nr_cmds * sizeof(submit->cmd[0]));
39 
40 	if (sz > SIZE_MAX)
41 		return NULL;
42 
43 	submit = kmalloc(sz, GFP_TEMPORARY | __GFP_NOWARN | __GFP_NORETRY);
44 	if (!submit)
45 		return NULL;
46 
47 	submit->dev = dev;
48 	submit->gpu = gpu;
49 	submit->fence = NULL;
50 	submit->pid = get_pid(task_pid(current));
51 	submit->cmd = (void *)&submit->bos[nr_bos];
52 
53 	/* initially, until copy_from_user() and bo lookup succeeds: */
54 	submit->nr_bos = 0;
55 	submit->nr_cmds = 0;
56 
57 	INIT_LIST_HEAD(&submit->node);
58 	INIT_LIST_HEAD(&submit->bo_list);
59 	ww_acquire_init(&submit->ticket, &reservation_ww_class);
60 
61 	return submit;
62 }
63 
64 void msm_gem_submit_free(struct msm_gem_submit *submit)
65 {
66 	dma_fence_put(submit->fence);
67 	list_del(&submit->node);
68 	put_pid(submit->pid);
69 	kfree(submit);
70 }
71 
72 static inline unsigned long __must_check
73 copy_from_user_inatomic(void *to, const void __user *from, unsigned long n)
74 {
75 	if (access_ok(VERIFY_READ, from, n))
76 		return __copy_from_user_inatomic(to, from, n);
77 	return -EFAULT;
78 }
79 
80 static int submit_lookup_objects(struct msm_gem_submit *submit,
81 		struct drm_msm_gem_submit *args, struct drm_file *file)
82 {
83 	unsigned i;
84 	int ret = 0;
85 
86 	spin_lock(&file->table_lock);
87 	pagefault_disable();
88 
89 	for (i = 0; i < args->nr_bos; i++) {
90 		struct drm_msm_gem_submit_bo submit_bo;
91 		struct drm_gem_object *obj;
92 		struct msm_gem_object *msm_obj;
93 		void __user *userptr =
94 			u64_to_user_ptr(args->bos + (i * sizeof(submit_bo)));
95 
96 		/* make sure we don't have garbage flags, in case we hit
97 		 * error path before flags is initialized:
98 		 */
99 		submit->bos[i].flags = 0;
100 
101 		if (copy_from_user_inatomic(&submit_bo, userptr, sizeof(submit_bo))) {
102 			pagefault_enable();
103 			spin_unlock(&file->table_lock);
104 			if (copy_from_user(&submit_bo, userptr, sizeof(submit_bo))) {
105 				ret = -EFAULT;
106 				goto out;
107 			}
108 			spin_lock(&file->table_lock);
109 			pagefault_disable();
110 		}
111 
112 		if ((submit_bo.flags & ~MSM_SUBMIT_BO_FLAGS) ||
113 			!(submit_bo.flags & MSM_SUBMIT_BO_FLAGS)) {
114 			DRM_ERROR("invalid flags: %x\n", submit_bo.flags);
115 			ret = -EINVAL;
116 			goto out_unlock;
117 		}
118 
119 		submit->bos[i].flags = submit_bo.flags;
120 		/* in validate_objects() we figure out if this is true: */
121 		submit->bos[i].iova  = submit_bo.presumed;
122 
123 		/* normally use drm_gem_object_lookup(), but for bulk lookup
124 		 * all under single table_lock just hit object_idr directly:
125 		 */
126 		obj = idr_find(&file->object_idr, submit_bo.handle);
127 		if (!obj) {
128 			DRM_ERROR("invalid handle %u at index %u\n", submit_bo.handle, i);
129 			ret = -EINVAL;
130 			goto out_unlock;
131 		}
132 
133 		msm_obj = to_msm_bo(obj);
134 
135 		if (!list_empty(&msm_obj->submit_entry)) {
136 			DRM_ERROR("handle %u at index %u already on submit list\n",
137 					submit_bo.handle, i);
138 			ret = -EINVAL;
139 			goto out_unlock;
140 		}
141 
142 		drm_gem_object_reference(obj);
143 
144 		submit->bos[i].obj = msm_obj;
145 
146 		list_add_tail(&msm_obj->submit_entry, &submit->bo_list);
147 	}
148 
149 out_unlock:
150 	pagefault_enable();
151 	spin_unlock(&file->table_lock);
152 
153 out:
154 	submit->nr_bos = i;
155 
156 	return ret;
157 }
158 
159 static void submit_unlock_unpin_bo(struct msm_gem_submit *submit, int i)
160 {
161 	struct msm_gem_object *msm_obj = submit->bos[i].obj;
162 
163 	if (submit->bos[i].flags & BO_PINNED)
164 		msm_gem_put_iova(&msm_obj->base, submit->gpu->aspace);
165 
166 	if (submit->bos[i].flags & BO_LOCKED)
167 		ww_mutex_unlock(&msm_obj->resv->lock);
168 
169 	if (!(submit->bos[i].flags & BO_VALID))
170 		submit->bos[i].iova = 0;
171 
172 	submit->bos[i].flags &= ~(BO_LOCKED | BO_PINNED);
173 }
174 
175 /* This is where we make sure all the bo's are reserved and pin'd: */
176 static int submit_lock_objects(struct msm_gem_submit *submit)
177 {
178 	int contended, slow_locked = -1, i, ret = 0;
179 
180 retry:
181 	for (i = 0; i < submit->nr_bos; i++) {
182 		struct msm_gem_object *msm_obj = submit->bos[i].obj;
183 
184 		if (slow_locked == i)
185 			slow_locked = -1;
186 
187 		contended = i;
188 
189 		if (!(submit->bos[i].flags & BO_LOCKED)) {
190 			ret = ww_mutex_lock_interruptible(&msm_obj->resv->lock,
191 					&submit->ticket);
192 			if (ret)
193 				goto fail;
194 			submit->bos[i].flags |= BO_LOCKED;
195 		}
196 	}
197 
198 	ww_acquire_done(&submit->ticket);
199 
200 	return 0;
201 
202 fail:
203 	for (; i >= 0; i--)
204 		submit_unlock_unpin_bo(submit, i);
205 
206 	if (slow_locked > 0)
207 		submit_unlock_unpin_bo(submit, slow_locked);
208 
209 	if (ret == -EDEADLK) {
210 		struct msm_gem_object *msm_obj = submit->bos[contended].obj;
211 		/* we lost out in a seqno race, lock and retry.. */
212 		ret = ww_mutex_lock_slow_interruptible(&msm_obj->resv->lock,
213 				&submit->ticket);
214 		if (!ret) {
215 			submit->bos[contended].flags |= BO_LOCKED;
216 			slow_locked = contended;
217 			goto retry;
218 		}
219 	}
220 
221 	return ret;
222 }
223 
224 static int submit_fence_sync(struct msm_gem_submit *submit)
225 {
226 	int i, ret = 0;
227 
228 	for (i = 0; i < submit->nr_bos; i++) {
229 		struct msm_gem_object *msm_obj = submit->bos[i].obj;
230 		bool write = submit->bos[i].flags & MSM_SUBMIT_BO_WRITE;
231 
232 		ret = msm_gem_sync_object(&msm_obj->base, submit->gpu->fctx, write);
233 		if (ret)
234 			break;
235 	}
236 
237 	return ret;
238 }
239 
240 static int submit_pin_objects(struct msm_gem_submit *submit)
241 {
242 	int i, ret = 0;
243 
244 	submit->valid = true;
245 
246 	for (i = 0; i < submit->nr_bos; i++) {
247 		struct msm_gem_object *msm_obj = submit->bos[i].obj;
248 		uint64_t iova;
249 
250 		/* if locking succeeded, pin bo: */
251 		ret = msm_gem_get_iova(&msm_obj->base,
252 				submit->gpu->aspace, &iova);
253 
254 		if (ret)
255 			break;
256 
257 		submit->bos[i].flags |= BO_PINNED;
258 
259 		if (iova == submit->bos[i].iova) {
260 			submit->bos[i].flags |= BO_VALID;
261 		} else {
262 			submit->bos[i].iova = iova;
263 			/* iova changed, so address in cmdstream is not valid: */
264 			submit->bos[i].flags &= ~BO_VALID;
265 			submit->valid = false;
266 		}
267 	}
268 
269 	return ret;
270 }
271 
272 static int submit_bo(struct msm_gem_submit *submit, uint32_t idx,
273 		struct msm_gem_object **obj, uint64_t *iova, bool *valid)
274 {
275 	if (idx >= submit->nr_bos) {
276 		DRM_ERROR("invalid buffer index: %u (out of %u)\n",
277 				idx, submit->nr_bos);
278 		return -EINVAL;
279 	}
280 
281 	if (obj)
282 		*obj = submit->bos[idx].obj;
283 	if (iova)
284 		*iova = submit->bos[idx].iova;
285 	if (valid)
286 		*valid = !!(submit->bos[idx].flags & BO_VALID);
287 
288 	return 0;
289 }
290 
291 /* process the reloc's and patch up the cmdstream as needed: */
292 static int submit_reloc(struct msm_gem_submit *submit, struct msm_gem_object *obj,
293 		uint32_t offset, uint32_t nr_relocs, uint64_t relocs)
294 {
295 	uint32_t i, last_offset = 0;
296 	uint32_t *ptr;
297 	int ret = 0;
298 
299 	if (offset % 4) {
300 		DRM_ERROR("non-aligned cmdstream buffer: %u\n", offset);
301 		return -EINVAL;
302 	}
303 
304 	/* For now, just map the entire thing.  Eventually we probably
305 	 * to do it page-by-page, w/ kmap() if not vmap()d..
306 	 */
307 	ptr = msm_gem_get_vaddr(&obj->base);
308 
309 	if (IS_ERR(ptr)) {
310 		ret = PTR_ERR(ptr);
311 		DBG("failed to map: %d", ret);
312 		return ret;
313 	}
314 
315 	for (i = 0; i < nr_relocs; i++) {
316 		struct drm_msm_gem_submit_reloc submit_reloc;
317 		void __user *userptr =
318 			u64_to_user_ptr(relocs + (i * sizeof(submit_reloc)));
319 		uint32_t off;
320 		uint64_t iova;
321 		bool valid;
322 
323 		if (copy_from_user(&submit_reloc, userptr, sizeof(submit_reloc))) {
324 			ret = -EFAULT;
325 			goto out;
326 		}
327 
328 		if (submit_reloc.submit_offset % 4) {
329 			DRM_ERROR("non-aligned reloc offset: %u\n",
330 					submit_reloc.submit_offset);
331 			ret = -EINVAL;
332 			goto out;
333 		}
334 
335 		/* offset in dwords: */
336 		off = submit_reloc.submit_offset / 4;
337 
338 		if ((off >= (obj->base.size / 4)) ||
339 				(off < last_offset)) {
340 			DRM_ERROR("invalid offset %u at reloc %u\n", off, i);
341 			ret = -EINVAL;
342 			goto out;
343 		}
344 
345 		ret = submit_bo(submit, submit_reloc.reloc_idx, NULL, &iova, &valid);
346 		if (ret)
347 			goto out;
348 
349 		if (valid)
350 			continue;
351 
352 		iova += submit_reloc.reloc_offset;
353 
354 		if (submit_reloc.shift < 0)
355 			iova >>= -submit_reloc.shift;
356 		else
357 			iova <<= submit_reloc.shift;
358 
359 		ptr[off] = iova | submit_reloc.or;
360 
361 		last_offset = off;
362 	}
363 
364 out:
365 	msm_gem_put_vaddr(&obj->base);
366 
367 	return ret;
368 }
369 
370 static void submit_cleanup(struct msm_gem_submit *submit)
371 {
372 	unsigned i;
373 
374 	for (i = 0; i < submit->nr_bos; i++) {
375 		struct msm_gem_object *msm_obj = submit->bos[i].obj;
376 		submit_unlock_unpin_bo(submit, i);
377 		list_del_init(&msm_obj->submit_entry);
378 		drm_gem_object_unreference(&msm_obj->base);
379 	}
380 
381 	ww_acquire_fini(&submit->ticket);
382 }
383 
384 int msm_ioctl_gem_submit(struct drm_device *dev, void *data,
385 		struct drm_file *file)
386 {
387 	struct msm_drm_private *priv = dev->dev_private;
388 	struct drm_msm_gem_submit *args = data;
389 	struct msm_file_private *ctx = file->driver_priv;
390 	struct msm_gem_submit *submit;
391 	struct msm_gpu *gpu = priv->gpu;
392 	struct dma_fence *in_fence = NULL;
393 	struct sync_file *sync_file = NULL;
394 	int out_fence_fd = -1;
395 	unsigned i;
396 	int ret;
397 
398 	if (!gpu)
399 		return -ENXIO;
400 
401 	/* for now, we just have 3d pipe.. eventually this would need to
402 	 * be more clever to dispatch to appropriate gpu module:
403 	 */
404 	if (MSM_PIPE_ID(args->flags) != MSM_PIPE_3D0)
405 		return -EINVAL;
406 
407 	if (MSM_PIPE_FLAGS(args->flags) & ~MSM_SUBMIT_FLAGS)
408 		return -EINVAL;
409 
410 	if (args->flags & MSM_SUBMIT_FENCE_FD_IN) {
411 		in_fence = sync_file_get_fence(args->fence_fd);
412 
413 		if (!in_fence)
414 			return -EINVAL;
415 
416 		/*
417 		 * Wait if the fence is from a foreign context, or if the fence
418 		 * array contains any fence from a foreign context.
419 		 */
420 		if (!dma_fence_match_context(in_fence, gpu->fctx->context)) {
421 			ret = dma_fence_wait(in_fence, true);
422 			if (ret)
423 				return ret;
424 		}
425 	}
426 
427 	ret = mutex_lock_interruptible(&dev->struct_mutex);
428 	if (ret)
429 		return ret;
430 
431 	if (args->flags & MSM_SUBMIT_FENCE_FD_OUT) {
432 		out_fence_fd = get_unused_fd_flags(O_CLOEXEC);
433 		if (out_fence_fd < 0) {
434 			ret = out_fence_fd;
435 			goto out_unlock;
436 		}
437 	}
438 	priv->struct_mutex_task = current;
439 
440 	submit = submit_create(dev, gpu, args->nr_bos, args->nr_cmds);
441 	if (!submit) {
442 		ret = -ENOMEM;
443 		goto out_unlock;
444 	}
445 
446 	ret = submit_lookup_objects(submit, args, file);
447 	if (ret)
448 		goto out;
449 
450 	ret = submit_lock_objects(submit);
451 	if (ret)
452 		goto out;
453 
454 	if (!(args->fence & MSM_SUBMIT_NO_IMPLICIT)) {
455 		ret = submit_fence_sync(submit);
456 		if (ret)
457 			goto out;
458 	}
459 
460 	ret = submit_pin_objects(submit);
461 	if (ret)
462 		goto out;
463 
464 	for (i = 0; i < args->nr_cmds; i++) {
465 		struct drm_msm_gem_submit_cmd submit_cmd;
466 		void __user *userptr =
467 			u64_to_user_ptr(args->cmds + (i * sizeof(submit_cmd)));
468 		struct msm_gem_object *msm_obj;
469 		uint64_t iova;
470 
471 		ret = copy_from_user(&submit_cmd, userptr, sizeof(submit_cmd));
472 		if (ret) {
473 			ret = -EFAULT;
474 			goto out;
475 		}
476 
477 		/* validate input from userspace: */
478 		switch (submit_cmd.type) {
479 		case MSM_SUBMIT_CMD_BUF:
480 		case MSM_SUBMIT_CMD_IB_TARGET_BUF:
481 		case MSM_SUBMIT_CMD_CTX_RESTORE_BUF:
482 			break;
483 		default:
484 			DRM_ERROR("invalid type: %08x\n", submit_cmd.type);
485 			ret = -EINVAL;
486 			goto out;
487 		}
488 
489 		ret = submit_bo(submit, submit_cmd.submit_idx,
490 				&msm_obj, &iova, NULL);
491 		if (ret)
492 			goto out;
493 
494 		if (submit_cmd.size % 4) {
495 			DRM_ERROR("non-aligned cmdstream buffer size: %u\n",
496 					submit_cmd.size);
497 			ret = -EINVAL;
498 			goto out;
499 		}
500 
501 		if (!submit_cmd.size ||
502 			((submit_cmd.size + submit_cmd.submit_offset) >
503 				msm_obj->base.size)) {
504 			DRM_ERROR("invalid cmdstream size: %u\n", submit_cmd.size);
505 			ret = -EINVAL;
506 			goto out;
507 		}
508 
509 		submit->cmd[i].type = submit_cmd.type;
510 		submit->cmd[i].size = submit_cmd.size / 4;
511 		submit->cmd[i].iova = iova + submit_cmd.submit_offset;
512 		submit->cmd[i].idx  = submit_cmd.submit_idx;
513 
514 		if (submit->valid)
515 			continue;
516 
517 		ret = submit_reloc(submit, msm_obj, submit_cmd.submit_offset,
518 				submit_cmd.nr_relocs, submit_cmd.relocs);
519 		if (ret)
520 			goto out;
521 	}
522 
523 	submit->nr_cmds = i;
524 
525 	submit->fence = msm_fence_alloc(gpu->fctx);
526 	if (IS_ERR(submit->fence)) {
527 		ret = PTR_ERR(submit->fence);
528 		submit->fence = NULL;
529 		goto out;
530 	}
531 
532 	if (args->flags & MSM_SUBMIT_FENCE_FD_OUT) {
533 		sync_file = sync_file_create(submit->fence);
534 		if (!sync_file) {
535 			ret = -ENOMEM;
536 			goto out;
537 		}
538 	}
539 
540 	msm_gpu_submit(gpu, submit, ctx);
541 
542 	args->fence = submit->fence->seqno;
543 
544 	if (args->flags & MSM_SUBMIT_FENCE_FD_OUT) {
545 		fd_install(out_fence_fd, sync_file->file);
546 		args->fence_fd = out_fence_fd;
547 	}
548 
549 out:
550 	if (in_fence)
551 		dma_fence_put(in_fence);
552 	submit_cleanup(submit);
553 	if (ret)
554 		msm_gem_submit_free(submit);
555 out_unlock:
556 	if (ret && (out_fence_fd >= 0))
557 		put_unused_fd(out_fence_fd);
558 	priv->struct_mutex_task = NULL;
559 	mutex_unlock(&dev->struct_mutex);
560 	return ret;
561 }
562