xref: /openbmc/linux/drivers/gpu/drm/msm/msm_gem_submit.c (revision b24413180f5600bcb3bb70fbed5cf186b60864bd)
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) + ((u64)nr_bos * sizeof(submit->bos[0])) +
38 		((u64)nr_cmds * sizeof(submit->cmd[0]));
39 
40 	if (sz > SIZE_MAX)
41 		return NULL;
42 
43 	submit = kmalloc(sz, GFP_KERNEL | __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, bool no_implicit)
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 		if (!write) {
233 			/* NOTE: _reserve_shared() must happen before
234 			 * _add_shared_fence(), which makes this a slightly
235 			 * strange place to call it.  OTOH this is a
236 			 * convenient can-fail point to hook it in.
237 			 */
238 			ret = reservation_object_reserve_shared(msm_obj->resv);
239 			if (ret)
240 				return ret;
241 		}
242 
243 		if (no_implicit)
244 			continue;
245 
246 		ret = msm_gem_sync_object(&msm_obj->base, submit->gpu->fctx, write);
247 		if (ret)
248 			break;
249 	}
250 
251 	return ret;
252 }
253 
254 static int submit_pin_objects(struct msm_gem_submit *submit)
255 {
256 	int i, ret = 0;
257 
258 	submit->valid = true;
259 
260 	for (i = 0; i < submit->nr_bos; i++) {
261 		struct msm_gem_object *msm_obj = submit->bos[i].obj;
262 		uint64_t iova;
263 
264 		/* if locking succeeded, pin bo: */
265 		ret = msm_gem_get_iova(&msm_obj->base,
266 				submit->gpu->aspace, &iova);
267 
268 		if (ret)
269 			break;
270 
271 		submit->bos[i].flags |= BO_PINNED;
272 
273 		if (iova == submit->bos[i].iova) {
274 			submit->bos[i].flags |= BO_VALID;
275 		} else {
276 			submit->bos[i].iova = iova;
277 			/* iova changed, so address in cmdstream is not valid: */
278 			submit->bos[i].flags &= ~BO_VALID;
279 			submit->valid = false;
280 		}
281 	}
282 
283 	return ret;
284 }
285 
286 static int submit_bo(struct msm_gem_submit *submit, uint32_t idx,
287 		struct msm_gem_object **obj, uint64_t *iova, bool *valid)
288 {
289 	if (idx >= submit->nr_bos) {
290 		DRM_ERROR("invalid buffer index: %u (out of %u)\n",
291 				idx, submit->nr_bos);
292 		return -EINVAL;
293 	}
294 
295 	if (obj)
296 		*obj = submit->bos[idx].obj;
297 	if (iova)
298 		*iova = submit->bos[idx].iova;
299 	if (valid)
300 		*valid = !!(submit->bos[idx].flags & BO_VALID);
301 
302 	return 0;
303 }
304 
305 /* process the reloc's and patch up the cmdstream as needed: */
306 static int submit_reloc(struct msm_gem_submit *submit, struct msm_gem_object *obj,
307 		uint32_t offset, uint32_t nr_relocs, uint64_t relocs)
308 {
309 	uint32_t i, last_offset = 0;
310 	uint32_t *ptr;
311 	int ret = 0;
312 
313 	if (offset % 4) {
314 		DRM_ERROR("non-aligned cmdstream buffer: %u\n", offset);
315 		return -EINVAL;
316 	}
317 
318 	/* For now, just map the entire thing.  Eventually we probably
319 	 * to do it page-by-page, w/ kmap() if not vmap()d..
320 	 */
321 	ptr = msm_gem_get_vaddr(&obj->base);
322 
323 	if (IS_ERR(ptr)) {
324 		ret = PTR_ERR(ptr);
325 		DBG("failed to map: %d", ret);
326 		return ret;
327 	}
328 
329 	for (i = 0; i < nr_relocs; i++) {
330 		struct drm_msm_gem_submit_reloc submit_reloc;
331 		void __user *userptr =
332 			u64_to_user_ptr(relocs + (i * sizeof(submit_reloc)));
333 		uint32_t off;
334 		uint64_t iova;
335 		bool valid;
336 
337 		if (copy_from_user(&submit_reloc, userptr, sizeof(submit_reloc))) {
338 			ret = -EFAULT;
339 			goto out;
340 		}
341 
342 		if (submit_reloc.submit_offset % 4) {
343 			DRM_ERROR("non-aligned reloc offset: %u\n",
344 					submit_reloc.submit_offset);
345 			ret = -EINVAL;
346 			goto out;
347 		}
348 
349 		/* offset in dwords: */
350 		off = submit_reloc.submit_offset / 4;
351 
352 		if ((off >= (obj->base.size / 4)) ||
353 				(off < last_offset)) {
354 			DRM_ERROR("invalid offset %u at reloc %u\n", off, i);
355 			ret = -EINVAL;
356 			goto out;
357 		}
358 
359 		ret = submit_bo(submit, submit_reloc.reloc_idx, NULL, &iova, &valid);
360 		if (ret)
361 			goto out;
362 
363 		if (valid)
364 			continue;
365 
366 		iova += submit_reloc.reloc_offset;
367 
368 		if (submit_reloc.shift < 0)
369 			iova >>= -submit_reloc.shift;
370 		else
371 			iova <<= submit_reloc.shift;
372 
373 		ptr[off] = iova | submit_reloc.or;
374 
375 		last_offset = off;
376 	}
377 
378 out:
379 	msm_gem_put_vaddr(&obj->base);
380 
381 	return ret;
382 }
383 
384 static void submit_cleanup(struct msm_gem_submit *submit)
385 {
386 	unsigned i;
387 
388 	for (i = 0; i < submit->nr_bos; i++) {
389 		struct msm_gem_object *msm_obj = submit->bos[i].obj;
390 		submit_unlock_unpin_bo(submit, i);
391 		list_del_init(&msm_obj->submit_entry);
392 		drm_gem_object_unreference(&msm_obj->base);
393 	}
394 
395 	ww_acquire_fini(&submit->ticket);
396 }
397 
398 int msm_ioctl_gem_submit(struct drm_device *dev, void *data,
399 		struct drm_file *file)
400 {
401 	struct msm_drm_private *priv = dev->dev_private;
402 	struct drm_msm_gem_submit *args = data;
403 	struct msm_file_private *ctx = file->driver_priv;
404 	struct msm_gem_submit *submit;
405 	struct msm_gpu *gpu = priv->gpu;
406 	struct dma_fence *in_fence = NULL;
407 	struct sync_file *sync_file = NULL;
408 	int out_fence_fd = -1;
409 	unsigned i;
410 	int ret;
411 
412 	if (!gpu)
413 		return -ENXIO;
414 
415 	/* for now, we just have 3d pipe.. eventually this would need to
416 	 * be more clever to dispatch to appropriate gpu module:
417 	 */
418 	if (MSM_PIPE_ID(args->flags) != MSM_PIPE_3D0)
419 		return -EINVAL;
420 
421 	if (MSM_PIPE_FLAGS(args->flags) & ~MSM_SUBMIT_FLAGS)
422 		return -EINVAL;
423 
424 	if (args->flags & MSM_SUBMIT_FENCE_FD_IN) {
425 		in_fence = sync_file_get_fence(args->fence_fd);
426 
427 		if (!in_fence)
428 			return -EINVAL;
429 
430 		/*
431 		 * Wait if the fence is from a foreign context, or if the fence
432 		 * array contains any fence from a foreign context.
433 		 */
434 		if (!dma_fence_match_context(in_fence, gpu->fctx->context)) {
435 			ret = dma_fence_wait(in_fence, true);
436 			if (ret)
437 				return ret;
438 		}
439 	}
440 
441 	ret = mutex_lock_interruptible(&dev->struct_mutex);
442 	if (ret)
443 		return ret;
444 
445 	if (args->flags & MSM_SUBMIT_FENCE_FD_OUT) {
446 		out_fence_fd = get_unused_fd_flags(O_CLOEXEC);
447 		if (out_fence_fd < 0) {
448 			ret = out_fence_fd;
449 			goto out_unlock;
450 		}
451 	}
452 	priv->struct_mutex_task = current;
453 
454 	submit = submit_create(dev, gpu, args->nr_bos, args->nr_cmds);
455 	if (!submit) {
456 		ret = -ENOMEM;
457 		goto out_unlock;
458 	}
459 
460 	ret = submit_lookup_objects(submit, args, file);
461 	if (ret)
462 		goto out;
463 
464 	ret = submit_lock_objects(submit);
465 	if (ret)
466 		goto out;
467 
468 	ret = submit_fence_sync(submit, !!(args->flags & MSM_SUBMIT_NO_IMPLICIT));
469 	if (ret)
470 		goto out;
471 
472 	ret = submit_pin_objects(submit);
473 	if (ret)
474 		goto out;
475 
476 	for (i = 0; i < args->nr_cmds; i++) {
477 		struct drm_msm_gem_submit_cmd submit_cmd;
478 		void __user *userptr =
479 			u64_to_user_ptr(args->cmds + (i * sizeof(submit_cmd)));
480 		struct msm_gem_object *msm_obj;
481 		uint64_t iova;
482 
483 		ret = copy_from_user(&submit_cmd, userptr, sizeof(submit_cmd));
484 		if (ret) {
485 			ret = -EFAULT;
486 			goto out;
487 		}
488 
489 		/* validate input from userspace: */
490 		switch (submit_cmd.type) {
491 		case MSM_SUBMIT_CMD_BUF:
492 		case MSM_SUBMIT_CMD_IB_TARGET_BUF:
493 		case MSM_SUBMIT_CMD_CTX_RESTORE_BUF:
494 			break;
495 		default:
496 			DRM_ERROR("invalid type: %08x\n", submit_cmd.type);
497 			ret = -EINVAL;
498 			goto out;
499 		}
500 
501 		ret = submit_bo(submit, submit_cmd.submit_idx,
502 				&msm_obj, &iova, NULL);
503 		if (ret)
504 			goto out;
505 
506 		if (submit_cmd.size % 4) {
507 			DRM_ERROR("non-aligned cmdstream buffer size: %u\n",
508 					submit_cmd.size);
509 			ret = -EINVAL;
510 			goto out;
511 		}
512 
513 		if (!submit_cmd.size ||
514 			((submit_cmd.size + submit_cmd.submit_offset) >
515 				msm_obj->base.size)) {
516 			DRM_ERROR("invalid cmdstream size: %u\n", submit_cmd.size);
517 			ret = -EINVAL;
518 			goto out;
519 		}
520 
521 		submit->cmd[i].type = submit_cmd.type;
522 		submit->cmd[i].size = submit_cmd.size / 4;
523 		submit->cmd[i].iova = iova + submit_cmd.submit_offset;
524 		submit->cmd[i].idx  = submit_cmd.submit_idx;
525 
526 		if (submit->valid)
527 			continue;
528 
529 		ret = submit_reloc(submit, msm_obj, submit_cmd.submit_offset,
530 				submit_cmd.nr_relocs, submit_cmd.relocs);
531 		if (ret)
532 			goto out;
533 	}
534 
535 	submit->nr_cmds = i;
536 
537 	submit->fence = msm_fence_alloc(gpu->fctx);
538 	if (IS_ERR(submit->fence)) {
539 		ret = PTR_ERR(submit->fence);
540 		submit->fence = NULL;
541 		goto out;
542 	}
543 
544 	if (args->flags & MSM_SUBMIT_FENCE_FD_OUT) {
545 		sync_file = sync_file_create(submit->fence);
546 		if (!sync_file) {
547 			ret = -ENOMEM;
548 			goto out;
549 		}
550 	}
551 
552 	msm_gpu_submit(gpu, submit, ctx);
553 
554 	args->fence = submit->fence->seqno;
555 
556 	if (args->flags & MSM_SUBMIT_FENCE_FD_OUT) {
557 		fd_install(out_fence_fd, sync_file->file);
558 		args->fence_fd = out_fence_fd;
559 	}
560 
561 out:
562 	if (in_fence)
563 		dma_fence_put(in_fence);
564 	submit_cleanup(submit);
565 	if (ret)
566 		msm_gem_submit_free(submit);
567 out_unlock:
568 	if (ret && (out_fence_fd >= 0))
569 		put_unused_fd(out_fence_fd);
570 	priv->struct_mutex_task = NULL;
571 	mutex_unlock(&dev->struct_mutex);
572 	return ret;
573 }
574