root/sys/dev/pci/drm/i915/gem/selftests/i915_gem_migrate.c
// SPDX-License-Identifier: MIT
/*
 * Copyright © 2020-2021 Intel Corporation
 */

#include "gt/intel_migrate.h"
#include "gt/intel_gpu_commands.h"
#include "gem/i915_gem_ttm_move.h"

#include "i915_deps.h"

#include "selftests/igt_reset.h"
#include "selftests/igt_spinner.h"

static int igt_fill_check_buffer(struct drm_i915_gem_object *obj,
                                 struct intel_gt *gt,
                                 bool fill)
{
        unsigned int i, count = obj->base.size / sizeof(u32);
        enum i915_map_type map_type =
                intel_gt_coherent_map_type(gt, obj, false);
        u32 *cur;
        int err = 0;

        assert_object_held(obj);
        cur = i915_gem_object_pin_map(obj, map_type);
        if (IS_ERR(cur))
                return PTR_ERR(cur);

        if (fill)
                for (i = 0; i < count; ++i)
                        *cur++ = i;
        else
                for (i = 0; i < count; ++i)
                        if (*cur++ != i) {
                                pr_err("Object content mismatch at location %d of %d\n", i, count);
                                err = -EINVAL;
                                break;
                        }

        i915_gem_object_unpin_map(obj);

        return err;
}

static int igt_create_migrate(struct intel_gt *gt, enum intel_region_id src,
                              enum intel_region_id dst)
{
        struct drm_i915_private *i915 = gt->i915;
        struct intel_memory_region *src_mr = i915->mm.regions[src];
        struct intel_memory_region *dst_mr = i915->mm.regions[dst];
        struct drm_i915_gem_object *obj;
        struct i915_gem_ww_ctx ww;
        int err = 0;

        GEM_BUG_ON(!src_mr);
        GEM_BUG_ON(!dst_mr);

        /* Switch object backing-store on create */
        obj = i915_gem_object_create_region(src_mr, dst_mr->min_page_size, 0, 0);
        if (IS_ERR(obj))
                return PTR_ERR(obj);

        for_i915_gem_ww(&ww, err, true) {
                err = i915_gem_object_lock(obj, &ww);
                if (err)
                        continue;

                err = igt_fill_check_buffer(obj, gt, true);
                if (err)
                        continue;

                err = i915_gem_object_migrate(obj, &ww, dst);
                if (err)
                        continue;

                err = i915_gem_object_pin_pages(obj);
                if (err)
                        continue;

                if (i915_gem_object_can_migrate(obj, src))
                        err = -EINVAL;

                i915_gem_object_unpin_pages(obj);
                err = i915_gem_object_wait_migration(obj, true);
                if (err)
                        continue;

                err = igt_fill_check_buffer(obj, gt, false);
        }
        i915_gem_object_put(obj);

        return err;
}

static int igt_smem_create_migrate(void *arg)
{
        return igt_create_migrate(arg, INTEL_REGION_LMEM_0, INTEL_REGION_SMEM);
}

static int igt_lmem_create_migrate(void *arg)
{
        return igt_create_migrate(arg, INTEL_REGION_SMEM, INTEL_REGION_LMEM_0);
}

static int igt_same_create_migrate(void *arg)
{
        return igt_create_migrate(arg, INTEL_REGION_LMEM_0, INTEL_REGION_LMEM_0);
}

