#include "../i915_selftest.h"
 
+#include "lib_sw_fence.h"
+#include "mock_context.h"
+#include "mock_drm.h"
 #include "mock_gem_device.h"
 
 static int populate_ggtt(struct drm_i915_private *i915)
        return err;
 }
 
+static int igt_evict_contexts(void *arg)
+{
+       const u64 PRETEND_GGTT_SIZE = 16ull << 20;
+       struct drm_i915_private *i915 = arg;
+       struct intel_engine_cs *engine;
+       enum intel_engine_id id;
+       struct reserved {
+               struct drm_mm_node node;
+               struct reserved *next;
+       } *reserved = NULL;
+       struct drm_mm_node hole;
+       unsigned long count;
+       int err;
+
+       /*
+        * The purpose of this test is to verify that we will trigger an
+        * eviction in the GGTT when constructing a request that requires
+        * additional space in the GGTT for pinning the context. This space
+        * is not directly tied to the request so reclaiming it requires
+        * extra work.
+        *
+        * As such this test is only meaningful for full-ppgtt environments
+        * where the GTT space of the request is separate from the GGTT
+        * allocation required to build the request.
+        */
+       if (!USES_FULL_PPGTT(i915))
+               return 0;
+
+       mutex_lock(&i915->drm.struct_mutex);
+
+       /* Reserve a block so that we know we have enough to fit a few rq */
+       memset(&hole, 0, sizeof(hole));
+       err = i915_gem_gtt_insert(&i915->ggtt.base, &hole,
+                                 PRETEND_GGTT_SIZE, 0, I915_COLOR_UNEVICTABLE,
+                                 0, i915->ggtt.base.total,
+                                 PIN_NOEVICT);
+       if (err)
+               goto out_locked;
+
+       /* Make the GGTT appear small by filling it with unevictable nodes */
+       count = 0;
+       do {
+               struct reserved *r;
+
+               r = kcalloc(1, sizeof(*r), GFP_KERNEL);
+               if (!r) {
+                       err = -ENOMEM;
+                       goto out_locked;
+               }
+
+               if (i915_gem_gtt_insert(&i915->ggtt.base, &r->node,
+                                       1ul << 20, 0, I915_COLOR_UNEVICTABLE,
+                                       0, i915->ggtt.base.total,
+                                       PIN_NOEVICT)) {
+                       kfree(r);
+                       break;
+               }
+
+               r->next = reserved;
+               reserved = r;
+
+               count++;
+       } while (1);
+       drm_mm_remove_node(&hole);
+       mutex_unlock(&i915->drm.struct_mutex);
+       pr_info("Filled GGTT with %lu 1MiB nodes\n", count);
+
+       /* Overfill the GGTT with context objects and so try to evict one. */
+       for_each_engine(engine, i915, id) {
+               struct i915_sw_fence fence;
+               struct drm_file *file;
+
+               file = mock_file(i915);
+               if (IS_ERR(file))
+                       return PTR_ERR(file);
+
+               count = 0;
+               mutex_lock(&i915->drm.struct_mutex);
+               onstack_fence_init(&fence);
+               do {
+                       struct drm_i915_gem_request *rq;
+                       struct i915_gem_context *ctx;
+
+                       ctx = live_context(i915, file);
+                       if (!ctx)
+                               break;
+
+                       /* We will need some GGTT space for the rq's context */
+                       igt_evict_ctl.fail_if_busy = true;
+                       rq = i915_gem_request_alloc(engine, ctx);
+                       igt_evict_ctl.fail_if_busy = false;
+
+                       if (IS_ERR(rq)) {
+                               /* When full, fail_if_busy will trigger EBUSY */
+                               if (PTR_ERR(rq) != -EBUSY) {
+                                       pr_err("Unexpected error from request alloc (ctx hw id %u, on %s): %d\n",
+                                              ctx->hw_id, engine->name,
+                                              (int)PTR_ERR(rq));
+                                       err = PTR_ERR(rq);
+                               }
+                               break;
+                       }
+
+                       /* Keep every request/ctx pinned until we are full */
+                       err = i915_sw_fence_await_sw_fence_gfp(&rq->submit,
+                                                              &fence,
+                                                              GFP_KERNEL);
+                       if (err < 0)
+                               break;
+
+                       i915_add_request(rq);
+                       count++;
+                       err = 0;
+               } while(1);
+               mutex_unlock(&i915->drm.struct_mutex);
+
+               onstack_fence_fini(&fence);
+               pr_info("Submitted %lu contexts/requests on %s\n",
+                       count, engine->name);
+
+               mock_file_free(i915, file);
+               if (err)
+                       break;
+       }
+
+       mutex_lock(&i915->drm.struct_mutex);
+out_locked:
+       while (reserved) {
+               struct reserved *next = reserved->next;
+
+               drm_mm_remove_node(&reserved->node);
+               kfree(reserved);
+
+               reserved = next;
+       }
+       if (drm_mm_node_allocated(&hole))
+               drm_mm_remove_node(&hole);
+       mutex_unlock(&i915->drm.struct_mutex);
+
+       return err;
+}
+
 int i915_gem_evict_mock_selftests(void)
 {
        static const struct i915_subtest tests[] = {
        drm_dev_unref(&i915->drm);
        return err;
 }
+
+int i915_gem_evict_live_selftests(struct drm_i915_private *i915)
+{
+       static const struct i915_subtest tests[] = {
+               SUBTEST(igt_evict_contexts),
+       };
+
+       return i915_subtests(tests, i915);
+}