linux/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (C) 2015 Etnaviv Project
   4 */
   5
   6#include <drm/drm_file.h>
   7#include <linux/dma-fence-array.h>
   8#include <linux/file.h>
   9#include <linux/pm_runtime.h>
  10#include <linux/dma-resv.h>
  11#include <linux/sync_file.h>
  12#include <linux/uaccess.h>
  13#include <linux/vmalloc.h>
  14
  15#include "etnaviv_cmdbuf.h"
  16#include "etnaviv_drv.h"
  17#include "etnaviv_gpu.h"
  18#include "etnaviv_gem.h"
  19#include "etnaviv_perfmon.h"
  20#include "etnaviv_sched.h"
  21
  22/*
  23 * Cmdstream submission:
  24 */
  25
  26#define BO_INVALID_FLAGS ~(ETNA_SUBMIT_BO_READ | ETNA_SUBMIT_BO_WRITE)
  27/* make sure these don't conflict w/ ETNAVIV_SUBMIT_BO_x */
  28#define BO_LOCKED   0x4000
  29#define BO_PINNED   0x2000
  30
  31static struct etnaviv_gem_submit *submit_create(struct drm_device *dev,
  32                struct etnaviv_gpu *gpu, size_t nr_bos, size_t nr_pmrs)
  33{
  34        struct etnaviv_gem_submit *submit;
  35        size_t sz = size_vstruct(nr_bos, sizeof(submit->bos[0]), sizeof(*submit));
  36
  37        submit = kzalloc(sz, GFP_KERNEL);
  38        if (!submit)
  39                return NULL;
  40
  41        submit->pmrs = kcalloc(nr_pmrs, sizeof(struct etnaviv_perfmon_request),
  42                               GFP_KERNEL);
  43        if (!submit->pmrs) {
  44                kfree(submit);
  45                return NULL;
  46        }
  47        submit->nr_pmrs = nr_pmrs;
  48
  49        submit->gpu = gpu;
  50        kref_init(&submit->refcount);
  51
  52        return submit;
  53}
  54
  55static int submit_lookup_objects(struct etnaviv_gem_submit *submit,
  56        struct drm_file *file, struct drm_etnaviv_gem_submit_bo *submit_bos,
  57        unsigned nr_bos)
  58{
  59        struct drm_etnaviv_gem_submit_bo *bo;
  60        unsigned i;
  61        int ret = 0;
  62
  63        spin_lock(&file->table_lock);
  64
  65        for (i = 0, bo = submit_bos; i < nr_bos; i++, bo++) {
  66                struct drm_gem_object *obj;
  67
  68                if (bo->flags & BO_INVALID_FLAGS) {
  69                        DRM_ERROR("invalid flags: %x\n", bo->flags);
  70                        ret = -EINVAL;
  71                        goto out_unlock;
  72                }
  73
  74                submit->bos[i].flags = bo->flags;
  75                if (submit->flags & ETNA_SUBMIT_SOFTPIN) {
  76                        if (bo->presumed < ETNAVIV_SOFTPIN_START_ADDRESS) {
  77                                DRM_ERROR("invalid softpin address\n");
  78                                ret = -EINVAL;
  79                                goto out_unlock;
  80                        }
  81                        submit->bos[i].va = bo->presumed;
  82                }
  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, bo->handle);
  88                if (!obj) {
  89                        DRM_ERROR("invalid handle %u at index %u\n",
  90                                  bo->handle, i);
  91                        ret = -EINVAL;
  92                        goto out_unlock;
  93                }
  94
  95                /*
  96                 * Take a refcount on the object. The file table lock
  97                 * prevents the object_idr's refcount on this being dropped.
  98                 */
  99                drm_gem_object_get(obj);
 100
 101                submit->bos[i].obj = to_etnaviv_bo(obj);
 102        }
 103
 104out_unlock:
 105        submit->nr_bos = i;
 106        spin_unlock(&file->table_lock);
 107
 108        return ret;
 109}
 110
 111static void submit_unlock_object(struct etnaviv_gem_submit *submit, int i)
 112{
 113        if (submit->bos[i].flags & BO_LOCKED) {
 114                struct drm_gem_object *obj = &submit->bos[i].obj->base;
 115
 116                dma_resv_unlock(obj->resv);
 117                submit->bos[i].flags &= ~BO_LOCKED;
 118        }
 119}
 120
 121static int submit_lock_objects(struct etnaviv_gem_submit *submit,
 122                struct ww_acquire_ctx *ticket)
 123{
 124        int contended, slow_locked = -1, i, ret = 0;
 125
 126retry:
 127        for (i = 0; i < submit->nr_bos; i++) {
 128                struct drm_gem_object *obj = &submit->bos[i].obj->base;
 129
 130                if (slow_locked == i)
 131                        slow_locked = -1;
 132
 133                contended = i;
 134
 135                if (!(submit->bos[i].flags & BO_LOCKED)) {
 136                        ret = dma_resv_lock_interruptible(obj->resv, ticket);
 137                        if (ret == -EALREADY)
 138                                DRM_ERROR("BO at index %u already on submit list\n",
 139                                          i);
 140                        if (ret)
 141                                goto fail;
 142                        submit->bos[i].flags |= BO_LOCKED;
 143                }
 144        }
 145
 146        ww_acquire_done(ticket);
 147
 148        return 0;
 149
 150fail:
 151        for (; i >= 0; i--)
 152                submit_unlock_object(submit, i);
 153
 154        if (slow_locked > 0)
 155                submit_unlock_object(submit, slow_locked);
 156
 157        if (ret == -EDEADLK) {
 158                struct drm_gem_object *obj;
 159
 160                obj = &submit->bos[contended].obj->base;
 161
 162                /* we lost out in a seqno race, lock and retry.. */
 163                ret = dma_resv_lock_slow_interruptible(obj->resv, ticket);
 164                if (!ret) {
 165                        submit->bos[contended].flags |= BO_LOCKED;
 166                        slow_locked = contended;
 167                        goto retry;
 168                }
 169        }
 170
 171        return ret;
 172}
 173
 174static int submit_fence_sync(struct etnaviv_gem_submit *submit)
 175{
 176        int i, ret = 0;
 177
 178        for (i = 0; i < submit->nr_bos; i++) {
 179                struct etnaviv_gem_submit_bo *bo = &submit->bos[i];
 180                struct dma_resv *robj = bo->obj->base.resv;
 181
 182                if (!(bo->flags & ETNA_SUBMIT_BO_WRITE)) {
 183                        ret = dma_resv_reserve_shared(robj, 1);
 184                        if (ret)
 185                                return ret;
 186                }
 187
 188                if (submit->flags & ETNA_SUBMIT_NO_IMPLICIT)
 189                        continue;
 190
 191                if (bo->flags & ETNA_SUBMIT_BO_WRITE) {
 192                        ret = dma_resv_get_fences_rcu(robj, &bo->excl,
 193                                                                &bo->nr_shared,
 194                                                                &bo->shared);
 195                        if (ret)
 196                                return ret;
 197                } else {
 198                        bo->excl = dma_resv_get_excl_rcu(robj);
 199                }
 200
 201        }
 202
 203        return ret;
 204}
 205
 206static void submit_attach_object_fences(struct etnaviv_gem_submit *submit)
 207{
 208        int i;
 209
 210        for (i = 0; i < submit->nr_bos; i++) {
 211                struct drm_gem_object *obj = &submit->bos[i].obj->base;
 212
 213                if (submit->bos[i].flags & ETNA_SUBMIT_BO_WRITE)
 214                        dma_resv_add_excl_fence(obj->resv,
 215                                                          submit->out_fence);
 216                else
 217                        dma_resv_add_shared_fence(obj->resv,
 218                                                            submit->out_fence);
 219
 220                submit_unlock_object(submit, i);
 221        }
 222}
 223
 224static int submit_pin_objects(struct etnaviv_gem_submit *submit)
 225{
 226        int i, ret = 0;
 227
 228        for (i = 0; i < submit->nr_bos; i++) {
 229                struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj;
 230                struct etnaviv_vram_mapping *mapping;
 231
 232                mapping = etnaviv_gem_mapping_get(&etnaviv_obj->base,
 233                                                  submit->mmu_context,
 234                                                  submit->bos[i].va);
 235                if (IS_ERR(mapping)) {
 236                        ret = PTR_ERR(mapping);
 237                        break;
 238                }
 239
 240                if ((submit->flags & ETNA_SUBMIT_SOFTPIN) &&
 241                     submit->bos[i].va != mapping->iova) {
 242                        etnaviv_gem_mapping_unreference(mapping);
 243                        return -EINVAL;
 244                }
 245
 246                atomic_inc(&etnaviv_obj->gpu_active);
 247
 248                submit->bos[i].flags |= BO_PINNED;
 249                submit->bos[i].mapping = mapping;
 250        }
 251
 252        return ret;
 253}
 254
 255static int submit_bo(struct etnaviv_gem_submit *submit, u32 idx,
 256        struct etnaviv_gem_submit_bo **bo)
 257{
 258        if (idx >= submit->nr_bos) {
 259                DRM_ERROR("invalid buffer index: %u (out of %u)\n",
 260                                idx, submit->nr_bos);
 261                return -EINVAL;
 262        }
 263
 264        *bo = &submit->bos[idx];
 265
 266        return 0;
 267}
 268
 269/* process the reloc's and patch up the cmdstream as needed: */
 270static int submit_reloc(struct etnaviv_gem_submit *submit, void *stream,
 271                u32 size, const struct drm_etnaviv_gem_submit_reloc *relocs,
 272                u32 nr_relocs)
 273{
 274        u32 i, last_offset = 0;
 275        u32 *ptr = stream;
 276        int ret;
 277
 278        /* Submits using softpin don't blend with relocs */
 279        if ((submit->flags & ETNA_SUBMIT_SOFTPIN) && nr_relocs != 0)
 280                return -EINVAL;
 281
 282        for (i = 0; i < nr_relocs; i++) {
 283                const struct drm_etnaviv_gem_submit_reloc *r = relocs + i;
 284                struct etnaviv_gem_submit_bo *bo;
 285                u32 off;
 286
 287                if (unlikely(r->flags)) {
 288                        DRM_ERROR("invalid reloc flags\n");
 289                        return -EINVAL;
 290                }
 291
 292                if (r->submit_offset % 4) {
 293                        DRM_ERROR("non-aligned reloc offset: %u\n",
 294                                  r->submit_offset);
 295                        return -EINVAL;
 296                }
 297
 298                /* offset in dwords: */
 299                off = r->submit_offset / 4;
 300
 301                if ((off >= size ) ||
 302                                (off < last_offset)) {
 303                        DRM_ERROR("invalid offset %u at reloc %u\n", off, i);
 304                        return -EINVAL;
 305                }
 306
 307                ret = submit_bo(submit, r->reloc_idx, &bo);
 308                if (ret)
 309                        return ret;
 310
 311                if (r->reloc_offset > bo->obj->base.size - sizeof(*ptr)) {
 312                        DRM_ERROR("relocation %u outside object\n", i);
 313                        return -EINVAL;
 314                }
 315
 316                ptr[off] = bo->mapping->iova + r->reloc_offset;
 317
 318                last_offset = off;
 319        }
 320
 321        return 0;
 322}
 323
 324static int submit_perfmon_validate(struct etnaviv_gem_submit *submit,
 325                u32 exec_state, const struct drm_etnaviv_gem_submit_pmr *pmrs)
 326{
 327        u32 i;
 328
 329        for (i = 0; i < submit->nr_pmrs; i++) {
 330                const struct drm_etnaviv_gem_submit_pmr *r = pmrs + i;
 331                struct etnaviv_gem_submit_bo *bo;
 332                int ret;
 333
 334                ret = submit_bo(submit, r->read_idx, &bo);
 335                if (ret)
 336                        return ret;
 337
 338                /* at offset 0 a sequence number gets stored used for userspace sync */
 339                if (r->read_offset == 0) {
 340                        DRM_ERROR("perfmon request: offset is 0");
 341                        return -EINVAL;
 342                }
 343
 344                if (r->read_offset >= bo->obj->base.size - sizeof(u32)) {
 345                        DRM_ERROR("perfmon request: offset %u outside object", i);
 346                        return -EINVAL;
 347                }
 348
 349                if (r->flags & ~(ETNA_PM_PROCESS_PRE | ETNA_PM_PROCESS_POST)) {
 350                        DRM_ERROR("perfmon request: flags are not valid");
 351                        return -EINVAL;
 352                }
 353
 354                if (etnaviv_pm_req_validate(r, exec_state)) {
 355                        DRM_ERROR("perfmon request: domain or signal not valid");
 356                        return -EINVAL;
 357                }
 358
 359                submit->pmrs[i].flags = r->flags;
 360                submit->pmrs[i].domain = r->domain;
 361                submit->pmrs[i].signal = r->signal;
 362                submit->pmrs[i].sequence = r->sequence;
 363                submit->pmrs[i].offset = r->read_offset;
 364                submit->pmrs[i].bo_vma = etnaviv_gem_vmap(&bo->obj->base);
 365        }
 366
 367        return 0;
 368}
 369
 370static void submit_cleanup(struct kref *kref)
 371{
 372        struct etnaviv_gem_submit *submit =
 373                        container_of(kref, struct etnaviv_gem_submit, refcount);
 374        unsigned i;
 375
 376        if (submit->runtime_resumed)
 377                pm_runtime_put_autosuspend(submit->gpu->dev);
 378
 379        if (submit->cmdbuf.suballoc)
 380                etnaviv_cmdbuf_free(&submit->cmdbuf);
 381
 382        if (submit->mmu_context)
 383                etnaviv_iommu_context_put(submit->mmu_context);
 384
 385        if (submit->prev_mmu_context)
 386                etnaviv_iommu_context_put(submit->prev_mmu_context);
 387
 388        for (i = 0; i < submit->nr_bos; i++) {
 389                struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj;
 390
 391                /* unpin all objects */
 392                if (submit->bos[i].flags & BO_PINNED) {
 393                        etnaviv_gem_mapping_unreference(submit->bos[i].mapping);
 394                        atomic_dec(&etnaviv_obj->gpu_active);
 395                        submit->bos[i].mapping = NULL;
 396                        submit->bos[i].flags &= ~BO_PINNED;
 397                }
 398
 399                /* if the GPU submit failed, objects might still be locked */
 400                submit_unlock_object(submit, i);
 401                drm_gem_object_put(&etnaviv_obj->base);
 402        }
 403
 404        wake_up_all(&submit->gpu->fence_event);
 405
 406        if (submit->in_fence)
 407                dma_fence_put(submit->in_fence);
 408        if (submit->out_fence) {
 409                /* first remove from IDR, so fence can not be found anymore */
 410                mutex_lock(&submit->gpu->fence_lock);
 411                idr_remove(&submit->gpu->fence_idr, submit->out_fence_id);
 412                mutex_unlock(&submit->gpu->fence_lock);
 413                dma_fence_put(submit->out_fence);
 414        }
 415        kfree(submit->pmrs);
 416        kfree(submit);
 417}
 418
 419void etnaviv_submit_put(struct etnaviv_gem_submit *submit)
 420{
 421        kref_put(&submit->refcount, submit_cleanup);
 422}
 423
 424int etnaviv_ioctl_gem_submit(struct drm_device *dev, void *data,
 425                struct drm_file *file)
 426{
 427        struct etnaviv_file_private *ctx = file->driver_priv;
 428        struct etnaviv_drm_private *priv = dev->dev_private;
 429        struct drm_etnaviv_gem_submit *args = data;
 430        struct drm_etnaviv_gem_submit_reloc *relocs;
 431        struct drm_etnaviv_gem_submit_pmr *pmrs;
 432        struct drm_etnaviv_gem_submit_bo *bos;
 433        struct etnaviv_gem_submit *submit;
 434        struct etnaviv_gpu *gpu;
 435        struct sync_file *sync_file = NULL;
 436        struct ww_acquire_ctx ticket;
 437        int out_fence_fd = -1;
 438        void *stream;
 439        int ret;
 440
 441        if (args->pipe >= ETNA_MAX_PIPES)
 442                return -EINVAL;
 443
 444        gpu = priv->gpu[args->pipe];
 445        if (!gpu)
 446                return -ENXIO;
 447
 448        if (args->stream_size % 4) {
 449                DRM_ERROR("non-aligned cmdstream buffer size: %u\n",
 450                          args->stream_size);
 451                return -EINVAL;
 452        }
 453
 454        if (args->exec_state != ETNA_PIPE_3D &&
 455            args->exec_state != ETNA_PIPE_2D &&
 456            args->exec_state != ETNA_PIPE_VG) {
 457                DRM_ERROR("invalid exec_state: 0x%x\n", args->exec_state);
 458                return -EINVAL;
 459        }
 460
 461        if (args->flags & ~ETNA_SUBMIT_FLAGS) {
 462                DRM_ERROR("invalid flags: 0x%x\n", args->flags);
 463                return -EINVAL;
 464        }
 465
 466        if ((args->flags & ETNA_SUBMIT_SOFTPIN) &&
 467            priv->mmu_global->version != ETNAVIV_IOMMU_V2) {
 468                DRM_ERROR("softpin requested on incompatible MMU\n");
 469                return -EINVAL;
 470        }
 471
 472        /*
 473         * Copy the command submission and bo array to kernel space in
 474         * one go, and do this outside of any locks.
 475         */
 476        bos = kvmalloc_array(args->nr_bos, sizeof(*bos), GFP_KERNEL);
 477        relocs = kvmalloc_array(args->nr_relocs, sizeof(*relocs), GFP_KERNEL);
 478        pmrs = kvmalloc_array(args->nr_pmrs, sizeof(*pmrs), GFP_KERNEL);
 479        stream = kvmalloc_array(1, args->stream_size, GFP_KERNEL);
 480        if (!bos || !relocs || !pmrs || !stream) {
 481                ret = -ENOMEM;
 482                goto err_submit_cmds;
 483        }
 484
 485        ret = copy_from_user(bos, u64_to_user_ptr(args->bos),
 486                             args->nr_bos * sizeof(*bos));
 487        if (ret) {
 488                ret = -EFAULT;
 489                goto err_submit_cmds;
 490        }
 491
 492        ret = copy_from_user(relocs, u64_to_user_ptr(args->relocs),
 493                             args->nr_relocs * sizeof(*relocs));
 494        if (ret) {
 495                ret = -EFAULT;
 496                goto err_submit_cmds;
 497        }
 498
 499        ret = copy_from_user(pmrs, u64_to_user_ptr(args->pmrs),
 500                             args->nr_pmrs * sizeof(*pmrs));
 501        if (ret) {
 502                ret = -EFAULT;
 503                goto err_submit_cmds;
 504        }
 505
 506        ret = copy_from_user(stream, u64_to_user_ptr(args->stream),
 507                             args->stream_size);
 508        if (ret) {
 509                ret = -EFAULT;
 510                goto err_submit_cmds;
 511        }
 512
 513        if (args->flags & ETNA_SUBMIT_FENCE_FD_OUT) {
 514                out_fence_fd = get_unused_fd_flags(O_CLOEXEC);
 515                if (out_fence_fd < 0) {
 516                        ret = out_fence_fd;
 517                        goto err_submit_cmds;
 518                }
 519        }
 520
 521        ww_acquire_init(&ticket, &reservation_ww_class);
 522
 523        submit = submit_create(dev, gpu, args->nr_bos, args->nr_pmrs);
 524        if (!submit) {
 525                ret = -ENOMEM;
 526                goto err_submit_ww_acquire;
 527        }
 528
 529        ret = etnaviv_cmdbuf_init(priv->cmdbuf_suballoc, &submit->cmdbuf,
 530                                  ALIGN(args->stream_size, 8) + 8);
 531        if (ret)
 532                goto err_submit_objects;
 533
 534        submit->ctx = file->driver_priv;
 535        etnaviv_iommu_context_get(submit->ctx->mmu);
 536        submit->mmu_context = submit->ctx->mmu;
 537        submit->exec_state = args->exec_state;
 538        submit->flags = args->flags;
 539
 540        ret = submit_lookup_objects(submit, file, bos, args->nr_bos);
 541        if (ret)
 542                goto err_submit_objects;
 543
 544        if ((priv->mmu_global->version != ETNAVIV_IOMMU_V2) &&
 545            !etnaviv_cmd_validate_one(gpu, stream, args->stream_size / 4,
 546                                      relocs, args->nr_relocs)) {
 547                ret = -EINVAL;
 548                goto err_submit_objects;
 549        }
 550
 551        if (args->flags & ETNA_SUBMIT_FENCE_FD_IN) {
 552                submit->in_fence = sync_file_get_fence(args->fence_fd);
 553                if (!submit->in_fence) {
 554                        ret = -EINVAL;
 555                        goto err_submit_objects;
 556                }
 557        }
 558
 559        ret = submit_pin_objects(submit);
 560        if (ret)
 561                goto err_submit_objects;
 562
 563        ret = submit_reloc(submit, stream, args->stream_size / 4,
 564                           relocs, args->nr_relocs);
 565        if (ret)
 566                goto err_submit_objects;
 567
 568        ret = submit_perfmon_validate(submit, args->exec_state, pmrs);
 569        if (ret)
 570                goto err_submit_objects;
 571
 572        memcpy(submit->cmdbuf.vaddr, stream, args->stream_size);
 573
 574        ret = submit_lock_objects(submit, &ticket);
 575        if (ret)
 576                goto err_submit_objects;
 577
 578        ret = submit_fence_sync(submit);
 579        if (ret)
 580                goto err_submit_objects;
 581
 582        ret = etnaviv_sched_push_job(&ctx->sched_entity[args->pipe], submit);
 583        if (ret)
 584                goto err_submit_objects;
 585
 586        submit_attach_object_fences(submit);
 587
 588        if (args->flags & ETNA_SUBMIT_FENCE_FD_OUT) {
 589                /*
 590                 * This can be improved: ideally we want to allocate the sync
 591                 * file before kicking off the GPU job and just attach the
 592                 * fence to the sync file here, eliminating the ENOMEM
 593                 * possibility at this stage.
 594                 */
 595                sync_file = sync_file_create(submit->out_fence);
 596                if (!sync_file) {
 597                        ret = -ENOMEM;
 598                        goto err_submit_objects;
 599                }
 600                fd_install(out_fence_fd, sync_file->file);
 601        }
 602
 603        args->fence_fd = out_fence_fd;
 604        args->fence = submit->out_fence_id;
 605
 606err_submit_objects:
 607        etnaviv_submit_put(submit);
 608
 609err_submit_ww_acquire:
 610        ww_acquire_fini(&ticket);
 611
 612err_submit_cmds:
 613        if (ret && (out_fence_fd >= 0))
 614                put_unused_fd(out_fence_fd);
 615        if (stream)
 616                kvfree(stream);
 617        if (bos)
 618                kvfree(bos);
 619        if (relocs)
 620                kvfree(relocs);
 621        if (pmrs)
 622                kvfree(pmrs);
 623
 624        return ret;
 625}
 626