static int lmem_pages_migrate_one(struct i915_gem_ww_ctx *ww,
                                  struct drm_i915_gem_object *obj,
                                  struct i915_vma *vma,
                                  bool silent_migrate)
{
        int err;

        err = i915_gem_object_lock(obj, ww);
        if (err)
                return err;

        if (vma) {
                err = i915_vma_pin_ww(vma, ww, obj->base.size, 0,
                                      0UL | PIN_OFFSET_FIXED |
                                      PIN_USER);
                if (err) {
                        if (err != -EINTR && err != ERESTARTSYS &&
                            err != -EDEADLK)
                                pr_err("Failed to pin vma.\n");
                        return err;
                }

                i915_vma_unpin(vma);
        }

        /*
         * Migration will implicitly unbind (asynchronously) any bound
         * vmas.
         */
        if (i915_gem_object_is_lmem(obj)) {
                err = i915_gem_object_migrate(obj, ww, INTEL_REGION_SMEM);
                if (err) {
                        if (!silent_migrate)
                                pr_err("Object failed migration to smem\n");
                        if (err)
                                return err;
                }

                if (i915_gem_object_is_lmem(obj)) {
                        pr_err("object still backed by lmem\n");
                        err = -EINVAL;
                }

                if (!i915_gem_object_has_struct_page(obj)) {
                        pr_err("object not backed by struct page\n");
                        err = -EINVAL;
                }

        } else {
                err = i915_gem_object_migrate(obj, ww, INTEL_REGION_LMEM_0);
                if (err) {
                        if (!silent_migrate)
                                pr_err("Object failed migration to lmem\n");
                        if (err)
                                return err;
                }

                if (i915_gem_object_has_struct_page(obj)) {
                        pr_err("object still backed by struct page\n");
                        err = -EINVAL;
                }

                if (!i915_gem_object_is_lmem(obj)) {
                        pr_err("object not backed by lmem\n");
                        err = -EINVAL;
                }
        }

        return err;
}

static int __igt_lmem_pages_migrate(struct intel_gt *gt,
                                    struct i915_address_space *vm,
                                    struct i915_deps *deps,
                                    struct igt_spinner *spin,
                                    struct dma_fence *spin_fence,
                                    bool borked_migrate)
{
        struct drm_i915_private *i915 = gt->i915;
        struct drm_i915_gem_object *obj;
        struct i915_vma *vma = NULL;
        struct i915_gem_ww_ctx ww;
        struct i915_request *rq;
        int err;
        int i;

        /* From LMEM to shmem and back again */

        obj = i915_gem_object_create_lmem(i915, SZ_2M, 0);
        if (IS_ERR(obj))
                return PTR_ERR(obj);

        if (vm) {
                vma = i915_vma_instance(obj, vm, NULL);
                if (IS_ERR(vma)) {
                        err = PTR_ERR(vma);
                        goto out_put;
                }
        }

        /* Initial GPU fill, sync, CPU initialization. */
        for_i915_gem_ww(&ww, err, true) {
                err = i915_gem_object_lock(obj, &ww);
                if (err)
                        continue;

                err = ____i915_gem_object_get_pages(obj);
                if (err)
                        continue;

                err = intel_migrate_clear(&gt->migrate, &ww, deps,
                                          obj->mm.pages->sgl, obj->pat_index,
                                          i915_gem_object_is_lmem(obj),
                                          0xdeadbeaf, &rq);
                if (rq) {
                        err = dma_resv_reserve_fences(obj->base.resv, 1);
                        if (!err)
                                dma_resv_add_fence(obj->base.resv, &rq->fence,
                                                   DMA_RESV_USAGE_KERNEL);
                        i915_request_put(rq);
                }
                if (err)
                        continue;

                if (!vma) {
                        err = igt_fill_check_buffer(obj, gt, true);
                        if (err)
                                continue;
                }
        }
        if (err)
                goto out_put;

        /*
         * Migrate to and from smem without explicitly syncing.
         * Finalize with data in smem for fast readout.
         */
        for (i = 1; i <= 5; ++i) {
                for_i915_gem_ww(&ww, err, true)
                        err = lmem_pages_migrate_one(&ww, obj, vma,
                                                     borked_migrate);
                if (err)
                        goto out_put;
        }

        err = i915_gem_object_lock_interruptible(obj, NULL);
        if (err)
                goto out_put;

        if (spin) {
                if (dma_fence_is_signaled(spin_fence)) {
                        pr_err("Spinner was terminated by hangcheck.\n");
                        err = -EBUSY;
                        goto out_unlock;
                }
                igt_spinner_end(spin);
        }

        /* Finally sync migration and check content. */
        err = i915_gem_object_wait_migration(obj, true);
        if (err)
                goto out_unlock;

        if (vma) {
                err = i915_vma_wait_for_bind(vma);
                if (err)
                        goto out_unlock;
        } else {
                err = igt_fill_check_buffer(obj, gt, false);
        }

out_unlock:
        i915_gem_object_unlock(obj);
out_put:
        i915_gem_object_put(obj);

        return err;
}

