Chris Wilson <chris@xxxxxxxxxxxxxxxxxx> writes: > Since batch buffers dominant execution time, most preemption requests > should naturally occur during execution of a batch buffer. We wish to > verify that should a preemption occur within a batch buffer, when we > come to restart that batch buffer, it occurs at the interrupted > instruction and most importantly does not rollback to an earlier point. > > v2: Do not clear the GPR at the start of the batch, but rely on them > being clear for new contexts. > > Suggested-by: Mika Kuoppala <mika.kuoppala@xxxxxxxxxxxxxxx> > Signed-off-by: Chris Wilson <chris@xxxxxxxxxxxxxxxxxx> > Cc: Mika Kuoppala <mika.kuoppala@xxxxxxxxxxxxxxx> > --- > drivers/gpu/drm/i915/gt/selftest_lrc.c | 329 ++++++++++++++++++++++++- > 1 file changed, 328 insertions(+), 1 deletion(-) > > diff --git a/drivers/gpu/drm/i915/gt/selftest_lrc.c b/drivers/gpu/drm/i915/gt/selftest_lrc.c > index 6f5e35afe1b2..fc3f9a248764 100644 > --- a/drivers/gpu/drm/i915/gt/selftest_lrc.c > +++ b/drivers/gpu/drm/i915/gt/selftest_lrc.c > @@ -21,7 +21,8 @@ > #include "gem/selftests/mock_context.h" > > #define CS_GPR(engine, n) ((engine)->mmio_base + 0x600 + (n) * 4) > -#define NUM_GPR_DW (16 * 2) /* each GPR is 2 dwords */ > +#define NUM_GPR 16 > +#define NUM_GPR_DW (NUM_GPR * 2) /* each GPR is 2 dwords */ > > static struct i915_vma *create_scratch(struct intel_gt *gt) > { > @@ -2791,6 +2792,331 @@ static int live_preempt_gang(void *arg) > return 0; > } > > +static struct i915_vma * > +create_gpr_user(struct intel_engine_cs *engine, > + struct i915_vma *result, > + unsigned int offset) > +{ > + struct drm_i915_gem_object *obj; > + struct i915_vma *vma; > + u32 *cs; > + int err; > + int i; > + > + obj = i915_gem_object_create_internal(engine->i915, 4096); > + if (IS_ERR(obj)) > + return ERR_CAST(obj); > + > + vma = i915_vma_instance(obj, result->vm, NULL); > + if (IS_ERR(vma)) { > + i915_gem_object_put(obj); > + return vma; > + } > + > + err = i915_vma_pin(vma, 0, 0, PIN_USER); > + if (err) { > + i915_vma_put(vma); > + return ERR_PTR(err); > + } > + > + cs = i915_gem_object_pin_map(obj, I915_MAP_WC); > + if (IS_ERR(cs)) { > + i915_vma_put(vma); > + return ERR_CAST(cs); > + } > + > + /* All GPR are clear for new contexts. We use GPR(0) as a constant */ > + *cs++ = MI_LOAD_REGISTER_IMM(1); > + *cs++ = CS_GPR(engine, 0); > + *cs++ = 1; > + > + for (i = 1; i < NUM_GPR; i++) { > + u64 addr; > + > + /* > + * Perform: GPR[i]++ > + * > + * As we read and write into the context saved GPR[i], if > + * we restart this batch buffer from an earlier point, we > + * will repeat the increment and store a value > 1. > + */ Yes, this should show restarting from wrong spot. I did ponder to put a conditional batch buffer start in the end to replay the last addition to smoketest the test. Reviewed-by: Mika Kuoppala <mika.kuoppala@xxxxxxxxxxxxxxx> > + *cs++ = MI_MATH(4); > + *cs++ = MI_MATH_LOAD(MI_MATH_REG_SRCA, MI_MATH_REG(i)); > + *cs++ = MI_MATH_LOAD(MI_MATH_REG_SRCB, MI_MATH_REG(0)); > + *cs++ = MI_MATH_ADD; > + *cs++ = MI_MATH_STORE(MI_MATH_REG(i), MI_MATH_REG_ACCU); > + > + addr = result->node.start + offset + i * sizeof(*cs); > + *cs++ = MI_STORE_REGISTER_MEM_GEN8; > + *cs++ = CS_GPR(engine, 2 * i); > + *cs++ = lower_32_bits(addr); > + *cs++ = upper_32_bits(addr); > + > + *cs++ = MI_SEMAPHORE_WAIT | > + MI_SEMAPHORE_POLL | > + MI_SEMAPHORE_SAD_GTE_SDD; > + *cs++ = i; > + *cs++ = lower_32_bits(result->node.start); > + *cs++ = upper_32_bits(result->node.start); > + } > + > + *cs++ = MI_BATCH_BUFFER_END; > + i915_gem_object_flush_map(obj); > + i915_gem_object_unpin_map(obj); > + > + return vma; > +} > + > +static struct i915_vma *create_global(struct intel_gt *gt, size_t sz) > +{ > + struct drm_i915_gem_object *obj; > + struct i915_vma *vma; > + int err; > + > + obj = i915_gem_object_create_internal(gt->i915, sz); > + if (IS_ERR(obj)) > + return ERR_CAST(obj); > + > + vma = i915_vma_instance(obj, >->ggtt->vm, NULL); > + if (IS_ERR(vma)) { > + i915_gem_object_put(obj); > + return vma; > + } > + > + err = i915_ggtt_pin(vma, 0, 0); > + if (err) { > + i915_vma_put(vma); > + return ERR_PTR(err); > + } > + > + return vma; > +} > + > +static struct i915_request * > +create_gpr_client(struct intel_engine_cs *engine, > + struct i915_vma *global, > + unsigned int offset) > +{ > + struct i915_vma *batch, *vma; > + struct intel_context *ce; > + struct i915_request *rq; > + int err; > + > + ce = intel_context_create(engine); > + if (IS_ERR(ce)) > + return ERR_CAST(ce); > + > + vma = i915_vma_instance(global->obj, ce->vm, NULL); > + if (IS_ERR(vma)) { > + err = PTR_ERR(vma); > + goto out_ce; > + } > + > + err = i915_vma_pin(vma, 0, 0, PIN_USER); > + if (err) > + goto out_ce; > + > + batch = create_gpr_user(engine, vma, offset); > + if (IS_ERR(batch)) { > + err = PTR_ERR(batch); > + goto out_vma; > + } > + > + rq = intel_context_create_request(ce); > + if (IS_ERR(rq)) { > + err = PTR_ERR(rq); > + goto out_batch; > + } > + > + i915_vma_lock(vma); > + err = i915_request_await_object(rq, vma->obj, false); > + if (!err) > + err = i915_vma_move_to_active(vma, rq, 0); > + i915_vma_unlock(vma); > + > + i915_vma_lock(batch); > + if (!err) > + err = i915_request_await_object(rq, batch->obj, false); > + if (!err) > + err = i915_vma_move_to_active(batch, rq, 0); > + if (!err) > + err = rq->engine->emit_bb_start(rq, > + batch->node.start, > + PAGE_SIZE, 0); > + i915_vma_unlock(batch); > + i915_vma_unpin(batch); > + > + if (!err) > + i915_request_get(rq); > + i915_request_add(rq); > + > +out_batch: > + i915_vma_put(batch); > +out_vma: > + i915_vma_unpin(vma); > +out_ce: > + intel_context_put(ce); > + return err ? ERR_PTR(err) : rq; > +} > + > +static int preempt_user(struct intel_engine_cs *engine, > + struct i915_vma *global, > + int id) > +{ > + struct i915_sched_attr attr = { > + .priority = I915_PRIORITY_MAX > + }; > + struct i915_request *rq; > + int err = 0; > + u32 *cs; > + > + rq = intel_engine_create_kernel_request(engine); > + if (IS_ERR(rq)) > + return PTR_ERR(rq); > + > + cs = intel_ring_begin(rq, 4); > + if (IS_ERR(cs)) { > + i915_request_add(rq); > + return PTR_ERR(cs); > + } > + > + *cs++ = MI_STORE_DWORD_IMM_GEN4 | MI_USE_GGTT; > + *cs++ = i915_ggtt_offset(global); > + *cs++ = 0; > + *cs++ = id; > + > + intel_ring_advance(rq, cs); > + > + i915_request_get(rq); > + i915_request_add(rq); > + > + engine->schedule(rq, &attr); > + > + if (i915_request_wait(rq, 0, HZ / 2) < 0) > + err = -ETIME; > + i915_request_put(rq); > + > + return err; > +} > + > +static int live_preempt_user(void *arg) > +{ > + struct intel_gt *gt = arg; > + struct intel_engine_cs *engine; > + struct i915_vma *global; > + enum intel_engine_id id; > + u32 *result; > + int err = 0; > + > + if (!HAS_LOGICAL_RING_PREEMPTION(gt->i915)) > + return 0; > + > + /* > + * In our other tests, we look at preemption in carefully > + * controlled conditions in the ringbuffer. Since most of the > + * time is spent in user batches, most of our preemptions naturally > + * occur there. We want to verify that when we preempt inside a batch > + * we continue on from the current instruction and do not roll back > + * to the start, or another earlier arbitration point. > + * > + * To verify this, we create a batch which is a mixture of > + * MI_MATH (gpr++) MI_SRM (gpr) and preemption points. Then with > + * a few preempting contexts thrown into the mix, we look for any > + * repeated instructions (which show up as incorrect values). > + */ > + > + global = create_global(gt, 4096); > + if (IS_ERR(global)) > + return PTR_ERR(global); > + > + result = i915_gem_object_pin_map(global->obj, I915_MAP_WC); > + if (IS_ERR(result)) { > + i915_vma_unpin_and_release(&global, 0); > + return PTR_ERR(result); > + } > + > + for_each_engine(engine, gt, id) { > + struct i915_request *client[3] = {}; > + struct igt_live_test t; > + int i; > + > + if (!intel_engine_has_preemption(engine)) > + continue; > + > + if (IS_GEN(gt->i915, 8) && engine->class != RENDER_CLASS) > + continue; /* we need per-context GPR */ > + > + if (igt_live_test_begin(&t, gt->i915, __func__, engine->name)) { > + err = -EIO; > + break; > + } > + > + memset(result, 0, 4096); > + > + for (i = 0; i < ARRAY_SIZE(client); i++) { > + struct i915_request *rq; > + > + rq = create_gpr_client(engine, global, > + NUM_GPR * i * sizeof(u32)); > + if (IS_ERR(rq)) > + goto end_test; > + > + client[i] = rq; > + } > + > + /* Continuously preempt the set of 3 running contexts */ > + for (i = 1; i <= NUM_GPR; i++) { > + err = preempt_user(engine, global, i); > + if (err) > + goto end_test; > + } > + > + if (READ_ONCE(result[0]) != NUM_GPR) { > + pr_err("%s: Failed to release semaphore\n", > + engine->name); > + err = -EIO; > + goto end_test; > + } > + > + for (i = 0; i < ARRAY_SIZE(client); i++) { > + int gpr; > + > + if (i915_request_wait(client[i], 0, HZ / 2) < 0) { > + err = -ETIME; > + goto end_test; > + } > + > + for (gpr = 1; gpr < NUM_GPR; gpr++) { > + if (result[NUM_GPR * i + gpr] != 1) { > + pr_err("%s: Invalid result, client %d, gpr %d, result: %d\n", > + engine->name, > + i, gpr, result[NUM_GPR * i + gpr]); > + err = -EINVAL; > + goto end_test; > + } > + } > + } > + > +end_test: > + for (i = 0; i < ARRAY_SIZE(client); i++) { > + if (!client[i]) > + break; > + > + i915_request_put(client[i]); > + } > + > + /* Flush the semaphores on error */ > + smp_store_mb(result[0], -1); > + if (igt_live_test_end(&t)) > + err = -EIO; > + if (err) > + break; > + } > + > + i915_vma_unpin_and_release(&global, I915_VMA_RELEASE_MAP); > + return err; > +} > + > static int live_preempt_timeout(void *arg) > { > struct intel_gt *gt = arg; > @@ -3998,6 +4324,7 @@ int intel_execlists_live_selftests(struct drm_i915_private *i915) > SUBTEST(live_chain_preempt), > SUBTEST(live_preempt_gang), > SUBTEST(live_preempt_timeout), > + SUBTEST(live_preempt_user), > SUBTEST(live_preempt_smoke), > SUBTEST(live_virtual_engine), > SUBTEST(live_virtual_mask), > -- > 2.20.1 _______________________________________________ Intel-gfx mailing list Intel-gfx@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/intel-gfx