diff mbox series

[i-g-t,11/21] gem_wsim: Engine map support

Message ID 20190508121058.27038-12-tvrtko.ursulin@linux.intel.com (mailing list archive)
State New, archived
Headers show
Series Media scalability tooling | expand

Commit Message

Tvrtko Ursulin May 8, 2019, 12:10 p.m. UTC
From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>

Support new i915 uAPI for configuring contexts with engine maps.

Please refer to the README file for more detailed explanation.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 benchmarks/gem_wsim.c  | 212 ++++++++++++++++++++++++++++++++++-------
 benchmarks/wsim/README |  17 +++-
 2 files changed, 192 insertions(+), 37 deletions(-)

Comments

Chris Wilson May 10, 2019, 1:26 p.m. UTC | #1
Quoting Tvrtko Ursulin (2019-05-08 13:10:48)
> From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
> 
> Support new i915 uAPI for configuring contexts with engine maps.
> 
> Please refer to the README file for more detailed explanation.
> 
> Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
> ---
> +static int parse_engine_map(struct w_step *step, const char *_str)
> +{
> +       char *token, *tctx = NULL, *tstart = (char *)_str;
> +
> +       while ((token = strtok_r(tstart, "|", &tctx))) {
> +               enum intel_engine_id engine;
> +
> +               tstart = NULL;
> +
> +               if (!strcmp(token, "DEFAULT"))
> +                       return -1;
> +               else if (!strcmp(token, "VCS"))
> +                       return -1;
> +
> +               engine = str_to_engine(token);
> +               if ((int)engine < 0)
> +                       return -1;
> +
> +               if (engine != VCS1 && engine != VCS2)
> +                       return -1; /* TODO */
> +
> +               step->engine_map_count++;
> +               step->engine_map = realloc(step->engine_map,
> +                                          step->engine_map_count *
> +                                          sizeof(step->engine_map[0]));
> +               step->engine_map[step->engine_map_count - 1] = engine;


> +               if (ctx->engine_map) {
> +                       I915_DEFINE_CONTEXT_PARAM_ENGINES(set_engines,
> +                                                         ctx->engine_map_count + 1);
> +                       struct drm_i915_gem_context_param param = {
> +                               .ctx_id = ctx_id,
> +                               .param = I915_CONTEXT_PARAM_ENGINES,
> +                               .size = sizeof(set_engines),
> +                               .value = to_user_pointer(&set_engines),
> +                       };
> +
> +                       set_engines.extensions = 0;
> +
> +                       /* Reserve slot for virtual engine. */
> +                       set_engines.engines[0].engine_class =
> +                               I915_ENGINE_CLASS_INVALID;
> +                       set_engines.engines[0].engine_instance =
> +                               I915_ENGINE_CLASS_INVALID_NONE;
> +
> +                       for (j = 1; j <= ctx->engine_map_count; j++) {
> +                               set_engines.engines[j].engine_class =
> +                                       I915_ENGINE_CLASS_VIDEO; /* FIXME */
> +                               set_engines.engines[j].engine_instance =
> +                                       ctx->engine_map[j - 1] - VCS1; /* FIXME */
> +                       }

I would suggest the file format starts with class:instance specifiers.
Too much FIXME that I think will need a file format change.
-Chris
Tvrtko Ursulin May 13, 2019, 1:18 p.m. UTC | #2
On 10/05/2019 14:26, Chris Wilson wrote:
> Quoting Tvrtko Ursulin (2019-05-08 13:10:48)
>> From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>>
>> Support new i915 uAPI for configuring contexts with engine maps.
>>
>> Please refer to the README file for more detailed explanation.
>>
>> Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>> ---
>> +static int parse_engine_map(struct w_step *step, const char *_str)
>> +{
>> +       char *token, *tctx = NULL, *tstart = (char *)_str;
>> +
>> +       while ((token = strtok_r(tstart, "|", &tctx))) {
>> +               enum intel_engine_id engine;
>> +
>> +               tstart = NULL;
>> +
>> +               if (!strcmp(token, "DEFAULT"))
>> +                       return -1;
>> +               else if (!strcmp(token, "VCS"))
>> +                       return -1;
>> +
>> +               engine = str_to_engine(token);
>> +               if ((int)engine < 0)
>> +                       return -1;
>> +
>> +               if (engine != VCS1 && engine != VCS2)
>> +                       return -1; /* TODO */
>> +
>> +               step->engine_map_count++;
>> +               step->engine_map = realloc(step->engine_map,
>> +                                          step->engine_map_count *
>> +                                          sizeof(step->engine_map[0]));
>> +               step->engine_map[step->engine_map_count - 1] = engine;
> 
> 
>> +               if (ctx->engine_map) {
>> +                       I915_DEFINE_CONTEXT_PARAM_ENGINES(set_engines,
>> +                                                         ctx->engine_map_count + 1);
>> +                       struct drm_i915_gem_context_param param = {
>> +                               .ctx_id = ctx_id,
>> +                               .param = I915_CONTEXT_PARAM_ENGINES,
>> +                               .size = sizeof(set_engines),
>> +                               .value = to_user_pointer(&set_engines),
>> +                       };
>> +
>> +                       set_engines.extensions = 0;
>> +
>> +                       /* Reserve slot for virtual engine. */
>> +                       set_engines.engines[0].engine_class =
>> +                               I915_ENGINE_CLASS_INVALID;
>> +                       set_engines.engines[0].engine_instance =
>> +                               I915_ENGINE_CLASS_INVALID_NONE;
>> +
>> +                       for (j = 1; j <= ctx->engine_map_count; j++) {
>> +                               set_engines.engines[j].engine_class =
>> +                                       I915_ENGINE_CLASS_VIDEO; /* FIXME */
>> +                               set_engines.engines[j].engine_instance =
>> +                                       ctx->engine_map[j - 1] - VCS1; /* FIXME */
>> +                       }
> 
> I would suggest the file format starts with class:instance specifiers.
> Too much FIXME that I think will need a file format change.

Where do you see the need for a file format change?

These FIXMEs can be addressed by either adding engine discovery or 
fixing the code to not assume class and engines to be balanced.

Larger rework might be needed to deal with the internal engine 
representation after adding engine discovery. Or at least an audit and 
checking legacy paths. Might be that refactor would be limited to engine 
string to internal engine id lookup.

But to change file format I don't see an immediate need. VCS is already 
defined as any VCS and there are explicit VCS1 and VCS2.

But even if the need to change it arises, I think wouldn't be a problem 
if left for later.

Regards,

Tvrtko
Chris Wilson May 13, 2019, 1:29 p.m. UTC | #3
Quoting Tvrtko Ursulin (2019-05-13 14:18:59)
> 
> On 10/05/2019 14:26, Chris Wilson wrote:
> > Quoting Tvrtko Ursulin (2019-05-08 13:10:48)
> >> From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
> >>
> >> Support new i915 uAPI for configuring contexts with engine maps.
> >>
> >> Please refer to the README file for more detailed explanation.
> >>
> >> Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
> >> ---
> >> +static int parse_engine_map(struct w_step *step, const char *_str)
> >> +{
> >> +       char *token, *tctx = NULL, *tstart = (char *)_str;
> >> +
> >> +       while ((token = strtok_r(tstart, "|", &tctx))) {
> >> +               enum intel_engine_id engine;
> >> +
> >> +               tstart = NULL;
> >> +
> >> +               if (!strcmp(token, "DEFAULT"))
> >> +                       return -1;
> >> +               else if (!strcmp(token, "VCS"))
> >> +                       return -1;
> >> +
> >> +               engine = str_to_engine(token);
> >> +               if ((int)engine < 0)
> >> +                       return -1;
> >> +
> >> +               if (engine != VCS1 && engine != VCS2)
> >> +                       return -1; /* TODO */
> >> +
> >> +               step->engine_map_count++;
> >> +               step->engine_map = realloc(step->engine_map,
> >> +                                          step->engine_map_count *
> >> +                                          sizeof(step->engine_map[0]));
> >> +               step->engine_map[step->engine_map_count - 1] = engine;
> > 
> > 
> >> +               if (ctx->engine_map) {
> >> +                       I915_DEFINE_CONTEXT_PARAM_ENGINES(set_engines,
> >> +                                                         ctx->engine_map_count + 1);
> >> +                       struct drm_i915_gem_context_param param = {
> >> +                               .ctx_id = ctx_id,
> >> +                               .param = I915_CONTEXT_PARAM_ENGINES,
> >> +                               .size = sizeof(set_engines),
> >> +                               .value = to_user_pointer(&set_engines),
> >> +                       };
> >> +
> >> +                       set_engines.extensions = 0;
> >> +
> >> +                       /* Reserve slot for virtual engine. */
> >> +                       set_engines.engines[0].engine_class =
> >> +                               I915_ENGINE_CLASS_INVALID;
> >> +                       set_engines.engines[0].engine_instance =
> >> +                               I915_ENGINE_CLASS_INVALID_NONE;
> >> +
> >> +                       for (j = 1; j <= ctx->engine_map_count; j++) {
> >> +                               set_engines.engines[j].engine_class =
> >> +                                       I915_ENGINE_CLASS_VIDEO; /* FIXME */
> >> +                               set_engines.engines[j].engine_instance =
> >> +                                       ctx->engine_map[j - 1] - VCS1; /* FIXME */
> >> +                       }
> > 
> > I would suggest the file format starts with class:instance specifiers.
> > Too much FIXME that I think will need a file format change.
> 
> Where do you see the need for a file format change?

Nah, I made the assumption the FIXMEs were because the implementation
was dictated by the file format.
 
> These FIXMEs can be addressed by either adding engine discovery or 
> fixing the code to not assume class and engines to be balanced.

The code is just obeying the .wsim; the question is how to handle a
mismatch between the file and hw -- whether to do a transparent fixup to
use bcs instead of a secondary vcs?
 
> Larger rework might be needed to deal with the internal engine 
> representation after adding engine discovery. Or at least an audit and 
> checking legacy paths. Might be that refactor would be limited to engine 
> string to internal engine id lookup.
> 
> But to change file format I don't see an immediate need. VCS is already 
> defined as any VCS and there are explicit VCS1 and VCS2.

I was more concerned in case vcs was implicit since it was heavily
assumed by the code.
-Chris
Tvrtko Ursulin May 13, 2019, 1:40 p.m. UTC | #4
On 13/05/2019 14:29, Chris Wilson wrote:
> Quoting Tvrtko Ursulin (2019-05-13 14:18:59)
>>
>> On 10/05/2019 14:26, Chris Wilson wrote:
>>> Quoting Tvrtko Ursulin (2019-05-08 13:10:48)
>>>> From: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>>>>
>>>> Support new i915 uAPI for configuring contexts with engine maps.
>>>>
>>>> Please refer to the README file for more detailed explanation.
>>>>
>>>> Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
>>>> ---
>>>> +static int parse_engine_map(struct w_step *step, const char *_str)
>>>> +{
>>>> +       char *token, *tctx = NULL, *tstart = (char *)_str;
>>>> +
>>>> +       while ((token = strtok_r(tstart, "|", &tctx))) {
>>>> +               enum intel_engine_id engine;
>>>> +
>>>> +               tstart = NULL;
>>>> +
>>>> +               if (!strcmp(token, "DEFAULT"))
>>>> +                       return -1;
>>>> +               else if (!strcmp(token, "VCS"))
>>>> +                       return -1;
>>>> +
>>>> +               engine = str_to_engine(token);
>>>> +               if ((int)engine < 0)
>>>> +                       return -1;
>>>> +
>>>> +               if (engine != VCS1 && engine != VCS2)
>>>> +                       return -1; /* TODO */
>>>> +
>>>> +               step->engine_map_count++;
>>>> +               step->engine_map = realloc(step->engine_map,
>>>> +                                          step->engine_map_count *
>>>> +                                          sizeof(step->engine_map[0]));
>>>> +               step->engine_map[step->engine_map_count - 1] = engine;
>>>
>>>
>>>> +               if (ctx->engine_map) {
>>>> +                       I915_DEFINE_CONTEXT_PARAM_ENGINES(set_engines,
>>>> +                                                         ctx->engine_map_count + 1);
>>>> +                       struct drm_i915_gem_context_param param = {
>>>> +                               .ctx_id = ctx_id,
>>>> +                               .param = I915_CONTEXT_PARAM_ENGINES,
>>>> +                               .size = sizeof(set_engines),
>>>> +                               .value = to_user_pointer(&set_engines),
>>>> +                       };
>>>> +
>>>> +                       set_engines.extensions = 0;
>>>> +
>>>> +                       /* Reserve slot for virtual engine. */
>>>> +                       set_engines.engines[0].engine_class =
>>>> +                               I915_ENGINE_CLASS_INVALID;
>>>> +                       set_engines.engines[0].engine_instance =
>>>> +                               I915_ENGINE_CLASS_INVALID_NONE;
>>>> +
>>>> +                       for (j = 1; j <= ctx->engine_map_count; j++) {
>>>> +                               set_engines.engines[j].engine_class =
>>>> +                                       I915_ENGINE_CLASS_VIDEO; /* FIXME */
>>>> +                               set_engines.engines[j].engine_instance =
>>>> +                                       ctx->engine_map[j - 1] - VCS1; /* FIXME */
>>>> +                       }
>>>
>>> I would suggest the file format starts with class:instance specifiers.
>>> Too much FIXME that I think will need a file format change.
>>
>> Where do you see the need for a file format change?
> 
> Nah, I made the assumption the FIXMEs were because the implementation
> was dictated by the file format.
>   
>> These FIXMEs can be addressed by either adding engine discovery or
>> fixing the code to not assume class and engines to be balanced.
> 
> The code is just obeying the .wsim; the question is how to handle a
> mismatch between the file and hw -- whether to do a transparent fixup to
> use bcs instead of a secondary vcs?

It would be of little use since we cannot load balance the two...

>> Larger rework might be needed to deal with the internal engine
>> representation after adding engine discovery. Or at least an audit and
>> checking legacy paths. Might be that refactor would be limited to engine
>> string to internal engine id lookup.
>>
>> But to change file format I don't see an immediate need. VCS is already
>> defined as any VCS and there are explicit VCS1 and VCS2.
> 
> I was more concerned in case vcs was implicit since it was heavily
> assumed by the code.

...and the thing I just realised I am not actually happy with is the 
lack of ability to write portable .wsim's when using engine maps.

Legacy files can configure implicit engine maps based on class (VCS), so 
I think the engine map command needs the same capability. Otherwise the 
.wsim's won't be portable. I want to be able to do:

M.1.VCS
B.1

And that to mean create engine map with all VCS class engines and enable 
load balancing. It can be achieved with legacy (implicit) load balancing 
but that cannot be tied with frame split.

Regards,

Tvrtko
diff mbox series

Patch

diff --git a/benchmarks/gem_wsim.c b/benchmarks/gem_wsim.c
index f654decb24cc..e6b7b8f5335d 100644
--- a/benchmarks/gem_wsim.c
+++ b/benchmarks/gem_wsim.c
@@ -57,6 +57,7 @@ 
 #include "ewma.h"
 
 enum intel_engine_id {
+	DEFAULT,
 	RCS,
 	BCS,
 	VCS,
@@ -81,7 +82,8 @@  enum w_type
 	SW_FENCE,
 	SW_FENCE_SIGNAL,
 	CTX_PRIORITY,
-	PREEMPTION
+	PREEMPTION,
+	ENGINE_MAP
 };
 
 struct deps
@@ -115,6 +117,10 @@  struct w_step
 		int throttle;
 		int fence_signal;
 		int priority;
+		struct {
+			unsigned int engine_map_count;
+			enum intel_engine_id *engine_map;
+		};
 	};
 
 	/* Implementation details */
@@ -142,6 +148,8 @@  DECLARE_EWMA(uint64_t, rt, 4, 2)
 struct ctx {
 	uint32_t id;
 	int priority;
+	unsigned int engine_map_count;
+	enum intel_engine_id *engine_map;
 	bool targets_instance;
 	bool wants_balance;
 	unsigned int static_vcs;
@@ -200,10 +208,10 @@  struct workload
 		int fd;
 		bool first;
 		unsigned int num_engines;
-		unsigned int engine_map[5];
+		unsigned int engine_map[NUM_ENGINES];
 		uint64_t t_prev;
-		uint64_t prev[5];
-		double busy[5];
+		uint64_t prev[NUM_ENGINES];
+		double busy[NUM_ENGINES];
 	} busy_balancer;
 };
 
@@ -234,6 +242,7 @@  static int fd;
 #define REG(x) (volatile uint32_t *)((volatile char *)igt_global_mmio + x)
 
 static const char *ring_str_map[NUM_ENGINES] = {
+	[DEFAULT] = "DEFAULT",
 	[RCS] = "RCS",
 	[BCS] = "BCS",
 	[VCS] = "VCS",
@@ -330,6 +339,37 @@  static int str_to_engine(const char *str)
 	return -1;
 }
 
+static int parse_engine_map(struct w_step *step, const char *_str)
+{
+	char *token, *tctx = NULL, *tstart = (char *)_str;
+
+	while ((token = strtok_r(tstart, "|", &tctx))) {
+		enum intel_engine_id engine;
+
+		tstart = NULL;
+
+		if (!strcmp(token, "DEFAULT"))
+			return -1;
+		else if (!strcmp(token, "VCS"))
+			return -1;
+
+		engine = str_to_engine(token);
+		if ((int)engine < 0)
+			return -1;
+
+		if (engine != VCS1 && engine != VCS2)
+			return -1; /* TODO */
+
+		step->engine_map_count++;
+		step->engine_map = realloc(step->engine_map,
+					   step->engine_map_count *
+					   sizeof(step->engine_map[0]));
+		step->engine_map[step->engine_map_count - 1] = engine;
+	}
+
+	return 0;
+}
+
 static struct workload *
 parse_workload(struct w_arg *arg, unsigned int flags, struct workload *app_w)
 {
@@ -448,6 +488,33 @@  parse_workload(struct w_arg *arg, unsigned int flags, struct workload *app_w)
 			} else if (!strcmp(field, "f")) {
 				step.type = SW_FENCE;
 				goto add_step;
+			} else if (!strcmp(field, "M")) {
+				unsigned int nr = 0;
+				while ((field = strtok_r(fstart, ".", &fctx)) !=
+				    NULL) {
+					tmp = atoi(field);
+					check_arg(nr == 0 && tmp <= 0,
+						  "Invalid context at step %u!\n",
+						  nr_steps);
+					check_arg(nr > 1,
+						  "Invalid engine map format at step %u!\n",
+						  nr_steps);
+
+					if (nr == 0) {
+						step.context = tmp;
+					} else {
+						tmp = parse_engine_map(&step,
+								       field);
+						check_arg(tmp < 0,
+							  "Invalid engine map list at step %u!\n",
+							  nr_steps);
+					}
+
+					nr++;
+				}
+
+				step.type = ENGINE_MAP;
+				goto add_step;
 			} else if (!strcmp(field, "X")) {
 				unsigned int nr = 0;
 				while ((field = strtok_r(fstart, ".", &fctx)) !=
@@ -497,9 +564,7 @@  parse_workload(struct w_arg *arg, unsigned int flags, struct workload *app_w)
 			i = str_to_engine(field);
 			check_arg(i < 0,
 				  "Invalid engine id at step %u!\n", nr_steps);
-			if (i >= 0)
-				valid++;
-
+			valid++;
 			step.engine = i;
 
 			if (step.engine == BCS)
@@ -774,6 +839,7 @@  terminate_bb(struct w_step *w, unsigned int flags)
 }
 
 static const unsigned int eb_engine_map[NUM_ENGINES] = {
+	[DEFAULT] = I915_EXEC_DEFAULT,
 	[RCS] = I915_EXEC_RENDER,
 	[BCS] = I915_EXEC_BLT,
 	[VCS] = I915_EXEC_BSD,
@@ -790,18 +856,42 @@  eb_set_engine(struct drm_i915_gem_execbuffer2 *eb,
 	if (engine == VCS2 && (flags & VCS2REMAP))
 		engine = BCS;
 
-	if ((flags & I915) && engine == VCS) {
+	if ((flags & I915) && engine == VCS)
 		eb->flags = 0;
-	} else {
+	else
 		eb->flags = eb_engine_map[engine];
+}
+
+static unsigned int
+find_engine_in_map(struct ctx *ctx, enum intel_engine_id engine)
+{
+	unsigned int i;
+
+	for (i = 0; i < ctx->engine_map_count; i++) {
+		if (ctx->engine_map[i] == engine)
+			return i + 1;
 	}
+
+	igt_assert(0);
+	return 0;
+}
+
+static struct ctx *
+__get_ctx(struct workload *wrk, struct w_step *w)
+{
+	return &wrk->ctx_list[w->context * 2];
 }
 
 static void
-eb_update_flags(struct w_step *w, enum intel_engine_id engine,
-		unsigned int flags)
+eb_update_flags(struct workload *wrk, struct w_step *w,
+		enum intel_engine_id engine, unsigned int flags)
 {
-	eb_set_engine(&w->eb, engine, flags);
+	struct ctx *ctx = __get_ctx(wrk, w);
+
+	if (ctx->engine_map)
+		w->eb.flags = find_engine_in_map(ctx, engine);
+	else
+		eb_set_engine(&w->eb, engine, flags);
 
 	w->eb.flags |= I915_EXEC_HANDLE_LUT;
 	w->eb.flags |= I915_EXEC_NO_RELOC;
@@ -820,12 +910,6 @@  get_status_objects(struct workload *wrk)
 		return wrk->status_object;
 }
 
-static struct ctx *
-__get_ctx(struct workload *wrk, struct w_step *w)
-{
-	return &wrk->ctx_list[w->context * 2];
-}
-
 static uint32_t
 get_ctxid(struct workload *wrk, struct w_step *w)
 {
@@ -895,7 +979,7 @@  alloc_step_batch(struct workload *wrk, struct w_step *w, unsigned int flags)
 		engine = VCS2;
 	else if (flags & SWAPVCS && engine == VCS2)
 		engine = VCS1;
-	eb_update_flags(w, engine, flags);
+	eb_update_flags(wrk, w, engine, flags);
 #ifdef DEBUG
 	printf("%u: %u:|", w->idx, w->eb.buffer_count);
 	for (i = 0; i <= j; i++)
@@ -918,7 +1002,7 @@  static void __ctx_set_prio(uint32_t ctx_id, unsigned int prio)
 		gem_context_set_param(fd, &param);
 }
 
-static void
+static int
 prepare_workload(unsigned int id, struct workload *wrk, unsigned int flags)
 {
 	unsigned int ctx_vcs = 0;
@@ -979,30 +1063,53 @@  prepare_workload(unsigned int id, struct workload *wrk, unsigned int flags)
 	/*
 	 * Identify if contexts target specific engine instances and if they
 	 * want to be balanced.
+	 *
+	 * Transfer over engine map configuration from the workload step.
 	 */
 	for (j = 0; j < wrk->nr_ctxs; j += 2) {
 		bool targets = false;
 		bool balance = false;
 
 		for (i = 0, w = wrk->steps; i < wrk->nr_steps; i++, w++) {
-			if (w->type != BATCH)
-				continue;
-
 			if (w->context != (j / 2))
 				continue;
 
-			if (w->engine == VCS)
-				balance = true;
-			else
-				targets = true;
+			if (w->type == BATCH) {
+				if (w->engine == VCS)
+					balance = true;
+				else
+					targets = true;
+			} else if (w->type == ENGINE_MAP) {
+				wrk->ctx_list[j].engine_map = w->engine_map;
+				wrk->ctx_list[j].engine_map_count =
+					w->engine_map_count;
+			}
 		}
 
-		if (flags & I915) {
-			wrk->ctx_list[j].targets_instance = targets;
+		wrk->ctx_list[j].targets_instance = targets;
+		if (flags & I915)
 			wrk->ctx_list[j].wants_balance = balance;
+	}
+
+	/*
+	 * Ensure VCS is not allowed with engine map contexts.
+	 */
+	for (j = 0; j < wrk->nr_ctxs; j += 2) {
+		for (i = 0, w = wrk->steps; i < wrk->nr_steps; i++, w++) {
+			if (w->context != (j / 2))
+				continue;
+
+			if (w->type != BATCH)
+				continue;
+
+			if (wrk->ctx_list[j].engine_map && w->engine == VCS) {
+				wsim_err("Batches targetting engine maps must use explicit engines!\n");
+				return -1;
+			}
 		}
 	}
 
+
 	/*
 	 * Create and configure contexts.
 	 */
@@ -1013,7 +1120,7 @@  prepare_workload(unsigned int id, struct workload *wrk, unsigned int flags)
 		if (ctx->id)
 			continue;
 
-		if (flags & I915) {
+		if ((flags & I915) || ctx->engine_map) {
 			struct drm_i915_gem_context_create_ext_setparam ext = {
 				.base.name = I915_CONTEXT_CREATE_EXT_SETPARAM,
 				.param.param = I915_CONTEXT_PARAM_VM,
@@ -1043,7 +1150,7 @@  prepare_workload(unsigned int id, struct workload *wrk, unsigned int flags)
 				break;
 			}
 
-			if (!ctx->targets_instance)
+			if (!ctx->engine_map && !ctx->targets_instance)
 				args.flags |=
 				     I915_CONTEXT_CREATE_FLAGS_SINGLE_TIMELINE;
 
@@ -1076,7 +1183,7 @@  prepare_workload(unsigned int id, struct workload *wrk, unsigned int flags)
 		 * both want to target specific engines and be balanced by i915?
 		 */
 		if ((flags & I915) && ctx->wants_balance &&
-		    ctx->targets_instance) {
+		    ctx->targets_instance && !ctx->engine_map) {
 			struct drm_i915_gem_context_create_ext_setparam ext = {
 				.base.name = I915_CONTEXT_CREATE_EXT_SETPARAM,
 				.param.param = I915_CONTEXT_PARAM_VM,
@@ -1101,7 +1208,33 @@  prepare_workload(unsigned int id, struct workload *wrk, unsigned int flags)
 			__ctx_set_prio(ctx_id, wrk->prio);
 		}
 
-		if (ctx->wants_balance) {
+		if (ctx->engine_map) {
+			I915_DEFINE_CONTEXT_PARAM_ENGINES(set_engines,
+							  ctx->engine_map_count + 1);
+			struct drm_i915_gem_context_param param = {
+				.ctx_id = ctx_id,
+				.param = I915_CONTEXT_PARAM_ENGINES,
+				.size = sizeof(set_engines),
+				.value = to_user_pointer(&set_engines),
+			};
+
+			set_engines.extensions = 0;
+
+			/* Reserve slot for virtual engine. */
+			set_engines.engines[0].engine_class =
+				I915_ENGINE_CLASS_INVALID;
+			set_engines.engines[0].engine_instance =
+				I915_ENGINE_CLASS_INVALID_NONE;
+
+			for (j = 1; j <= ctx->engine_map_count; j++) {
+				set_engines.engines[j].engine_class =
+					I915_ENGINE_CLASS_VIDEO; /* FIXME */
+				set_engines.engines[j].engine_instance =
+					ctx->engine_map[j - 1] - VCS1; /* FIXME */
+			}
+
+			gem_context_set_param(fd, &param);
+		} else if (ctx->wants_balance) {
 			I915_DEFINE_CONTEXT_ENGINES_LOAD_BALANCE(load_balance, 2) = {
 				.base.name = I915_CONTEXT_ENGINES_EXT_LOAD_BALANCE,
 				.num_siblings = 2,
@@ -1181,6 +1314,8 @@  prepare_workload(unsigned int id, struct workload *wrk, unsigned int flags)
 
 		alloc_step_batch(wrk, w, _flags);
 	}
+
+	return 0;
 }
 
 static double elapsed(const struct timespec *start, const struct timespec *end)
@@ -1918,7 +2053,7 @@  do_eb(struct workload *wrk, struct w_step *w, enum intel_engine_id engine,
 	uint32_t seqno = new_seqno(wrk, engine);
 	unsigned int i;
 
-	eb_update_flags(w, engine, flags);
+	eb_update_flags(wrk, w, engine, flags);
 
 	if (flags & SEQNO)
 		update_bb_seqno(w, engine, seqno);
@@ -2067,7 +2202,8 @@  static void *run_workload(void *data)
 								    w->priority;
 				}
 				continue;
-			} else if (w->type == PREEMPTION) {
+			} else if (w->type == PREEMPTION ||
+				   w->type == ENGINE_MAP) {
 				continue;
 			}
 
@@ -2625,7 +2761,11 @@  int main(int argc, char **argv)
 		w[i]->print_stats = verbose > 1 ||
 				    (verbose > 0 && master_workload == i);
 
-		prepare_workload(i, w[i], flags_);
+		if (prepare_workload(i, w[i], flags_)) {
+			wsim_err("Failed to prepare workload %u!\n", i);
+			return 1;
+		}
+
 
 		if (balancer && balancer->init) {
 			int ret = balancer->init(balancer, w[i]);
diff --git a/benchmarks/wsim/README b/benchmarks/wsim/README
index 4786f116b4ac..4b14aa28bfa7 100644
--- a/benchmarks/wsim/README
+++ b/benchmarks/wsim/README
@@ -3,6 +3,7 @@  Workload descriptor format
 
 ctx.engine.duration_us.dependency.wait,...
 <uint>.<str>.<uint>[-<uint>].<int <= 0>[/<int <= 0>][...].<0|1>,...
+M.<uint>.<str>[|<str>]...
 P|X.<uint>.<int>
 d|p|s|t|q|a.<int>,...
 f
@@ -23,10 +24,11 @@  Additional workload steps are also supported:
  'q' - Throttle to n max queue depth.
  'f' - Create a sync fence.
  'a' - Advance the previously created sync fence.
+ 'M' - Set up engine map.
  'P' - Context priority.
  'X' - Context preemption control.
 
-Engine ids: RCS, BCS, VCS, VCS1, VCS2, VECS
+Engine ids: DEFAULT, RCS, BCS, VCS, VCS1, VCS2, VECS
 
 Example (leading spaces must not be present in the actual file):
 ----------------------------------------------------------------
@@ -161,3 +163,16 @@  The same context is then marked to have batches which can be preempted every
 
 Same as with context priority, context preemption commands are valid until
 optionally overriden by another preemption control change on the same context.
+
+Engine maps
+-----------
+
+Engine maps are a per context feature which changes the way engine selection is
+done in the driver.
+
+Example:
+
+  M.1.VCS1|VCS2
+
+This sets up context 1 with an engine map containing VCS1 and VCS2 engine.
+Submission to this context can now only reference these two engines.