• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
submit_create(struct drm_device * dev,struct msm_gpu * gpu,uint32_t nr_bos,uint32_t nr_cmds)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_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 
msm_gem_submit_free(struct msm_gem_submit * submit)64 void msm_gem_submit_free(struct msm_gem_submit *submit)
65 {
66 	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
copy_from_user_inatomic(void * to,const void __user * from,unsigned long n)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 
submit_lookup_objects(struct msm_gem_submit * submit,struct drm_msm_gem_submit * args,struct drm_file * file)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 		ret = copy_from_user_inatomic(&submit_bo, userptr, sizeof(submit_bo));
102 		if (unlikely(ret)) {
103 			pagefault_enable();
104 			spin_unlock(&file->table_lock);
105 			ret = copy_from_user(&submit_bo, userptr, sizeof(submit_bo));
106 			if (ret)
107 				goto out;
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 
submit_unlock_unpin_bo(struct msm_gem_submit * submit,int i)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->id);
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: */
submit_lock_objects(struct msm_gem_submit * submit)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 
submit_fence_sync(struct msm_gem_submit * submit)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 
submit_pin_objects(struct msm_gem_submit * submit)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 		uint32_t iova;
249 
250 		/* if locking succeeded, pin bo: */
251 		ret = msm_gem_get_iova_locked(&msm_obj->base,
252 				submit->gpu->id, &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 
submit_bo(struct msm_gem_submit * submit,uint32_t idx,struct msm_gem_object ** obj,uint32_t * iova,bool * valid)272 static int submit_bo(struct msm_gem_submit *submit, uint32_t idx,
273 		struct msm_gem_object **obj, uint32_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: */
submit_reloc(struct msm_gem_submit * submit,struct msm_gem_object * obj,uint32_t offset,uint32_t nr_relocs,uint64_t relocs)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_locked(&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 iova, off;
320 		bool valid;
321 
322 		ret = copy_from_user(&submit_reloc, userptr, sizeof(submit_reloc));
323 		if (ret)
324 			goto out;
325 
326 		if (submit_reloc.submit_offset % 4) {
327 			DRM_ERROR("non-aligned reloc offset: %u\n",
328 					submit_reloc.submit_offset);
329 			ret = -EINVAL;
330 			goto out;
331 		}
332 
333 		/* offset in dwords: */
334 		off = submit_reloc.submit_offset / 4;
335 
336 		if ((off >= (obj->base.size / 4)) ||
337 				(off < last_offset)) {
338 			DRM_ERROR("invalid offset %u at reloc %u\n", off, i);
339 			ret = -EINVAL;
340 			goto out;
341 		}
342 
343 		ret = submit_bo(submit, submit_reloc.reloc_idx, NULL, &iova, &valid);
344 		if (ret)
345 			goto out;
346 
347 		if (valid)
348 			continue;
349 
350 		iova += submit_reloc.reloc_offset;
351 
352 		if (submit_reloc.shift < 0)
353 			iova >>= -submit_reloc.shift;
354 		else
355 			iova <<= submit_reloc.shift;
356 
357 		ptr[off] = iova | submit_reloc.or;
358 
359 		last_offset = off;
360 	}
361 
362 out:
363 	msm_gem_put_vaddr_locked(&obj->base);
364 
365 	return ret;
366 }
367 
submit_cleanup(struct msm_gem_submit * submit)368 static void submit_cleanup(struct msm_gem_submit *submit)
369 {
370 	unsigned i;
371 
372 	for (i = 0; i < submit->nr_bos; i++) {
373 		struct msm_gem_object *msm_obj = submit->bos[i].obj;
374 		submit_unlock_unpin_bo(submit, i);
375 		list_del_init(&msm_obj->submit_entry);
376 		drm_gem_object_unreference(&msm_obj->base);
377 	}
378 
379 	ww_acquire_fini(&submit->ticket);
380 }
381 
msm_ioctl_gem_submit(struct drm_device * dev,void * data,struct drm_file * file)382 int msm_ioctl_gem_submit(struct drm_device *dev, void *data,
383 		struct drm_file *file)
384 {
385 	struct msm_drm_private *priv = dev->dev_private;
386 	struct drm_msm_gem_submit *args = data;
387 	struct msm_file_private *ctx = file->driver_priv;
388 	struct msm_gem_submit *submit;
389 	struct msm_gpu *gpu = priv->gpu;
390 	struct fence *in_fence = NULL;
391 	struct sync_file *sync_file = NULL;
392 	int out_fence_fd = -1;
393 	unsigned i;
394 	int ret;
395 
396 	if (!gpu)
397 		return -ENXIO;
398 
399 	/* for now, we just have 3d pipe.. eventually this would need to
400 	 * be more clever to dispatch to appropriate gpu module:
401 	 */
402 	if (MSM_PIPE_ID(args->flags) != MSM_PIPE_3D0)
403 		return -EINVAL;
404 
405 	if (MSM_PIPE_FLAGS(args->flags) & ~MSM_SUBMIT_FLAGS)
406 		return -EINVAL;
407 
408 	ret = mutex_lock_interruptible(&dev->struct_mutex);
409 	if (ret)
410 		return ret;
411 
412 	if (args->flags & MSM_SUBMIT_FENCE_FD_OUT) {
413 		out_fence_fd = get_unused_fd_flags(O_CLOEXEC);
414 		if (out_fence_fd < 0) {
415 			ret = out_fence_fd;
416 			goto out_unlock;
417 		}
418 	}
419 	priv->struct_mutex_task = current;
420 
421 	submit = submit_create(dev, gpu, args->nr_bos, args->nr_cmds);
422 	if (!submit) {
423 		ret = -ENOMEM;
424 		goto out_unlock;
425 	}
426 
427 	ret = submit_lookup_objects(submit, args, file);
428 	if (ret)
429 		goto out;
430 
431 	ret = submit_lock_objects(submit);
432 	if (ret)
433 		goto out;
434 
435 	if (args->flags & MSM_SUBMIT_FENCE_FD_IN) {
436 		in_fence = sync_file_get_fence(args->fence_fd);
437 
438 		if (!in_fence) {
439 			ret = -EINVAL;
440 			goto out;
441 		}
442 
443 		/* TODO if we get an array-fence due to userspace merging multiple
444 		 * fences, we need a way to determine if all the backing fences
445 		 * are from our own context..
446 		 */
447 
448 		if (in_fence->context != gpu->fctx->context) {
449 			ret = fence_wait(in_fence, true);
450 			if (ret)
451 				goto out;
452 		}
453 
454 	}
455 
456 	if (!(args->fence & MSM_SUBMIT_NO_IMPLICIT)) {
457 		ret = submit_fence_sync(submit);
458 		if (ret)
459 			goto out;
460 	}
461 
462 	ret = submit_pin_objects(submit);
463 	if (ret)
464 		goto out;
465 
466 	for (i = 0; i < args->nr_cmds; i++) {
467 		struct drm_msm_gem_submit_cmd submit_cmd;
468 		void __user *userptr =
469 			u64_to_user_ptr(args->cmds + (i * sizeof(submit_cmd)));
470 		struct msm_gem_object *msm_obj;
471 		uint32_t iova;
472 
473 		ret = copy_from_user(&submit_cmd, userptr, sizeof(submit_cmd));
474 		if (ret) {
475 			ret = -EFAULT;
476 			goto out;
477 		}
478 
479 		/* validate input from userspace: */
480 		switch (submit_cmd.type) {
481 		case MSM_SUBMIT_CMD_BUF:
482 		case MSM_SUBMIT_CMD_IB_TARGET_BUF:
483 		case MSM_SUBMIT_CMD_CTX_RESTORE_BUF:
484 			break;
485 		default:
486 			DRM_ERROR("invalid type: %08x\n", submit_cmd.type);
487 			ret = -EINVAL;
488 			goto out;
489 		}
490 
491 		ret = submit_bo(submit, submit_cmd.submit_idx,
492 				&msm_obj, &iova, NULL);
493 		if (ret)
494 			goto out;
495 
496 		if (submit_cmd.size % 4) {
497 			DRM_ERROR("non-aligned cmdstream buffer size: %u\n",
498 					submit_cmd.size);
499 			ret = -EINVAL;
500 			goto out;
501 		}
502 
503 		if ((submit_cmd.size + submit_cmd.submit_offset) >=
504 				msm_obj->base.size) {
505 			DRM_ERROR("invalid cmdstream size: %u\n", submit_cmd.size);
506 			ret = -EINVAL;
507 			goto out;
508 		}
509 
510 		submit->cmd[i].type = submit_cmd.type;
511 		submit->cmd[i].size = submit_cmd.size / 4;
512 		submit->cmd[i].iova = iova + submit_cmd.submit_offset;
513 		submit->cmd[i].idx  = submit_cmd.submit_idx;
514 
515 		if (submit->valid)
516 			continue;
517 
518 		ret = submit_reloc(submit, msm_obj, submit_cmd.submit_offset,
519 				submit_cmd.nr_relocs, submit_cmd.relocs);
520 		if (ret)
521 			goto out;
522 	}
523 
524 	submit->nr_cmds = i;
525 
526 	submit->fence = msm_fence_alloc(gpu->fctx);
527 	if (IS_ERR(submit->fence)) {
528 		ret = PTR_ERR(submit->fence);
529 		submit->fence = NULL;
530 		goto out;
531 	}
532 
533 	if (args->flags & MSM_SUBMIT_FENCE_FD_OUT) {
534 		sync_file = sync_file_create(submit->fence);
535 		if (!sync_file) {
536 			ret = -ENOMEM;
537 			goto out;
538 		}
539 	}
540 
541 	msm_gpu_submit(gpu, submit, ctx);
542 
543 	args->fence = submit->fence->seqno;
544 
545 	if (args->flags & MSM_SUBMIT_FENCE_FD_OUT) {
546 		fd_install(out_fence_fd, sync_file->file);
547 		args->fence_fd = out_fence_fd;
548 	}
549 
550 out:
551 	if (in_fence)
552 		fence_put(in_fence);
553 	submit_cleanup(submit);
554 	if (ret)
555 		msm_gem_submit_free(submit);
556 out_unlock:
557 	if (ret && (out_fence_fd >= 0))
558 		put_unused_fd(out_fence_fd);
559 	priv->struct_mutex_task = NULL;
560 	mutex_unlock(&dev->struct_mutex);
561 	return ret;
562 }
563