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