static int igt_lmem_pages_failsafe_migrate(void *arg)
{
        int fail_gpu, fail_alloc, ban_memcpy, ret;
        struct intel_gt *gt = arg;

        for (fail_gpu = 0; fail_gpu < 2; ++fail_gpu) {
                for (fail_alloc = 0; fail_alloc < 2; ++fail_alloc) {
                        for (ban_memcpy = 0; ban_memcpy < 2; ++ban_memcpy) {
                                pr_info("Simulated failure modes: gpu: %d, alloc:%d, ban_memcpy: %d\n",
                                        fail_gpu, fail_alloc, ban_memcpy);
                                i915_ttm_migrate_set_ban_memcpy(ban_memcpy);
                                i915_ttm_migrate_set_failure_modes(fail_gpu,
                                                                   fail_alloc);
                                ret = __igt_lmem_pages_migrate(gt, NULL, NULL,
                                                               NULL, NULL,
                                                               ban_memcpy &&
                                                               fail_gpu);

                                if (ban_memcpy && fail_gpu) {
                                        struct intel_gt *__gt;
                                        unsigned int id;

                                        if (ret != -EIO) {
                                                pr_err("expected -EIO, got (%d)\n", ret);
                                                ret = -EINVAL;
                                        } else {
                                                ret = 0;
                                        }

                                        for_each_gt(__gt, gt->i915, id) {
                                                intel_wakeref_t wakeref;
                                                bool wedged;

                                                mutex_lock(&__gt->reset.mutex);
                                                wedged = test_bit(I915_WEDGED, &__gt->reset.flags);
                                                mutex_unlock(&__gt->reset.mutex);

                                                if (fail_gpu && !fail_alloc) {
                                                        if (!wedged) {
                                                                pr_err("gt(%u) not wedged\n", id);
                                                                ret = -EINVAL;
                                                                continue;
                                                        }
                                                } else if (wedged) {
                                                        pr_err("gt(%u) incorrectly wedged\n", id);
                                                        ret = -EINVAL;
                                                } else {
                                                        continue;
                                                }

                                                wakeref = intel_runtime_pm_get(__gt->uncore->rpm);
                                                igt_global_reset_lock(__gt);
                                                intel_gt_reset(__gt, ALL_ENGINES, NULL);
                                                igt_global_reset_unlock(__gt);
                                                intel_runtime_pm_put(__gt->uncore->rpm, wakeref);
                                        }
                                        if (ret)
                                                goto out_err;
                                }
                        }
                }
        }

out_err:
        i915_ttm_migrate_set_failure_modes(false, false);
        i915_ttm_migrate_set_ban_memcpy(false);
        return ret;
}

/*
 * This subtest tests that unbinding at migration is indeed performed
 * async. We launch a spinner and a number of migrations depending on
 * that spinner to have terminated. Before each migration we bind a
 * vma, which should then be async unbound by the migration operation.
 * If we are able to schedule migrations without blocking while the
 * spinner is still running, those unbinds are indeed async and non-
 * blocking.
 *
 * Note that each async bind operation is awaiting the previous migration
 * due to the moving fence resulting from the migration.
 */
static int igt_async_migrate(struct intel_gt *gt)
{
        struct intel_engine_cs *engine;
        enum intel_engine_id id;
        struct i915_ppgtt *ppgtt;
        struct igt_spinner spin;
        int err;

        ppgtt = i915_ppgtt_create(gt, 0);
        if (IS_ERR(ppgtt))
                return PTR_ERR(ppgtt);

        if (igt_spinner_init(&spin, gt)) {
                err = -ENOMEM;
                goto out_spin;
        }

        for_each_engine(engine, gt, id) {
                struct ttm_operation_ctx ctx = {
                        .interruptible = true
                };
                struct dma_fence *spin_fence;
                struct intel_context *ce;
                struct i915_request *rq;
                struct i915_deps deps;

                ce = intel_context_create(engine);
                if (IS_ERR(ce)) {
                        err = PTR_ERR(ce);
                        goto out_ce;
                }

                /*
                 * Use MI_NOOP, making the spinner non-preemptible. If there
                 * is a code path where we fail async operation due to the
                 * running spinner, we will block and fail to end the
                 * spinner resulting in a deadlock. But with a non-
                 * preemptible spinner, hangcheck will terminate the spinner
                 * for us, and we will later detect that and fail the test.
                 */
                rq = igt_spinner_create_request(&spin, ce, MI_NOOP);
                intel_context_put(ce);
                if (IS_ERR(rq)) {
                        err = PTR_ERR(rq);
                        goto out_ce;
                }

                i915_deps_init(&deps, GFP_KERNEL);
                err = i915_deps_add_dependency(&deps, &rq->fence, &ctx);
                spin_fence = dma_fence_get(&rq->fence);
                i915_request_add(rq);
                if (err)
                        goto out_ce;

                err = __igt_lmem_pages_migrate(gt, &ppgtt->vm, &deps, &spin,
                                               spin_fence, false);
                i915_deps_fini(&deps);
                dma_fence_put(spin_fence);
                if (err)
                        goto out_ce;
        }

out_ce:
        igt_spinner_fini(&spin);
out_spin:
        i915_vm_put(&ppgtt->vm);

        return err;
}

