xref: /linux/drivers/gpu/drm/msm/msm_gem_submit.c (revision 0883c2c06fb5bcf5b9e008270827e63c09a88c1e)
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 "msm_drv.h"
19 #include "msm_gpu.h"
20 #include "msm_gem.h"
21 
22 /*
23  * Cmdstream submission:
24  */
25 
26 /* make sure these don't conflict w/ MSM_SUBMIT_BO_x */
27 #define BO_VALID    0x8000   /* is current addr in cmdstream correct/valid? */
28 #define BO_LOCKED   0x4000
29 #define BO_PINNED   0x2000
30 
31 static struct msm_gem_submit *submit_create(struct drm_device *dev,
32 		struct msm_gpu *gpu, int nr)
33 {
34 	struct msm_gem_submit *submit;
35 	int sz = sizeof(*submit) + (nr * sizeof(submit->bos[0]));
36 
37 	submit = kmalloc(sz, GFP_TEMPORARY | __GFP_NOWARN | __GFP_NORETRY);
38 	if (!submit)
39 		return NULL;
40 
41 	submit->dev = dev;
42 	submit->gpu = gpu;
43 	submit->fence = NULL;
44 	submit->pid = get_pid(task_pid(current));
45 
46 	/* initially, until copy_from_user() and bo lookup succeeds: */
47 	submit->nr_bos = 0;
48 	submit->nr_cmds = 0;
49 
50 	INIT_LIST_HEAD(&submit->node);
51 	INIT_LIST_HEAD(&submit->bo_list);
52 	ww_acquire_init(&submit->ticket, &reservation_ww_class);
53 
54 	return submit;
55 }
56 
57 void msm_gem_submit_free(struct msm_gem_submit *submit)
58 {
59 	fence_put(submit->fence);
60 	list_del(&submit->node);
61 	put_pid(submit->pid);
62 	kfree(submit);
63 }
64 
65 static int submit_lookup_objects(struct msm_gem_submit *submit,
66 		struct drm_msm_gem_submit *args, struct drm_file *file)
67 {
68 	unsigned i;
69 	int ret = 0;
70 
71 	spin_lock(&file->table_lock);
72 
73 	for (i = 0; i < args->nr_bos; i++) {
74 		struct drm_msm_gem_submit_bo submit_bo;
75 		struct drm_gem_object *obj;
76 		struct msm_gem_object *msm_obj;
77 		void __user *userptr =
78 			u64_to_user_ptr(args->bos + (i * sizeof(submit_bo)));
79 
80 		/* make sure we don't have garbage flags, in case we hit
81 		 * error path before flags is initialized:
82 		 */
83 		submit->bos[i].flags = 0;
84 
85 		ret = copy_from_user(&submit_bo, userptr, sizeof(submit_bo));
86 		if (ret) {
87 			ret = -EFAULT;
88 			goto out_unlock;
89 		}
90 
91 		if (submit_bo.flags & ~MSM_SUBMIT_BO_FLAGS) {
92 			DRM_ERROR("invalid flags: %x\n", submit_bo.flags);
93 			ret = -EINVAL;
94 			goto out_unlock;
95 		}
96 
97 		submit->bos[i].flags = submit_bo.flags;
98 		/* in validate_objects() we figure out if this is true: */
99 		submit->bos[i].iova  = submit_bo.presumed;
100 
101 		/* normally use drm_gem_object_lookup(), but for bulk lookup
102 		 * all under single table_lock just hit object_idr directly:
103 		 */
104 		obj = idr_find(&file->object_idr, submit_bo.handle);
105 		if (!obj) {
106 			DRM_ERROR("invalid handle %u at index %u\n", submit_bo.handle, i);
107 			ret = -EINVAL;
108 			goto out_unlock;
109 		}
110 
111 		msm_obj = to_msm_bo(obj);
112 
113 		if (!list_empty(&msm_obj->submit_entry)) {
114 			DRM_ERROR("handle %u at index %u already on submit list\n",
115 					submit_bo.handle, i);
116 			ret = -EINVAL;
117 			goto out_unlock;
118 		}
119 
120 		drm_gem_object_reference(obj);
121 
122 		submit->bos[i].obj = msm_obj;
123 
124 		list_add_tail(&msm_obj->submit_entry, &submit->bo_list);
125 	}
126 
127 out_unlock:
128 	submit->nr_bos = i;
129 	spin_unlock(&file->table_lock);
130 
131 	return ret;
132 }
133 
134 static void submit_unlock_unpin_bo(struct msm_gem_submit *submit, int i)
135 {
136 	struct msm_gem_object *msm_obj = submit->bos[i].obj;
137 
138 	if (submit->bos[i].flags & BO_PINNED)
139 		msm_gem_put_iova(&msm_obj->base, submit->gpu->id);
140 
141 	if (submit->bos[i].flags & BO_LOCKED)
142 		ww_mutex_unlock(&msm_obj->resv->lock);
143 
144 	if (!(submit->bos[i].flags & BO_VALID))
145 		submit->bos[i].iova = 0;
146 
147 	submit->bos[i].flags &= ~(BO_LOCKED | BO_PINNED);
148 }
149 
150 /* This is where we make sure all the bo's are reserved and pin'd: */
151 static int submit_lock_objects(struct msm_gem_submit *submit)
152 {
153 	int contended, slow_locked = -1, i, ret = 0;
154 
155 retry:
156 	for (i = 0; i < submit->nr_bos; i++) {
157 		struct msm_gem_object *msm_obj = submit->bos[i].obj;
158 
159 		if (slow_locked == i)
160 			slow_locked = -1;
161 
162 		contended = i;
163 
164 		if (!(submit->bos[i].flags & BO_LOCKED)) {
165 			ret = ww_mutex_lock_interruptible(&msm_obj->resv->lock,
166 					&submit->ticket);
167 			if (ret)
168 				goto fail;
169 			submit->bos[i].flags |= BO_LOCKED;
170 		}
171 	}
172 
173 	ww_acquire_done(&submit->ticket);
174 
175 	return 0;
176 
177 fail:
178 	for (; i >= 0; i--)
179 		submit_unlock_unpin_bo(submit, i);
180 
181 	if (slow_locked > 0)
182 		submit_unlock_unpin_bo(submit, slow_locked);
183 
184 	if (ret == -EDEADLK) {
185 		struct msm_gem_object *msm_obj = submit->bos[contended].obj;
186 		/* we lost out in a seqno race, lock and retry.. */
187 		ret = ww_mutex_lock_slow_interruptible(&msm_obj->resv->lock,
188 				&submit->ticket);
189 		if (!ret) {
190 			submit->bos[contended].flags |= BO_LOCKED;
191 			slow_locked = contended;
192 			goto retry;
193 		}
194 	}
195 
196 	return ret;
197 }
198 
199 static int submit_fence_sync(struct msm_gem_submit *submit)
200 {
201 	int i, ret = 0;
202 
203 	for (i = 0; i < submit->nr_bos; i++) {
204 		struct msm_gem_object *msm_obj = submit->bos[i].obj;
205 		bool write = submit->bos[i].flags & MSM_SUBMIT_BO_WRITE;
206 
207 		ret = msm_gem_sync_object(&msm_obj->base, submit->gpu->fctx, write);
208 		if (ret)
209 			break;
210 	}
211 
212 	return ret;
213 }
214 
215 static int submit_pin_objects(struct msm_gem_submit *submit)
216 {
217 	int i, ret = 0;
218 
219 	submit->valid = true;
220 
221 	for (i = 0; i < submit->nr_bos; i++) {
222 		struct msm_gem_object *msm_obj = submit->bos[i].obj;
223 		uint32_t iova;
224 
225 		/* if locking succeeded, pin bo: */
226 		ret = msm_gem_get_iova_locked(&msm_obj->base,
227 				submit->gpu->id, &iova);
228 
229 		if (ret)
230 			break;
231 
232 		submit->bos[i].flags |= BO_PINNED;
233 
234 		if (iova == submit->bos[i].iova) {
235 			submit->bos[i].flags |= BO_VALID;
236 		} else {
237 			submit->bos[i].iova = iova;
238 			/* iova changed, so address in cmdstream is not valid: */
239 			submit->bos[i].flags &= ~BO_VALID;
240 			submit->valid = false;
241 		}
242 	}
243 
244 	return ret;
245 }
246 
247 static int submit_bo(struct msm_gem_submit *submit, uint32_t idx,
248 		struct msm_gem_object **obj, uint32_t *iova, bool *valid)
249 {
250 	if (idx >= submit->nr_bos) {
251 		DRM_ERROR("invalid buffer index: %u (out of %u)\n",
252 				idx, submit->nr_bos);
253 		return -EINVAL;
254 	}
255 
256 	if (obj)
257 		*obj = submit->bos[idx].obj;
258 	if (iova)
259 		*iova = submit->bos[idx].iova;
260 	if (valid)
261 		*valid = !!(submit->bos[idx].flags & BO_VALID);
262 
263 	return 0;
264 }
265 
266 /* process the reloc's and patch up the cmdstream as needed: */
267 static int submit_reloc(struct msm_gem_submit *submit, struct msm_gem_object *obj,
268 		uint32_t offset, uint32_t nr_relocs, uint64_t relocs)
269 {
270 	uint32_t i, last_offset = 0;
271 	uint32_t *ptr;
272 	int ret;
273 
274 	if (offset % 4) {
275 		DRM_ERROR("non-aligned cmdstream buffer: %u\n", offset);
276 		return -EINVAL;
277 	}
278 
279 	/* For now, just map the entire thing.  Eventually we probably
280 	 * to do it page-by-page, w/ kmap() if not vmap()d..
281 	 */
282 	ptr = msm_gem_vaddr_locked(&obj->base);
283 
284 	if (IS_ERR(ptr)) {
285 		ret = PTR_ERR(ptr);
286 		DBG("failed to map: %d", ret);
287 		return ret;
288 	}
289 
290 	for (i = 0; i < nr_relocs; i++) {
291 		struct drm_msm_gem_submit_reloc submit_reloc;
292 		void __user *userptr =
293 			u64_to_user_ptr(relocs + (i * sizeof(submit_reloc)));
294 		uint32_t iova, off;
295 		bool valid;
296 
297 		ret = copy_from_user(&submit_reloc, userptr, sizeof(submit_reloc));
298 		if (ret)
299 			return -EFAULT;
300 
301 		if (submit_reloc.submit_offset % 4) {
302 			DRM_ERROR("non-aligned reloc offset: %u\n",
303 					submit_reloc.submit_offset);
304 			return -EINVAL;
305 		}
306 
307 		/* offset in dwords: */
308 		off = submit_reloc.submit_offset / 4;
309 
310 		if ((off >= (obj->base.size / 4)) ||
311 				(off < last_offset)) {
312 			DRM_ERROR("invalid offset %u at reloc %u\n", off, i);
313 			return -EINVAL;
314 		}
315 
316 		ret = submit_bo(submit, submit_reloc.reloc_idx, NULL, &iova, &valid);
317 		if (ret)
318 			return ret;
319 
320 		if (valid)
321 			continue;
322 
323 		iova += submit_reloc.reloc_offset;
324 
325 		if (submit_reloc.shift < 0)
326 			iova >>= -submit_reloc.shift;
327 		else
328 			iova <<= submit_reloc.shift;
329 
330 		ptr[off] = iova | submit_reloc.or;
331 
332 		last_offset = off;
333 	}
334 
335 	return 0;
336 }
337 
338 static void submit_cleanup(struct msm_gem_submit *submit)
339 {
340 	unsigned i;
341 
342 	for (i = 0; i < submit->nr_bos; i++) {
343 		struct msm_gem_object *msm_obj = submit->bos[i].obj;
344 		submit_unlock_unpin_bo(submit, i);
345 		list_del_init(&msm_obj->submit_entry);
346 		drm_gem_object_unreference(&msm_obj->base);
347 	}
348 
349 	ww_acquire_fini(&submit->ticket);
350 }
351 
352 int msm_ioctl_gem_submit(struct drm_device *dev, void *data,
353 		struct drm_file *file)
354 {
355 	struct msm_drm_private *priv = dev->dev_private;
356 	struct drm_msm_gem_submit *args = data;
357 	struct msm_file_private *ctx = file->driver_priv;
358 	struct msm_gem_submit *submit;
359 	struct msm_gpu *gpu = priv->gpu;
360 	unsigned i;
361 	int ret;
362 
363 	if (!gpu)
364 		return -ENXIO;
365 
366 	/* for now, we just have 3d pipe.. eventually this would need to
367 	 * be more clever to dispatch to appropriate gpu module:
368 	 */
369 	if (args->pipe != MSM_PIPE_3D0)
370 		return -EINVAL;
371 
372 	if (args->nr_cmds > MAX_CMDS)
373 		return -EINVAL;
374 
375 	submit = submit_create(dev, gpu, args->nr_bos);
376 	if (!submit)
377 		return -ENOMEM;
378 
379 	mutex_lock(&dev->struct_mutex);
380 
381 	ret = submit_lookup_objects(submit, args, file);
382 	if (ret)
383 		goto out;
384 
385 	ret = submit_lock_objects(submit);
386 	if (ret)
387 		goto out;
388 
389 	ret = submit_fence_sync(submit);
390 	if (ret)
391 		goto out;
392 
393 	ret = submit_pin_objects(submit);
394 	if (ret)
395 		goto out;
396 
397 	for (i = 0; i < args->nr_cmds; i++) {
398 		struct drm_msm_gem_submit_cmd submit_cmd;
399 		void __user *userptr =
400 			u64_to_user_ptr(args->cmds + (i * sizeof(submit_cmd)));
401 		struct msm_gem_object *msm_obj;
402 		uint32_t iova;
403 
404 		ret = copy_from_user(&submit_cmd, userptr, sizeof(submit_cmd));
405 		if (ret) {
406 			ret = -EFAULT;
407 			goto out;
408 		}
409 
410 		/* validate input from userspace: */
411 		switch (submit_cmd.type) {
412 		case MSM_SUBMIT_CMD_BUF:
413 		case MSM_SUBMIT_CMD_IB_TARGET_BUF:
414 		case MSM_SUBMIT_CMD_CTX_RESTORE_BUF:
415 			break;
416 		default:
417 			DRM_ERROR("invalid type: %08x\n", submit_cmd.type);
418 			ret = -EINVAL;
419 			goto out;
420 		}
421 
422 		ret = submit_bo(submit, submit_cmd.submit_idx,
423 				&msm_obj, &iova, NULL);
424 		if (ret)
425 			goto out;
426 
427 		if (submit_cmd.size % 4) {
428 			DRM_ERROR("non-aligned cmdstream buffer size: %u\n",
429 					submit_cmd.size);
430 			ret = -EINVAL;
431 			goto out;
432 		}
433 
434 		if ((submit_cmd.size + submit_cmd.submit_offset) >=
435 				msm_obj->base.size) {
436 			DRM_ERROR("invalid cmdstream size: %u\n", submit_cmd.size);
437 			ret = -EINVAL;
438 			goto out;
439 		}
440 
441 		submit->cmd[i].type = submit_cmd.type;
442 		submit->cmd[i].size = submit_cmd.size / 4;
443 		submit->cmd[i].iova = iova + submit_cmd.submit_offset;
444 		submit->cmd[i].idx  = submit_cmd.submit_idx;
445 
446 		if (submit->valid)
447 			continue;
448 
449 		ret = submit_reloc(submit, msm_obj, submit_cmd.submit_offset,
450 				submit_cmd.nr_relocs, submit_cmd.relocs);
451 		if (ret)
452 			goto out;
453 	}
454 
455 	submit->nr_cmds = i;
456 
457 	ret = msm_gpu_submit(gpu, submit, ctx);
458 
459 	args->fence = submit->fence->seqno;
460 
461 out:
462 	submit_cleanup(submit);
463 	if (ret)
464 		msm_gem_submit_free(submit);
465 	mutex_unlock(&dev->struct_mutex);
466 	return ret;
467 }
468