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