/*
 * Setting ASYNC_FAIL_ALLOC to 2 will simulate memory allocation failure while
 * arming the migration error check and block async migration. This
 * will cause us to deadlock and hangcheck will terminate the spinner
 * causing the test to fail.
 */
#define ASYNC_FAIL_ALLOC 1
static int igt_lmem_async_migrate(void *arg)
{
        int fail_gpu, fail_alloc, ban_memcpy, ret;
        struct intel_gt *gt = arg;

        for (fail_gpu = 0; fail_gpu < 2; ++fail_gpu) {
                for (fail_alloc = 0; fail_alloc < ASYNC_FAIL_ALLOC; ++fail_alloc) {
                        for (ban_memcpy = 0; ban_memcpy < 2; ++ban_memcpy) {
                                pr_info("Simulated failure modes: gpu: %d, alloc: %d, ban_memcpy: %d\n",
                                        fail_gpu, fail_alloc, ban_memcpy);
                                i915_ttm_migrate_set_ban_memcpy(ban_memcpy);
                                i915_ttm_migrate_set_failure_modes(fail_gpu,
                                                                   fail_alloc);
                                ret = igt_async_migrate(gt);

                                if (fail_gpu && ban_memcpy) {
                                        struct intel_gt *__gt;
                                        unsigned int id;

                                        if (ret != -EIO) {
                                                pr_err("expected -EIO, got (%d)\n", ret);
                                                ret = -EINVAL;
                                        } else {
                                                ret = 0;
                                        }

                                        for_each_gt(__gt, gt->i915, id) {
                                                intel_wakeref_t wakeref;
                                                bool wedged;

                                                mutex_lock(&__gt->reset.mutex);
                                                wedged = test_bit(I915_WEDGED, &__gt->reset.flags);
                                                mutex_unlock(&__gt->reset.mutex);

                                                if (fail_gpu && !fail_alloc) {
                                                        if (!wedged) {
                                                                pr_err("gt(%u) not wedged\n", id);
                                                                ret = -EINVAL;
                                                                continue;
                                                        }
                                                } else if (wedged) {
                                                        pr_err("gt(%u) incorrectly wedged\n", id);
                                                        ret = -EINVAL;
                                                } else {
                                                        continue;
                                                }

                                                wakeref = intel_runtime_pm_get(__gt->uncore->rpm);
                                                igt_global_reset_lock(__gt);
                                                intel_gt_reset(__gt, ALL_ENGINES, NULL);
                                                igt_global_reset_unlock(__gt);
                                                intel_runtime_pm_put(__gt->uncore->rpm, wakeref);
                                        }
                                }
                                if (ret)
                                        goto out_err;
                        }
                }
        }

out_err:
        i915_ttm_migrate_set_failure_modes(false, false);
        i915_ttm_migrate_set_ban_memcpy(false);
        return ret;
}

int i915_gem_migrate_live_selftests(struct drm_i915_private *i915)
{
        static const struct i915_subtest tests[] = {
                SUBTEST(igt_smem_create_migrate),
                SUBTEST(igt_lmem_create_migrate),
                SUBTEST(igt_same_create_migrate),
                SUBTEST(igt_lmem_pages_failsafe_migrate),
                SUBTEST(igt_lmem_async_migrate),
        };

        if (!HAS_LMEM(i915))
                return 0;

        return intel_gt_live_subtests(tests, to_gt(i915));
}