blob: 711a658cf1ea8772df99b2ce05ce5ad2e296980b [file] [log] [blame]
Rob Clark7198e6b2013-07-19 12:59:32 -04001/*
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
Rob Clark7198e6b2013-07-19 12:59:32 -040026/* make sure these don't conflict w/ MSM_SUBMIT_BO_x */
Rob Clark340faef2016-03-14 13:56:37 -040027#define BO_VALID 0x8000 /* is current addr in cmdstream correct/valid? */
Rob Clark7198e6b2013-07-19 12:59:32 -040028#define BO_LOCKED 0x4000
29#define BO_PINNED 0x2000
30
31static inline void __user *to_user_ptr(u64 address)
32{
33 return (void __user *)(uintptr_t)address;
34}
35
36static struct msm_gem_submit *submit_create(struct drm_device *dev,
37 struct msm_gpu *gpu, int nr)
38{
39 struct msm_gem_submit *submit;
40 int sz = sizeof(*submit) + (nr * sizeof(submit->bos[0]));
41
42 submit = kmalloc(sz, GFP_TEMPORARY | __GFP_NOWARN | __GFP_NORETRY);
Rob Clark6860b562016-05-03 09:49:40 -040043 if (!submit)
44 return NULL;
Rob Clark7198e6b2013-07-19 12:59:32 -040045
Rob Clark6860b562016-05-03 09:49:40 -040046 submit->dev = dev;
47 submit->gpu = gpu;
Rob Clark7198e6b2013-07-19 12:59:32 -040048
Rob Clark6860b562016-05-03 09:49:40 -040049 /* initially, until copy_from_user() and bo lookup succeeds: */
50 submit->nr_bos = 0;
51 submit->nr_cmds = 0;
52
53 INIT_LIST_HEAD(&submit->bo_list);
54 ww_acquire_init(&submit->ticket, &reservation_ww_class);
Rob Clark7198e6b2013-07-19 12:59:32 -040055
56 return submit;
57}
58
Rob Clark40e68152016-05-03 09:50:26 -040059void msm_gem_submit_free(struct msm_gem_submit *submit)
60{
61 fence_put(submit->fence);
62 list_del(&submit->node);
63 kfree(submit);
64}
65
Rob Clark7198e6b2013-07-19 12:59:32 -040066static int submit_lookup_objects(struct msm_gem_submit *submit,
67 struct drm_msm_gem_submit *args, struct drm_file *file)
68{
69 unsigned i;
70 int ret = 0;
71
72 spin_lock(&file->table_lock);
73
74 for (i = 0; i < args->nr_bos; i++) {
75 struct drm_msm_gem_submit_bo submit_bo;
76 struct drm_gem_object *obj;
77 struct msm_gem_object *msm_obj;
78 void __user *userptr =
79 to_user_ptr(args->bos + (i * sizeof(submit_bo)));
80
81 ret = copy_from_user(&submit_bo, userptr, sizeof(submit_bo));
82 if (ret) {
83 ret = -EFAULT;
84 goto out_unlock;
85 }
86
Rob Clark93ddb0d2014-03-03 09:42:33 -050087 if (submit_bo.flags & ~MSM_SUBMIT_BO_FLAGS) {
Rob Clark19872532013-09-06 15:36:40 -040088 DRM_ERROR("invalid flags: %x\n", submit_bo.flags);
Rob Clark7198e6b2013-07-19 12:59:32 -040089 ret = -EINVAL;
90 goto out_unlock;
91 }
92
93 submit->bos[i].flags = submit_bo.flags;
94 /* in validate_objects() we figure out if this is true: */
95 submit->bos[i].iova = submit_bo.presumed;
96
97 /* normally use drm_gem_object_lookup(), but for bulk lookup
98 * all under single table_lock just hit object_idr directly:
99 */
100 obj = idr_find(&file->object_idr, submit_bo.handle);
101 if (!obj) {
Rob Clark19872532013-09-06 15:36:40 -0400102 DRM_ERROR("invalid handle %u at index %u\n", submit_bo.handle, i);
Rob Clark7198e6b2013-07-19 12:59:32 -0400103 ret = -EINVAL;
104 goto out_unlock;
105 }
106
107 msm_obj = to_msm_bo(obj);
108
109 if (!list_empty(&msm_obj->submit_entry)) {
Rob Clark19872532013-09-06 15:36:40 -0400110 DRM_ERROR("handle %u at index %u already on submit list\n",
Rob Clark7198e6b2013-07-19 12:59:32 -0400111 submit_bo.handle, i);
112 ret = -EINVAL;
113 goto out_unlock;
114 }
115
116 drm_gem_object_reference(obj);
117
118 submit->bos[i].obj = msm_obj;
119
120 list_add_tail(&msm_obj->submit_entry, &submit->bo_list);
121 }
122
123out_unlock:
124 submit->nr_bos = i;
125 spin_unlock(&file->table_lock);
126
127 return ret;
128}
129
130static void submit_unlock_unpin_bo(struct msm_gem_submit *submit, int i)
131{
132 struct msm_gem_object *msm_obj = submit->bos[i].obj;
133
134 if (submit->bos[i].flags & BO_PINNED)
135 msm_gem_put_iova(&msm_obj->base, submit->gpu->id);
136
137 if (submit->bos[i].flags & BO_LOCKED)
138 ww_mutex_unlock(&msm_obj->resv->lock);
139
140 if (!(submit->bos[i].flags & BO_VALID))
141 submit->bos[i].iova = 0;
142
143 submit->bos[i].flags &= ~(BO_LOCKED | BO_PINNED);
144}
145
146/* This is where we make sure all the bo's are reserved and pin'd: */
Rob Clark340faef2016-03-14 13:56:37 -0400147static int submit_lock_objects(struct msm_gem_submit *submit)
Rob Clark7198e6b2013-07-19 12:59:32 -0400148{
149 int contended, slow_locked = -1, i, ret = 0;
150
151retry:
Rob Clark7198e6b2013-07-19 12:59:32 -0400152 for (i = 0; i < submit->nr_bos; i++) {
153 struct msm_gem_object *msm_obj = submit->bos[i].obj;
Rob Clark7198e6b2013-07-19 12:59:32 -0400154
155 if (slow_locked == i)
156 slow_locked = -1;
157
158 contended = i;
159
160 if (!(submit->bos[i].flags & BO_LOCKED)) {
161 ret = ww_mutex_lock_interruptible(&msm_obj->resv->lock,
162 &submit->ticket);
163 if (ret)
164 goto fail;
165 submit->bos[i].flags |= BO_LOCKED;
166 }
Rob Clark7198e6b2013-07-19 12:59:32 -0400167 }
168
169 ww_acquire_done(&submit->ticket);
170
171 return 0;
172
173fail:
174 for (; i >= 0; i--)
175 submit_unlock_unpin_bo(submit, i);
176
177 if (slow_locked > 0)
178 submit_unlock_unpin_bo(submit, slow_locked);
179
180 if (ret == -EDEADLK) {
181 struct msm_gem_object *msm_obj = submit->bos[contended].obj;
182 /* we lost out in a seqno race, lock and retry.. */
183 ret = ww_mutex_lock_slow_interruptible(&msm_obj->resv->lock,
184 &submit->ticket);
185 if (!ret) {
186 submit->bos[contended].flags |= BO_LOCKED;
187 slow_locked = contended;
188 goto retry;
189 }
190 }
191
192 return ret;
193}
194
Rob Clarkb6295f92016-03-15 18:26:28 -0400195static int submit_fence_sync(struct msm_gem_submit *submit)
196{
197 int i, ret = 0;
198
199 for (i = 0; i < submit->nr_bos; i++) {
200 struct msm_gem_object *msm_obj = submit->bos[i].obj;
201 bool write = submit->bos[i].flags & MSM_SUBMIT_BO_WRITE;
202
203 ret = msm_gem_sync_object(&msm_obj->base, submit->gpu->fctx, write);
204 if (ret)
205 break;
206 }
207
208 return ret;
209}
210
Rob Clark340faef2016-03-14 13:56:37 -0400211static int submit_pin_objects(struct msm_gem_submit *submit)
212{
213 int i, ret = 0;
214
215 submit->valid = true;
216
217 for (i = 0; i < submit->nr_bos; i++) {
218 struct msm_gem_object *msm_obj = submit->bos[i].obj;
219 uint32_t iova;
220
221 /* if locking succeeded, pin bo: */
222 ret = msm_gem_get_iova_locked(&msm_obj->base,
223 submit->gpu->id, &iova);
224
225 if (ret)
226 break;
227
228 submit->bos[i].flags |= BO_PINNED;
229
230 if (iova == submit->bos[i].iova) {
231 submit->bos[i].flags |= BO_VALID;
232 } else {
233 submit->bos[i].iova = iova;
234 /* iova changed, so address in cmdstream is not valid: */
235 submit->bos[i].flags &= ~BO_VALID;
236 submit->valid = false;
237 }
238 }
239
240 return ret;
241}
242
Rob Clark7198e6b2013-07-19 12:59:32 -0400243static int submit_bo(struct msm_gem_submit *submit, uint32_t idx,
244 struct msm_gem_object **obj, uint32_t *iova, bool *valid)
245{
246 if (idx >= submit->nr_bos) {
Rob Clark19872532013-09-06 15:36:40 -0400247 DRM_ERROR("invalid buffer index: %u (out of %u)\n",
248 idx, submit->nr_bos);
249 return -EINVAL;
Rob Clark7198e6b2013-07-19 12:59:32 -0400250 }
251
252 if (obj)
253 *obj = submit->bos[idx].obj;
254 if (iova)
255 *iova = submit->bos[idx].iova;
256 if (valid)
257 *valid = !!(submit->bos[idx].flags & BO_VALID);
258
259 return 0;
260}
261
262/* process the reloc's and patch up the cmdstream as needed: */
263static int submit_reloc(struct msm_gem_submit *submit, struct msm_gem_object *obj,
264 uint32_t offset, uint32_t nr_relocs, uint64_t relocs)
265{
266 uint32_t i, last_offset = 0;
267 uint32_t *ptr;
268 int ret;
269
270 if (offset % 4) {
Rob Clark19872532013-09-06 15:36:40 -0400271 DRM_ERROR("non-aligned cmdstream buffer: %u\n", offset);
Rob Clark7198e6b2013-07-19 12:59:32 -0400272 return -EINVAL;
273 }
274
275 /* For now, just map the entire thing. Eventually we probably
276 * to do it page-by-page, w/ kmap() if not vmap()d..
277 */
Rob Clarkc2703b12014-02-06 19:19:20 -0500278 ptr = msm_gem_vaddr_locked(&obj->base);
Rob Clark7198e6b2013-07-19 12:59:32 -0400279
280 if (IS_ERR(ptr)) {
281 ret = PTR_ERR(ptr);
282 DBG("failed to map: %d", ret);
283 return ret;
284 }
285
286 for (i = 0; i < nr_relocs; i++) {
287 struct drm_msm_gem_submit_reloc submit_reloc;
288 void __user *userptr =
289 to_user_ptr(relocs + (i * sizeof(submit_reloc)));
290 uint32_t iova, off;
291 bool valid;
292
293 ret = copy_from_user(&submit_reloc, userptr, sizeof(submit_reloc));
294 if (ret)
295 return -EFAULT;
296
297 if (submit_reloc.submit_offset % 4) {
Rob Clark19872532013-09-06 15:36:40 -0400298 DRM_ERROR("non-aligned reloc offset: %u\n",
Rob Clark7198e6b2013-07-19 12:59:32 -0400299 submit_reloc.submit_offset);
300 return -EINVAL;
301 }
302
303 /* offset in dwords: */
304 off = submit_reloc.submit_offset / 4;
305
306 if ((off >= (obj->base.size / 4)) ||
307 (off < last_offset)) {
Rob Clark19872532013-09-06 15:36:40 -0400308 DRM_ERROR("invalid offset %u at reloc %u\n", off, i);
Rob Clark7198e6b2013-07-19 12:59:32 -0400309 return -EINVAL;
310 }
311
312 ret = submit_bo(submit, submit_reloc.reloc_idx, NULL, &iova, &valid);
313 if (ret)
314 return ret;
315
316 if (valid)
317 continue;
318
319 iova += submit_reloc.reloc_offset;
320
321 if (submit_reloc.shift < 0)
322 iova >>= -submit_reloc.shift;
323 else
324 iova <<= submit_reloc.shift;
325
326 ptr[off] = iova | submit_reloc.or;
327
328 last_offset = off;
329 }
330
331 return 0;
332}
333
Rob Clark40e68152016-05-03 09:50:26 -0400334static void submit_cleanup(struct msm_gem_submit *submit)
Rob Clark7198e6b2013-07-19 12:59:32 -0400335{
336 unsigned i;
337
Rob Clark7198e6b2013-07-19 12:59:32 -0400338 for (i = 0; i < submit->nr_bos; i++) {
339 struct msm_gem_object *msm_obj = submit->bos[i].obj;
340 submit_unlock_unpin_bo(submit, i);
341 list_del_init(&msm_obj->submit_entry);
342 drm_gem_object_unreference(&msm_obj->base);
343 }
Rob Clark7198e6b2013-07-19 12:59:32 -0400344
345 ww_acquire_fini(&submit->ticket);
Rob Clark7198e6b2013-07-19 12:59:32 -0400346}
347
348int msm_ioctl_gem_submit(struct drm_device *dev, void *data,
349 struct drm_file *file)
350{
351 struct msm_drm_private *priv = dev->dev_private;
352 struct drm_msm_gem_submit *args = data;
353 struct msm_file_private *ctx = file->driver_priv;
354 struct msm_gem_submit *submit;
Rob Clarkc01a9582016-02-03 13:12:31 -0500355 struct msm_gpu *gpu = priv->gpu;
Rob Clark7198e6b2013-07-19 12:59:32 -0400356 unsigned i;
357 int ret;
358
Rob Clarkc01a9582016-02-03 13:12:31 -0500359 if (!gpu)
360 return -ENXIO;
361
Rob Clark7198e6b2013-07-19 12:59:32 -0400362 /* for now, we just have 3d pipe.. eventually this would need to
363 * be more clever to dispatch to appropriate gpu module:
364 */
365 if (args->pipe != MSM_PIPE_3D0)
366 return -EINVAL;
367
Rob Clark7198e6b2013-07-19 12:59:32 -0400368 if (args->nr_cmds > MAX_CMDS)
369 return -EINVAL;
370
371 submit = submit_create(dev, gpu, args->nr_bos);
Rob Clark687f0842016-02-03 13:24:35 -0500372 if (!submit)
373 return -ENOMEM;
374
375 mutex_lock(&dev->struct_mutex);
Rob Clark7198e6b2013-07-19 12:59:32 -0400376
377 ret = submit_lookup_objects(submit, args, file);
378 if (ret)
379 goto out;
380
Rob Clark340faef2016-03-14 13:56:37 -0400381 ret = submit_lock_objects(submit);
382 if (ret)
383 goto out;
384
Rob Clarkb6295f92016-03-15 18:26:28 -0400385 ret = submit_fence_sync(submit);
386 if (ret)
387 goto out;
388
Rob Clark340faef2016-03-14 13:56:37 -0400389 ret = submit_pin_objects(submit);
Rob Clark7198e6b2013-07-19 12:59:32 -0400390 if (ret)
391 goto out;
392
393 for (i = 0; i < args->nr_cmds; i++) {
394 struct drm_msm_gem_submit_cmd submit_cmd;
395 void __user *userptr =
396 to_user_ptr(args->cmds + (i * sizeof(submit_cmd)));
397 struct msm_gem_object *msm_obj;
398 uint32_t iova;
399
400 ret = copy_from_user(&submit_cmd, userptr, sizeof(submit_cmd));
401 if (ret) {
402 ret = -EFAULT;
403 goto out;
404 }
405
Rob Clark93ddb0d2014-03-03 09:42:33 -0500406 /* validate input from userspace: */
407 switch (submit_cmd.type) {
408 case MSM_SUBMIT_CMD_BUF:
409 case MSM_SUBMIT_CMD_IB_TARGET_BUF:
410 case MSM_SUBMIT_CMD_CTX_RESTORE_BUF:
411 break;
412 default:
413 DRM_ERROR("invalid type: %08x\n", submit_cmd.type);
414 ret = -EINVAL;
415 goto out;
416 }
417
Rob Clark7198e6b2013-07-19 12:59:32 -0400418 ret = submit_bo(submit, submit_cmd.submit_idx,
419 &msm_obj, &iova, NULL);
420 if (ret)
421 goto out;
422
423 if (submit_cmd.size % 4) {
Rob Clark19872532013-09-06 15:36:40 -0400424 DRM_ERROR("non-aligned cmdstream buffer size: %u\n",
Rob Clark7198e6b2013-07-19 12:59:32 -0400425 submit_cmd.size);
426 ret = -EINVAL;
427 goto out;
428 }
429
Rob Clark19872532013-09-06 15:36:40 -0400430 if ((submit_cmd.size + submit_cmd.submit_offset) >=
431 msm_obj->base.size) {
432 DRM_ERROR("invalid cmdstream size: %u\n", submit_cmd.size);
Rob Clark7198e6b2013-07-19 12:59:32 -0400433 ret = -EINVAL;
434 goto out;
435 }
436
437 submit->cmd[i].type = submit_cmd.type;
438 submit->cmd[i].size = submit_cmd.size / 4;
439 submit->cmd[i].iova = iova + submit_cmd.submit_offset;
Rob Clarka7d3c952014-05-30 14:47:38 -0400440 submit->cmd[i].idx = submit_cmd.submit_idx;
Rob Clark7198e6b2013-07-19 12:59:32 -0400441
442 if (submit->valid)
443 continue;
444
445 ret = submit_reloc(submit, msm_obj, submit_cmd.submit_offset,
446 submit_cmd.nr_relocs, submit_cmd.relocs);
447 if (ret)
448 goto out;
449 }
450
451 submit->nr_cmds = i;
452
453 ret = msm_gpu_submit(gpu, submit, ctx);
454
Rob Clarkb6295f92016-03-15 18:26:28 -0400455 args->fence = submit->fence->seqno;
Rob Clark7198e6b2013-07-19 12:59:32 -0400456
457out:
Rob Clark40e68152016-05-03 09:50:26 -0400458 submit_cleanup(submit);
459 if (ret)
460 msm_gem_submit_free(submit);
Rob Clarkc2703b12014-02-06 19:19:20 -0500461 mutex_unlock(&dev->struct_mutex);
Rob Clark7198e6b2013-07-19 12:59:32 -0400462 return ret;
463}