8e4ee5e87c
We already wrap i915_vma.node.start for use with the GGTT, as there we can perform additional sanity checks that the node belongs to the GGTT and fits within the 32b registers. In the next couple of patches, we will introduce guard pages around the objects _inside_ the drm_mm_node allocation. That is we will offset the vma->pages so that the first page is at drm_mm_node.start + vma->guard (not 0 as is currently the case). All users must then not use i915_vma.node.start directly, but compute the guard offset, thus all users are converted to use a i915_vma_offset() wrapper. The notable exceptions are the selftests that are testing exact behaviour of i915_vma_pin/i915_vma_insert. Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk> Signed-off-by: Tejas Upadhyay <tejaskumarx.surendrakumar.upadhyay@intel.com> Co-developed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com> Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com> Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com> Signed-off-by: Andi Shyti <andi.shyti@linux.intel.com> Reviewed-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20221130235805.221010-3-andi.shyti@linux.intel.com
299 lines
6.0 KiB
C
299 lines
6.0 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright © 2020 Intel Corporation
|
|
*/
|
|
|
|
#include "intel_engine_pm.h"
|
|
#include "selftests/igt_flush_test.h"
|
|
|
|
static struct i915_vma *create_wally(struct intel_engine_cs *engine)
|
|
{
|
|
struct drm_i915_gem_object *obj;
|
|
struct i915_vma *vma;
|
|
u32 *cs;
|
|
int err;
|
|
|
|
obj = i915_gem_object_create_internal(engine->i915, 4096);
|
|
if (IS_ERR(obj))
|
|
return ERR_CAST(obj);
|
|
|
|
vma = i915_vma_instance(obj, engine->gt->vm, NULL);
|
|
if (IS_ERR(vma)) {
|
|
i915_gem_object_put(obj);
|
|
return vma;
|
|
}
|
|
|
|
err = i915_vma_pin(vma, 0, 0, PIN_USER | PIN_HIGH);
|
|
if (err) {
|
|
i915_gem_object_put(obj);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
err = i915_vma_sync(vma);
|
|
if (err) {
|
|
i915_gem_object_put(obj);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
cs = i915_gem_object_pin_map_unlocked(obj, I915_MAP_WC);
|
|
if (IS_ERR(cs)) {
|
|
i915_gem_object_put(obj);
|
|
return ERR_CAST(cs);
|
|
}
|
|
|
|
if (GRAPHICS_VER(engine->i915) >= 6) {
|
|
*cs++ = MI_STORE_DWORD_IMM_GEN4;
|
|
*cs++ = 0;
|
|
} else if (GRAPHICS_VER(engine->i915) >= 4) {
|
|
*cs++ = MI_STORE_DWORD_IMM_GEN4 | MI_USE_GGTT;
|
|
*cs++ = 0;
|
|
} else {
|
|
*cs++ = MI_STORE_DWORD_IMM | MI_MEM_VIRTUAL;
|
|
}
|
|
*cs++ = i915_vma_offset(vma) + 4000;
|
|
*cs++ = STACK_MAGIC;
|
|
|
|
*cs++ = MI_BATCH_BUFFER_END;
|
|
|
|
i915_gem_object_flush_map(obj);
|
|
i915_gem_object_unpin_map(obj);
|
|
|
|
vma->private = intel_context_create(engine); /* dummy residuals */
|
|
if (IS_ERR(vma->private)) {
|
|
vma = ERR_CAST(vma->private);
|
|
i915_gem_object_put(obj);
|
|
}
|
|
|
|
return vma;
|
|
}
|
|
|
|
static int context_sync(struct intel_context *ce)
|
|
{
|
|
struct i915_request *rq;
|
|
int err = 0;
|
|
|
|
rq = intel_context_create_request(ce);
|
|
if (IS_ERR(rq))
|
|
return PTR_ERR(rq);
|
|
|
|
i915_request_get(rq);
|
|
i915_request_add(rq);
|
|
|
|
if (i915_request_wait(rq, 0, HZ / 5) < 0)
|
|
err = -ETIME;
|
|
i915_request_put(rq);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int new_context_sync(struct intel_engine_cs *engine)
|
|
{
|
|
struct intel_context *ce;
|
|
int err;
|
|
|
|
ce = intel_context_create(engine);
|
|
if (IS_ERR(ce))
|
|
return PTR_ERR(ce);
|
|
|
|
err = context_sync(ce);
|
|
intel_context_put(ce);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int mixed_contexts_sync(struct intel_engine_cs *engine, u32 *result)
|
|
{
|
|
int pass;
|
|
int err;
|
|
|
|
for (pass = 0; pass < 2; pass++) {
|
|
WRITE_ONCE(*result, 0);
|
|
err = context_sync(engine->kernel_context);
|
|
if (err || READ_ONCE(*result)) {
|
|
if (!err) {
|
|
pr_err("pass[%d] wa_bb emitted for the kernel context\n",
|
|
pass);
|
|
err = -EINVAL;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
WRITE_ONCE(*result, 0);
|
|
err = new_context_sync(engine);
|
|
if (READ_ONCE(*result) != STACK_MAGIC) {
|
|
if (!err) {
|
|
pr_err("pass[%d] wa_bb *NOT* emitted after the kernel context\n",
|
|
pass);
|
|
err = -EINVAL;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
WRITE_ONCE(*result, 0);
|
|
err = new_context_sync(engine);
|
|
if (READ_ONCE(*result) != STACK_MAGIC) {
|
|
if (!err) {
|
|
pr_err("pass[%d] wa_bb *NOT* emitted for the user context switch\n",
|
|
pass);
|
|
err = -EINVAL;
|
|
}
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int double_context_sync_00(struct intel_engine_cs *engine, u32 *result)
|
|
{
|
|
struct intel_context *ce;
|
|
int err, i;
|
|
|
|
ce = intel_context_create(engine);
|
|
if (IS_ERR(ce))
|
|
return PTR_ERR(ce);
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
WRITE_ONCE(*result, 0);
|
|
err = context_sync(ce);
|
|
if (err)
|
|
break;
|
|
}
|
|
intel_context_put(ce);
|
|
if (err)
|
|
return err;
|
|
|
|
if (READ_ONCE(*result)) {
|
|
pr_err("wa_bb emitted between the same user context\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int kernel_context_sync_00(struct intel_engine_cs *engine, u32 *result)
|
|
{
|
|
struct intel_context *ce;
|
|
int err, i;
|
|
|
|
ce = intel_context_create(engine);
|
|
if (IS_ERR(ce))
|
|
return PTR_ERR(ce);
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
WRITE_ONCE(*result, 0);
|
|
err = context_sync(ce);
|
|
if (err)
|
|
break;
|
|
|
|
err = context_sync(engine->kernel_context);
|
|
if (err)
|
|
break;
|
|
}
|
|
intel_context_put(ce);
|
|
if (err)
|
|
return err;
|
|
|
|
if (READ_ONCE(*result)) {
|
|
pr_err("wa_bb emitted between the same user context [with intervening kernel]\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __live_ctx_switch_wa(struct intel_engine_cs *engine)
|
|
{
|
|
struct i915_vma *bb;
|
|
u32 *result;
|
|
int err;
|
|
|
|
bb = create_wally(engine);
|
|
if (IS_ERR(bb))
|
|
return PTR_ERR(bb);
|
|
|
|
result = i915_gem_object_pin_map_unlocked(bb->obj, I915_MAP_WC);
|
|
if (IS_ERR(result)) {
|
|
intel_context_put(bb->private);
|
|
i915_vma_unpin_and_release(&bb, 0);
|
|
return PTR_ERR(result);
|
|
}
|
|
result += 1000;
|
|
|
|
engine->wa_ctx.vma = bb;
|
|
|
|
err = mixed_contexts_sync(engine, result);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = double_context_sync_00(engine, result);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = kernel_context_sync_00(engine, result);
|
|
if (err)
|
|
goto out;
|
|
|
|
out:
|
|
intel_context_put(engine->wa_ctx.vma->private);
|
|
i915_vma_unpin_and_release(&engine->wa_ctx.vma, I915_VMA_RELEASE_MAP);
|
|
return err;
|
|
}
|
|
|
|
static int live_ctx_switch_wa(void *arg)
|
|
{
|
|
struct intel_gt *gt = arg;
|
|
struct intel_engine_cs *engine;
|
|
enum intel_engine_id id;
|
|
|
|
/*
|
|
* Exercise the inter-context wa batch.
|
|
*
|
|
* Between each user context we run a wa batch, and since it may
|
|
* have implications for user visible state, we have to check that
|
|
* we do actually execute it.
|
|
*
|
|
* The trick we use is to replace the normal wa batch with a custom
|
|
* one that writes to a marker within it, and we can then look for
|
|
* that marker to confirm if the batch was run when we expect it,
|
|
* and equally important it was wasn't run when we don't!
|
|
*/
|
|
|
|
for_each_engine(engine, gt, id) {
|
|
struct i915_vma *saved_wa;
|
|
int err;
|
|
|
|
if (!intel_engine_can_store_dword(engine))
|
|
continue;
|
|
|
|
if (IS_GRAPHICS_VER(gt->i915, 4, 5))
|
|
continue; /* MI_STORE_DWORD is privileged! */
|
|
|
|
saved_wa = fetch_and_zero(&engine->wa_ctx.vma);
|
|
|
|
intel_engine_pm_get(engine);
|
|
err = __live_ctx_switch_wa(engine);
|
|
intel_engine_pm_put(engine);
|
|
if (igt_flush_test(gt->i915))
|
|
err = -EIO;
|
|
|
|
engine->wa_ctx.vma = saved_wa;
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int intel_ring_submission_live_selftests(struct drm_i915_private *i915)
|
|
{
|
|
static const struct i915_subtest tests[] = {
|
|
SUBTEST(live_ctx_switch_wa),
|
|
};
|
|
|
|
if (to_gt(i915)->submission_method > INTEL_SUBMISSION_RING)
|
|
return 0;
|
|
|
|
return intel_gt_live_subtests(tests, to_gt(i915));
|
|
}
|