diff --git a/lib/Makefile.sources b/lib/Makefile.sources
index 97685823894b..ef96bd09d049 100644
--- a/lib/Makefile.sources
+++ b/lib/Makefile.sources
@@ -13,6 +13,8 @@ lib_source_list =	 	\
 	i915/gem_ring.c	\
 	i915/gem_mman.c	\
 	i915/gem_mman.h	\
+	i915/gem_engine_topology.c	\
+	i915/gem_engine_topology.h	\
 	i915_3d.h		\
 	i915_reg.h		\
 	i915_pciids.h		\
diff --git a/lib/i915/gem_engine_topology.c b/lib/i915/gem_engine_topology.c
new file mode 100644
index 000000000000..dd26fae00853
--- /dev/null
+++ b/lib/i915/gem_engine_topology.c
@@ -0,0 +1,298 @@
+/*
+ * Copyright © 2019 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "drmtest.h"
+#include "ioctl_wrappers.h"
+
+#include "i915/gem_engine_topology.h"
+
+/*
+ * Limit what we support for simplicity due limitation in how much we
+ * can address via execbuf2.
+ */
+#define SIZEOF_CTX_PARAM	offsetof(struct i915_context_param_engines, \
+					 engines[GEM_MAX_ENGINES])
+#define SIZEOF_QUERY		offsetof(struct drm_i915_query_engine_info, \
+					 engines[GEM_MAX_ENGINES])
+
+#define DEFINE_CONTEXT_ENGINES_PARAM(e__, p__, c__, N__) \
+		I915_DEFINE_CONTEXT_PARAM_ENGINES(e__, N__); \
+		struct drm_i915_gem_context_param p__ = { \
+			.param = I915_CONTEXT_PARAM_ENGINES, \
+			.ctx_id = c__, \
+			.size = SIZEOF_CTX_PARAM, \
+			.value = to_user_pointer(&e__), \
+		}
+
+static int __gem_query(int fd, struct drm_i915_query *q)
+{
+	int err = 0;
+
+	if (igt_ioctl(fd, DRM_IOCTL_I915_QUERY, q))
+		err = -errno;
+
+	errno = 0;
+	return err;
+}
+
+static void gem_query(int fd, struct drm_i915_query *q)
+{
+	igt_assert_eq(__gem_query(fd, q), 0);
+}
+
+static void query_engines(int fd,
+			  struct drm_i915_query_engine_info *query_engines,
+			  int length)
+{
+	struct drm_i915_query_item item = { };
+	struct drm_i915_query query = { };
+
+	item.query_id = DRM_I915_QUERY_ENGINE_INFO;
+	query.items_ptr = to_user_pointer(&item);
+	query.num_items = 1;
+	item.length = length;
+
+	item.data_ptr = to_user_pointer(query_engines);
+
+	gem_query(fd, &query);
+}
+
+static void ctx_map_engines(int fd, struct intel_engine_data *ed,
+			    struct drm_i915_gem_context_param *param)
+{
+	struct i915_context_param_engines *engines =
+			(struct i915_context_param_engines *) param->value;
+	int i = 0;
+
+	for (typeof(engines->engines[0]) *p =
+	     &engines->engines[0];
+	     i < ed->nengines; i++, p++) {
+		p->engine_class = ed->engines[i].class;
+		p->engine_instance = ed->engines[i].instance;
+	}
+
+	param->size = offsetof(typeof(*engines), engines[i]);
+	engines->extensions = 0;
+
+	gem_context_set_param(fd, param);
+}
+
+static void init_engine(struct intel_execution_engine2 *e2,
+			int class, int instance, uint64_t flags)
+{
+	const struct intel_execution_engine2 *__e2;
+	static const char *unknown_name = "unknown",
+			  *virtual_name = "virtual";
+
+	e2->class    = class;
+	e2->instance = instance;
+	e2->flags    = flags;
+
+	/* engine is a virtual engine */
+	if (class == I915_ENGINE_CLASS_INVALID &&
+	    instance == I915_ENGINE_CLASS_INVALID_VIRTUAL) {
+		e2->name = virtual_name;
+		e2->is_virtual = true;
+		return;
+	}
+
+	__for_each_static_engine(__e2)
+		if (__e2->class == class && __e2->instance == instance)
+			break;
+
+	if (__e2->name) {
+		e2->name = __e2->name;
+	} else {
+		igt_warn("found unknown engine (%d, %d)", class, instance);
+		e2->name = unknown_name;
+		e2->flags = -1;
+	}
+
+	/* just to remark it */
+	e2->is_virtual = false;
+}
+
+static void query_engine_list(int fd, struct intel_engine_data *ed)
+{
+	uint8_t buff[SIZEOF_QUERY] = { };
+	struct drm_i915_query_engine_info *query_engine =
+			(struct drm_i915_query_engine_info *) buff;
+	int i;
+
+	query_engines(fd, query_engine, SIZEOF_QUERY);
+
+	for (i = 0; i < query_engine->num_engines; i++)
+		init_engine(&ed->engines[i],
+			    query_engine->engines[i].engine.engine_class,
+			    query_engine->engines[i].engine.engine_instance, i);
+
+	ed->nengines = query_engine->num_engines;
+}
+
+struct intel_execution_engine2 *
+intel_get_current_engine(struct intel_engine_data *ed)
+{
+	if (!ed->n)
+		ed->current_engine = &ed->engines[0];
+	else if (ed->n >= ed->nengines)
+		ed->current_engine = NULL;
+
+	return ed->current_engine;
+}
+
+void intel_next_engine(struct intel_engine_data *ed)
+{
+	if (ed->n + 1 < ed->nengines) {
+		ed->n++;
+		ed->current_engine = &ed->engines[ed->n];
+	} else {
+		ed->n = ed->nengines;
+		ed->current_engine = NULL;
+	}
+}
+
+struct intel_execution_engine2 *
+intel_get_current_physical_engine(struct intel_engine_data *ed)
+{
+	struct intel_execution_engine2 *e;
+
+	for (e = intel_get_current_engine(ed);
+	     e && e->is_virtual;
+	     intel_next_engine(ed))
+		;
+
+	return e;
+}
+
+static int gem_topology_get_param(int fd,
+				  struct drm_i915_gem_context_param *p)
+{
+	if (igt_only_list_subtests())
+		return -ENODEV;
+
+	if (__gem_context_get_param(fd, p))
+		return -1; /* using default engine map */
+
+	if (!p->size)
+		return 0;
+
+	/* size will store the engine count */
+	p->size = (p->size - sizeof(struct i915_context_param_engines)) /
+		  (offsetof(struct i915_context_param_engines,
+			    engines[1]) -
+		  sizeof(struct i915_context_param_engines));
+
+	igt_assert_f(p->size <= GEM_MAX_ENGINES, "unsupported engine count\n");
+
+	return 0;
+}
+
+struct intel_engine_data intel_init_engine_list(int fd, uint32_t ctx_id)
+{
+	DEFINE_CONTEXT_ENGINES_PARAM(engines, param, ctx_id, GEM_MAX_ENGINES);
+	struct intel_engine_data engine_data = { };
+	int i;
+
+	if (gem_topology_get_param(fd, &param)) {
+		/* if kernel does not support engine/context mapping */
+		const struct intel_execution_engine2 *e2;
+
+		igt_debug("using pre-allocated engine list\n");
+
+		__for_each_static_engine(e2) {
+			struct intel_execution_engine2 *__e2 =
+				&engine_data.engines[engine_data.nengines];
+
+			if (!igt_only_list_subtests()) {
+				__e2->flags = gem_class_instance_to_eb_flags(fd,
+						e2->class, e2->instance);
+
+				if (!gem_has_ring(fd, __e2->flags))
+					continue;
+			} else {
+				__e2->flags = -1; /* 0xfff... */
+			}
+
+			__e2->name       = e2->name;
+			__e2->instance   = e2->instance;
+			__e2->class      = e2->class;
+			__e2->is_virtual = false;
+
+			engine_data.nengines++;
+		}
+		return engine_data;
+	}
+
+	if (!param.size) {
+		query_engine_list(fd, &engine_data);
+		ctx_map_engines(fd, &engine_data, &param);
+	} else {
+		/* param.size contains the engine count */
+		for (i = 0; i < param.size; i++)
+			init_engine(&engine_data.engines[i],
+				    engines.engines[i].engine_class,
+				    engines.engines[i].engine_instance,
+				    i);
+
+		engine_data.nengines = i;
+	}
+
+	return engine_data;
+}
+
+int gem_context_lookup_engine(int fd, uint64_t engine, uint32_t ctx_id,
+			      struct intel_execution_engine2 *e)
+{
+	DEFINE_CONTEXT_ENGINES_PARAM(engines, param, ctx_id, GEM_MAX_ENGINES);
+
+	/* a bit paranoic */
+	igt_assert(e);
+
+	if (gem_topology_get_param(fd, &param) || !param.size)
+		return -EINVAL;
+
+	e->class = engines.engines[engine].engine_class;
+	e->instance = engines.engines[engine].engine_instance;
+
+	return 0;
+}
+
+void gem_context_set_all_engines(int fd, uint32_t ctx)
+{
+	DEFINE_CONTEXT_ENGINES_PARAM(engines, param, ctx, GEM_MAX_ENGINES);
+	struct intel_engine_data engine_data = { };
+
+	if (!gem_topology_get_param(fd, &param) && !param.size) {
+		query_engine_list(fd, &engine_data);
+		ctx_map_engines(fd, &engine_data, &param);
+	}
+}
+
+bool gem_has_engine_topology(int fd)
+{
+	struct drm_i915_gem_context_param param = {
+		.param = I915_CONTEXT_PARAM_ENGINES,
+	};
+
+	return !__gem_context_get_param(fd, &param);
+}
diff --git a/lib/i915/gem_engine_topology.h b/lib/i915/gem_engine_topology.h
new file mode 100644
index 000000000000..2415fd1e379b
--- /dev/null
+++ b/lib/i915/gem_engine_topology.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright © 2019 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef GEM_ENGINE_TOPOLOGY_H
+#define GEM_ENGINE_TOPOLOGY_H
+
+#include "igt_gt.h"
+#include "i915_drm.h"
+
+#define GEM_MAX_ENGINES		I915_EXEC_RING_MASK + 1
+
+struct intel_engine_data {
+	uint32_t nengines;
+	uint32_t n;
+	struct intel_execution_engine2 *current_engine;
+	struct intel_execution_engine2 engines[GEM_MAX_ENGINES];
+};
+
+bool gem_has_engine_topology(int fd);
+struct intel_engine_data intel_init_engine_list(int fd, uint32_t ctx_id);
+
+/* iteration functions */
+struct intel_execution_engine2 *
+intel_get_current_engine(struct intel_engine_data *ed);
+
+struct intel_execution_engine2 *
+intel_get_current_physical_engine(struct intel_engine_data *ed);
+
+void intel_next_engine(struct intel_engine_data *ed);
+
+int gem_context_lookup_engine(int fd, uint64_t engine, uint32_t ctx_id,
+			      struct intel_execution_engine2 *e);
+
+void gem_context_set_all_engines(int fd, uint32_t ctx);
+
+#define __for_each_static_engine(e__) \
+	for ((e__) = intel_execution_engines2; (e__)->name; (e__)++)
+
+#define for_each_context_engine(fd__, ctx__, e__) \
+	for (struct intel_engine_data i__ = intel_init_engine_list(fd__, ctx__); \
+	     ((e__) = intel_get_current_engine(&i__)); \
+	     intel_next_engine(&i__))
+
+/* needs to replace "for_each_physical_engine" when conflicts are fixed */
+#define __for_each_physical_engine(fd__, e__) \
+	for (struct intel_engine_data i__ = intel_init_engine_list(fd__, 0); \
+	     ((e__) = intel_get_current_physical_engine(&i__)); \
+	     intel_next_engine(&i__))
+
+#endif /* GEM_ENGINE_TOPOLOGY_H */
diff --git a/lib/igt.h b/lib/igt.h
index 2ae43c30ff61..82cdd57a4deb 100644
--- a/lib/igt.h
+++ b/lib/igt.h
@@ -54,5 +54,6 @@
 #include "media_spin.h"
 #include "rendercopy.h"
 #include "i915/gem_mman.h"
+#include "i915/gem_engine_topology.h"
 
 #endif /* IGT_H */
diff --git a/lib/igt_gt.h b/lib/igt_gt.h
index 475c0b3c3cc6..52b2f1ea95a5 100644
--- a/lib/igt_gt.h
+++ b/lib/igt_gt.h
@@ -95,6 +95,8 @@ extern const struct intel_execution_engine2 {
 	const char *name;
 	int class;
 	int instance;
+	uint64_t flags;
+	bool is_virtual;
 } intel_execution_engines2[];
 
 unsigned int
diff --git a/lib/meson.build b/lib/meson.build
index 8073686867d4..b6d8e6ca5a7e 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -5,6 +5,7 @@ lib_sources = [
 	'i915/gem_submission.c',
 	'i915/gem_ring.c',
 	'i915/gem_mman.c',
+	'i915/gem_engine_topology.c',
 	'igt_color_encoding.c',
 	'igt_debugfs.c',
 	'igt_device.c',
