diff mbox

Add a link training test (v2)

Message ID 1441980706-9241-1-git-send-email-ander.conselvan.de.oliveira@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Ander Conselvan de Oliveira Sept. 11, 2015, 2:11 p.m. UTC
This adds a test that compiles the link training code from i915 into a
separate executable and uses it to train a fake sink device. The test
iterates over possible combinations of max voltage swing, pre emphasis
and bit rates, with the sink attemting to get the highest values
possible. The test doesn't cover HBR2, however.

Adding several stubs was necessary to get the code to compile. Stubs are
placed in the unit-test-helper/include subdirectory. Those headers are
replacement for the kernel headers, or they perform some stubbing and
then include the original header from the kernel src tree.

The stubs for building the drm_dp_helper are not there yet, so the few
functions necessary were copied to the test. I still haven't figured out
the best way to solve this issue.

v2: Proper autotools integration (Thomas)
    Move to ./tests and make it a proper igt test. (Thomas)
    Don't pull in device_info. (Ville)
---

On Wed, 2015-09-09 at 11:33 +0100, Thomas Wood wrote:
> On 8 September 2015 at 13:28, Ander Conselvan de Oliveira
> <ander.conselvan.de.oliveira@intel.com> wrote:
> > 

> > diff --git a/link-training-test/Makefile b/link-training-test/Makefile
> 
> If this is meant to be part of the test suite, then it needs to be in
> the tests directory and use the igt test infrastructure. Otherwise it
> should be placed in tools or tools/link-training-test.

I made the test use the igt infrastructure, but I'm not sure if this is
a good fit for it. The dependency on the kernel is on build time, but
once compiled this can be run on any machine. This can also introduce
build failures if the test is not kept in sync with the driver source.
Ideally that a failure to build this would be reported as the test
failing, but I have no idea of how to achieve that.


> > --- /dev/nul
> > +++ b/link-training-test/Makefile
> > @@ -0,0 +1,40 @@
> > +KERNEL_SRC_DIR=/home/aconselv/linux
> 
> It may be necassary to make the building of this test optional in
> configure and provide a way of specifying the kernel source directory.

I did the integration with autotools. It mostly works, but on the first
build make needs to called with -k, since a .Po file isn't created for
the source file copied from i915's tree. I still need to look into this
issue.

Thanks,
Ander



 Makefile.am                                      |   4 +
 configure.ac                                     |  20 +
 tests/Makefile.am                                |  14 +
 tests/Makefile.sources                           |  11 +
 tests/unit_dp_link_training.c                    | 577 +++++++++++++++++++++++
 unit-test-helper/Makefile.am                     |   1 +
 unit-test-helper/i915/Makefile.am                |   9 +
 unit-test-helper/include/build_magic.h           |  27 ++
 unit-test-helper/include/drm/drmP.h              |  24 +
 unit-test-helper/include/drm/drm_dp_helper.h     |  14 +
 unit-test-helper/include/drm/drm_dp_mst_helper.h |   7 +
 unit-test-helper/include/linux/delay.h           |   0
 12 files changed, 708 insertions(+)
 create mode 100644 tests/unit_dp_link_training.c
 create mode 100644 unit-test-helper/Makefile.am
 create mode 100644 unit-test-helper/i915/Makefile.am
 create mode 100644 unit-test-helper/include/build_magic.h
 create mode 100644 unit-test-helper/include/drm/drmP.h
 create mode 100644 unit-test-helper/include/drm/drm_dp_helper.h
 create mode 100644 unit-test-helper/include/drm/drm_dp_mst_helper.h
 create mode 100644 unit-test-helper/include/linux/delay.h

diff --git a/unit-test-helper/include/linux/delay.h b/unit-test-helper/include/linux/delay.h
new file mode 100644
index 0000000..e69de29
diff mbox

Patch

diff --git a/Makefile.am b/Makefile.am
index 4f71a3a..5ce9d58 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -31,6 +31,10 @@  if BUILD_SHADER_DEBUGGER
 SUBDIRS += debugger
 endif
 
+if HAVE_KERNEL_SRC_DIR
+SUBDIRS += unit-test-helper
+endif
+
 if BUILD_TESTS
 SUBDIRS += tests
 endif
diff --git a/configure.ac b/configure.ac
index 19f6fa4..53e1f6d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -199,6 +199,17 @@  if test "x$with_libunwind" = xyes; then
 			  AC_MSG_ERROR([libunwind not found. Use --without-libunwind to disable libunwind support.]))
 fi
 
+AC_ARG_WITH(kernel-src-dir,
+	    AS_HELP_STRING([--with-kernel-src-dir],
+			   [Kernel src directory, used by the link training test]),
+	    [KERNEL_SRC_DIR="$withval"], [KERNEL_SRC_DIR=""])
+if test -n "$KERNEL_SRC_DIR"; then
+	I915_SRC_DIR="${KERNEL_SRC_DIR}/drivers/gpu/drm/i915"
+	AC_SUBST([KERNEL_SRC_DIR])
+	AC_SUBST([I915_SRC_DIR])
+fi
+AM_CONDITIONAL([HAVE_KERNEL_SRC_DIR], test -n "$KERNEL_SRC_DIR")
+
 # enable debug symbols
 AC_ARG_ENABLE(debug,
 	      AS_HELP_STRING([--disable-debug],
@@ -264,10 +275,16 @@  AC_CONFIG_FILES([
 		 assembler/test/Makefile
 		 assembler/intel-gen4asm.pc
 		 overlay/Makefile
+		 unit-test-helper/Makefile
+		 unit-test-helper/i915/Makefile
 		 ])
 
 AC_CONFIG_FILES([tools/intel_aubdump], [chmod +x tools/intel_aubdump])
 
+if test -n "${KERNEL_SRC_DIR}"; then
+AC_CONFIG_LINKS([unit-test-helper/kernel-src:"${KERNEL_SRC_DIR}"])
+fi
+
 AC_OUTPUT
 
 # Print a summary of the compilation
@@ -287,6 +304,9 @@  echo "       Debugger           : ${enable_debugger}"
 echo "       Python dumper      : ${DUMPER}"
 echo "       Overlay            : X: ${enable_overlay_xlib}, Xv: ${enable_overlay_xvlib}"
 echo ""
+echo " • Variables:"
+echo "       Kernel src dir     : ${KERNEL_SRC_DIR}"
+echo ""
 echo " • API-Documentation      : ${enable_gtk_doc}"
 echo ""
 
diff --git a/tests/Makefile.am b/tests/Makefile.am
index dcac2f3..9602a67 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -4,6 +4,10 @@  if HAVE_NOUVEAU
     TESTS_progs_M += $(NOUVEAU_TESTS_M)
 endif
 
+if HAVE_KERNEL_SRC_DIR
+    TESTS_progs_M += $(UNIT_TESTS_M)
+endif
+
 if BUILD_TESTS
 test-list.txt: Makefile.sources
 	@echo TESTLIST > $@
@@ -53,6 +57,11 @@  LDADD = ../lib/libintel_tools.la $(PCIACCESS_LIBS) $(DRM_LIBS) $(LIBUNWIND_LIBS)
 LDADD += $(CAIRO_LIBS) $(LIBUDEV_LIBS) $(GLIB_LIBS) -lm
 AM_CFLAGS += $(CAIRO_CFLAGS) $(LIBUDEV_CFLAGS) $(GLIB_CFLAGS)
 
+UNIT_CFLAGS = \
+	-I$(top_srcdir)/unit-test-helper/include \
+	-iquote $(I915_SRC_DIR) \
+	-iquote $(top_builddir)
+
 drm_import_export_CFLAGS = $(AM_CFLAGS) $(THREAD_CFLAGS)
 drm_import_export_LDADD = $(LDADD) -lpthread
 gem_close_race_CFLAGS = $(AM_CFLAGS) $(THREAD_CFLAGS)
@@ -91,5 +100,10 @@  prime_nv_api_CFLAGS = $(AM_CFLAGS) $(DRM_NOUVEAU_CFLAGS)
 prime_nv_api_LDADD = $(LDADD) $(DRM_NOUVEAU_LIBS)
 prime_nv_pcopy_CFLAGS = $(AM_CFLAGS) $(DRM_NOUVEAU_CFLAGS)
 prime_nv_pcopy_LDADD = $(LDADD) $(DRM_NOUVEAU_LIBS)
+
+if HAVE_KERNEL_SRC_DIR
+unit_dp_link_training_CFLAGS = $(UNIT_CFLAGS) $(AM_CFLAGS)
+endif
+
 endif
 
diff --git a/tests/Makefile.sources b/tests/Makefile.sources
index ef69299..b33a4a9 100644
--- a/tests/Makefile.sources
+++ b/tests/Makefile.sources
@@ -185,6 +185,16 @@  TESTS_scripts = \
 	tools_test \
 	$(NULL)
 
+UNIT_TESTS_M = \
+	unit_dp_link_training
+
+if HAVE_KERNEL_SRC_DIR
+unit_dp_link_training_SOURCES = \
+	unit_dp_link_training.c \
+	$(top_builddir)/unit-test-helper/i915/intel_dp_link_training.c
+endif
+
+#
 # This target contains testcases which support automagic subtest enumeration
 # from the piglit testrunner with --list-subtests and running individual
 # subtests with --run-subtest <testname>
@@ -194,6 +204,7 @@  TESTS_scripts = \
 multi_kernel_tests = \
 	$(TESTS_progs_M) \
 	$(TESTS_scripts_M) \
+	$(UNIT_TESTS_progs_M) \
 	$(NULL)
 
 # This target is for simple testcase which don't expose any subtest.
diff --git a/tests/unit_dp_link_training.c b/tests/unit_dp_link_training.c
new file mode 100644
index 0000000..683b2bf
--- /dev/null
+++ b/tests/unit_dp_link_training.c
@@ -0,0 +1,577 @@ 
+/*
+ * Copyright © 2015 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.
+ *
+ * Authors:
+ *    Ander Conselvan de Oliveira <ander.conselvan.de.oliveira@intel.com>
+ *
+ */
+
+#include "igt_core.h"
+
+#include "unit-test-helper/kernel-src/drivers/gpu/drm/i915/intel_dp.h"
+
+
+/**
+ * ARRAY_SIZE:
+ * @arr: static array
+ *
+ * Macro to compute the size of the static array @arr.
+ */
+#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
+
+
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+#define container_of(ptr, type, member) ({			\
+	const typeof(((type *)0)->member) * __mptr = (ptr);	\
+	(type *)((char *)__mptr - offsetof(type, member)); })
+
+
+struct sink_device {
+	ssize_t (*dpcd_write)(struct sink_device *sink, unsigned int offset,
+			      void *buffer, size_t size);
+	bool (*get_link_status)(struct sink_device *sink,
+				uint8_t link_status[DP_LINK_STATUS_SIZE]);
+
+	struct {
+		bool lane_count_and_bw_set;
+		bool training_pattern_1_set;
+		bool started_with_non_zero_levels;
+		bool cr_done;
+		bool channel_eq_done;
+
+		uint8_t dpcd[0x3000];
+	} data;
+};
+
+/* Fake sink device implementation */
+
+static uint8_t
+sink_device_lane_count(struct sink_device *sink)
+{
+	return sink->data.dpcd[DP_LANE_COUNT_SET];
+}
+
+static uint8_t
+sink_device_get_training_pattern(struct sink_device *sink)
+{
+	return sink->data.dpcd[DP_TRAINING_PATTERN_SET] & DP_TRAINING_PATTERN_MASK;
+}
+
+static uint8_t
+sink_device_get_voltage_swing(struct sink_device *sink, int lane)
+{
+	return sink->data.dpcd[DP_TRAINING_LANE0_SET + lane] &
+		DP_TRAIN_VOLTAGE_SWING_MASK;
+}
+
+static uint8_t
+sink_device_get_pre_emphasis_level(struct sink_device *sink, int lane)
+{
+	return (sink->data.dpcd[DP_TRAINING_LANE0_SET + lane] &
+		 DP_TRAIN_PRE_EMPHASIS_MASK) >> DP_TRAIN_PRE_EMPHASIS_SHIFT;
+}
+
+static void
+sink_device_check_lane_count_and_bw(struct sink_device *sink)
+{
+	if (sink->data.lane_count_and_bw_set)
+		return;
+
+	igt_assert(sink->data.dpcd[DP_TRAINING_PATTERN_SET] == 0);
+
+	if (sink->data.dpcd[DP_LINK_BW_SET] != 0 &&
+	    sink->data.dpcd[DP_LANE_COUNT_SET] != 0)
+		sink->data.lane_count_and_bw_set = true;
+}
+
+static void
+sink_device_check_pattern_1_set(struct sink_device *sink)
+{
+	if (!sink->data.lane_count_and_bw_set ||
+	    sink->data.training_pattern_1_set)
+		return;
+
+	igt_assert(sink_device_get_training_pattern(sink) <= DP_TRAINING_PATTERN_1);
+
+	if (sink_device_get_training_pattern(sink) != DP_TRAINING_PATTERN_1)
+		return;
+
+	igt_assert(sink->data.dpcd[DP_LINK_BW_SET] == DP_LINK_BW_1_62 ||
+	       sink->data.dpcd[DP_LINK_BW_SET] == DP_LINK_BW_2_7);
+
+	igt_assert(sink->data.dpcd[DP_LANE_COUNT_SET] == 1 ||
+		   sink->data.dpcd[DP_LANE_COUNT_SET] == 2 ||
+		   sink->data.dpcd[DP_LANE_COUNT_SET] == 4);
+
+	for (int lane = 0; lane < sink_device_lane_count(sink); lane++) {
+		if (sink_device_get_voltage_swing(sink, lane) != DP_TRAIN_VOLTAGE_SWING_LEVEL_0 ||
+		    sink_device_get_pre_emphasis_level(sink, lane) != DP_TRAIN_PRE_EMPH_LEVEL_0)
+			sink->data.started_with_non_zero_levels = true;
+	}
+
+	sink->data.training_pattern_1_set = true;
+}
+
+static void
+sink_device_check_pattern_2_set(struct sink_device *sink)
+{
+	if (!sink->data.cr_done)
+		return;
+
+	igt_assert(sink_device_get_training_pattern(sink) == DP_TRAINING_PATTERN_2);
+}
+
+static void
+sink_device_check_pattern_disable(struct sink_device *sink)
+{
+	if (!sink->data.cr_done || ! sink->data.channel_eq_done)
+		return;
+
+	igt_assert(sink_device_get_training_pattern(sink) == DP_TRAINING_PATTERN_DISABLE);
+}
+
+static ssize_t
+sink_device_dpcd_write(struct sink_device *sink, unsigned int offset,
+		       void *buffer, size_t size)
+{
+	memcpy(sink->data.dpcd + offset, buffer, size);
+
+	sink_device_check_lane_count_and_bw(sink);
+
+	if (!sink->data.cr_done)
+		sink_device_check_pattern_1_set(sink);
+	else if (!sink->data.channel_eq_done)
+		sink_device_check_pattern_2_set(sink);
+	else
+		sink_device_check_pattern_disable(sink);
+
+	return size;
+}
+
+static bool
+sink_device_max_voltage_reached(struct sink_device *sink, int lane)
+{
+	return (sink->data.dpcd[DP_TRAINING_LANE0_SET + lane] & DP_TRAIN_MAX_SWING_REACHED) ==
+		DP_TRAIN_MAX_SWING_REACHED;
+}
+
+static bool
+sink_device_max_pre_emphasis_reached(struct sink_device *sink, int lane)
+{
+	return (sink->data.dpcd[DP_TRAINING_LANE0_SET + lane] & DP_TRAIN_MAX_PRE_EMPHASIS_REACHED) ==
+		DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
+}
+
+static void
+sink_device_set_adjust_voltage(struct sink_device *sink,
+			       int lane, uint8_t level)
+{
+	int shift = DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT * (lane & 1);
+
+	sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] &=
+		~(DP_ADJUST_VOLTAGE_SWING_LANE0_MASK << shift);
+	sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] |=
+		level << shift;
+}
+
+static void
+sink_device_set_adjust_pre_emphasis(struct sink_device *sink,
+				    int lane, uint8_t level)
+{
+	int shift = DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT * (lane & 1);
+
+	sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] &=
+		~(DP_ADJUST_PRE_EMPHASIS_LANE0_MASK << shift);
+	sink->data.dpcd[DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1)] |=
+		level << (DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT + shift);
+}
+
+static bool
+sink_device_request_higher_voltage_swing(struct sink_device *sink)
+{
+	bool max_reached = false;
+
+	for (int lane = 0; lane < sink_device_lane_count(sink); lane++) {
+		if (sink_device_max_voltage_reached(sink, lane)) {
+			max_reached = true;
+			break;
+		}
+	}
+
+	if (max_reached)
+		return false;
+
+	for (int lane = 0; lane < sink_device_lane_count(sink); lane++) {
+		uint8_t new_voltage =
+			sink_device_get_voltage_swing(sink, lane) + 1;
+
+		sink_device_set_adjust_voltage(sink, lane, new_voltage);
+	}
+
+	return true;
+}
+
+static bool
+sink_device_request_higher_pre_emphasis(struct sink_device *sink)
+{
+	bool max_reached = false;
+
+	for (int lane = 0; lane < sink_device_lane_count(sink); lane++) {
+		if (sink_device_max_pre_emphasis_reached(sink, lane)) {
+			max_reached = true;
+			break;
+		}
+	}
+
+	if (max_reached)
+		return false;
+
+	for (int lane = 0; lane < sink_device_lane_count(sink); lane++) {
+		uint8_t new_pre_emphasis =
+			sink_device_get_pre_emphasis_level(sink, lane) + 1;
+
+		sink_device_set_adjust_pre_emphasis(sink, lane, new_pre_emphasis);
+	}
+
+	return true;
+}
+
+static void
+sink_device_mark_cr_done(struct sink_device *sink)
+{
+	for (int lane = 0; lane < sink_device_lane_count(sink); lane++)
+		sink->data.dpcd[DP_LANE0_1_STATUS + (lane >> 1)] |=
+			DP_LANE_CR_DONE << (4 * (lane & 1));
+
+	sink->data.cr_done = true;
+}
+
+static void
+sink_device_mark_channel_eq_done(struct sink_device *sink)
+{
+	for (int lane = 0; lane < sink_device_lane_count(sink); lane++) {
+		uint8_t mask = (DP_LANE_CHANNEL_EQ_DONE | DP_LANE_SYMBOL_LOCKED);
+		sink->data.dpcd[DP_LANE0_1_STATUS + (lane >> 1)] |=
+			mask << (4 * (lane & 1));
+	}
+
+	sink->data.dpcd[DP_LANE_ALIGN_STATUS_UPDATED] |= DP_INTERLANE_ALIGN_DONE;
+
+	sink->data.channel_eq_done = true;
+}
+
+static bool
+sink_device_get_link_status(struct sink_device *sink,
+			    uint8_t link_status[DP_LINK_STATUS_SIZE])
+{
+	if (!sink->data.cr_done) {
+		if (!sink_device_request_higher_voltage_swing(sink))
+			sink_device_mark_cr_done(sink);
+	} else if (!sink->data.channel_eq_done) {
+		if (!sink_device_request_higher_pre_emphasis(sink))
+			sink_device_mark_channel_eq_done(sink);
+	}
+
+	memcpy(link_status, sink->data.dpcd + DP_LANE0_1_STATUS,
+	       DP_LINK_STATUS_SIZE);
+
+	return true;
+}
+
+static void
+sink_device_reset(struct sink_device *sink, int lanes, uint8_t link_bw)
+{
+	memset(&sink->data, 0, sizeof sink->data);
+	sink->data.dpcd[DP_MAX_LINK_RATE] = link_bw;
+	sink->data.dpcd[DP_MAX_LANE_COUNT] = lanes;
+}
+
+static struct sink_device simple_sink = {
+	.get_link_status = sink_device_get_link_status,
+	.dpcd_write = sink_device_dpcd_write,
+};
+
+/* Glue code */
+
+struct test_intel_dp {
+	struct intel_dp dp;
+	struct sink_device *sink;
+	uint8_t link_bw;
+
+	uint8_t max_voltage;
+	uint8_t max_pre_emphasis;
+};
+
+static struct test_intel_dp *
+to_test_intel_dp(struct intel_dp *dp)
+{
+	return container_of(dp, struct test_intel_dp, dp);
+}
+
+void intel_dp_set_idle_link_train(struct intel_dp *intel_dp)
+{
+}
+
+bool intel_dp_source_supports_hbr2(struct intel_dp *intel_dp)
+{
+	return false;
+}
+
+bool
+intel_dp_get_link_status(struct intel_dp *intel_dp, uint8_t link_status[DP_LINK_STATUS_SIZE])
+{
+	struct sink_device *sink = to_test_intel_dp(intel_dp)->sink;
+	return sink->get_link_status(sink, link_status);
+}
+
+void
+intel_dp_update_signal_levels(struct intel_dp *intel_dp)
+{
+}
+
+void intel_dp_compute_rate(struct intel_dp *intel_dp, int port_clock,
+			   uint8_t *link_bw, uint8_t *rate_select)
+{
+	*link_bw = to_test_intel_dp(intel_dp)->link_bw;
+	*rate_select = 0;
+}
+
+void
+intel_dp_program_link_training_pattern(struct intel_dp *intel_dp,
+				       uint8_t dp_train_pat)
+{
+}
+
+uint8_t
+intel_dp_voltage_max(struct intel_dp *intel_dp)
+{
+	return to_test_intel_dp(intel_dp)->max_voltage <<
+		DP_TRAIN_VOLTAGE_SWING_SHIFT;
+}
+
+uint8_t
+intel_dp_pre_emphasis_max(struct intel_dp *intel_dp, uint8_t voltage_swing)
+{
+	return to_test_intel_dp(intel_dp)->max_pre_emphasis <<
+		DP_TRAIN_PRE_EMPHASIS_SHIFT;
+}
+
+ssize_t drm_dp_dpcd_write(struct drm_dp_aux *aux, unsigned int offset,
+			  void *buffer, size_t size)
+{
+	struct intel_dp *intel_dp =
+		container_of(aux, struct intel_dp, aux);
+	struct sink_device *sink = to_test_intel_dp(intel_dp)->sink;
+
+	return sink->dpcd_write(sink, offset, buffer, size);
+}
+
+/* --- */
+
+static struct test_intel_dp test_dp;
+
+static void
+do_test(struct sink_device *sink, int lanes, uint8_t link_bw,
+	     uint8_t max_voltage, uint8_t max_pre_emphasis)
+{
+	memset(&test_dp, 0, sizeof test_dp);
+	test_dp.dp.lane_count = lanes;
+	test_dp.link_bw = link_bw;
+	test_dp.sink = sink;
+	test_dp.max_voltage =
+		max_voltage >> DP_TRAIN_VOLTAGE_SWING_SHIFT;
+	test_dp.max_pre_emphasis =
+		max_pre_emphasis >> DP_TRAIN_PRE_EMPHASIS_SHIFT;
+
+	sink_device_reset(sink, lanes, link_bw);
+
+	intel_dp_start_link_train(&test_dp.dp);
+	intel_dp_stop_link_train(&test_dp.dp);
+
+	igt_assert(sink->data.cr_done);
+	igt_assert(sink->data.channel_eq_done);
+
+	for (int lane = 0; lane < test_dp.dp.lane_count; lane++) {
+		uint8_t cur_v = sink_device_get_voltage_swing(sink, lane);
+		uint8_t cur_p = sink_device_get_pre_emphasis_level(sink, lane);
+
+		igt_assert_eq(cur_v, test_dp.max_voltage);
+		igt_assert_eq(cur_p, test_dp.max_pre_emphasis);
+	}
+}
+
+int test_lanes[] = {
+	1, 2, 4,
+};
+
+uint8_t test_bw[] = {
+	DP_LINK_BW_1_62,
+	DP_LINK_BW_2_7,
+};
+
+uint8_t test_max_voltage[] = {
+	DP_TRAIN_VOLTAGE_SWING_LEVEL_0,
+	DP_TRAIN_VOLTAGE_SWING_LEVEL_1,
+	DP_TRAIN_VOLTAGE_SWING_LEVEL_2,
+	DP_TRAIN_VOLTAGE_SWING_LEVEL_3,
+};
+
+uint8_t test_max_pre_emphasis[] = {
+	DP_TRAIN_PRE_EMPH_LEVEL_0,
+	DP_TRAIN_PRE_EMPH_LEVEL_1,
+	DP_TRAIN_PRE_EMPH_LEVEL_2,
+	DP_TRAIN_PRE_EMPH_LEVEL_3,
+};
+
+igt_main
+{
+	for (int lane = 0; lane < ARRAY_SIZE(test_lanes); lane++)
+	for (int bw = 0; bw < ARRAY_SIZE(test_bw); bw++)
+	for (int voltage = 0; voltage < ARRAY_SIZE(test_max_voltage); voltage++)
+	for (int emph = 0; emph < ARRAY_SIZE(test_max_pre_emphasis); emph++)
+		igt_subtest_f("l%d-bw%d-v%d-pe%d",
+			      test_lanes[lane],
+			      test_bw[bw],
+			      test_max_voltage[voltage],
+			      test_max_pre_emphasis[emph])
+			do_test(&simple_sink,
+				test_lanes[lane],
+				test_bw[bw],
+				test_max_voltage[voltage],
+				test_max_pre_emphasis[emph]);
+}
+
+/* TODO: Get rid of this copy of drm_dp_helper functions. */
+
+/*
+ * Copyright © 2009 Keith Packard
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#include <drm/drm_dp_helper.h>
+
+static u8 dp_link_status(const u8 link_status[DP_LINK_STATUS_SIZE], int r)
+{
+        return link_status[r - DP_LANE0_1_STATUS];
+}
+
+static u8 dp_get_lane_status(const u8 link_status[DP_LINK_STATUS_SIZE],
+                             int lane)
+{
+        int i = DP_LANE0_1_STATUS + (lane >> 1);
+        int s = (lane & 1) * 4;
+        u8 l = dp_link_status(link_status, i);
+        return (l >> s) & 0xf;
+}
+
+bool drm_dp_channel_eq_ok(const u8 link_status[DP_LINK_STATUS_SIZE],
+                          int lane_count)
+{
+        u8 lane_align;
+        u8 lane_status;
+        int lane;
+
+        lane_align = dp_link_status(link_status,
+                                    DP_LANE_ALIGN_STATUS_UPDATED);
+        if ((lane_align & DP_INTERLANE_ALIGN_DONE) == 0)
+                return false; 
+        for (lane = 0; lane < lane_count; lane++) {
+                lane_status = dp_get_lane_status(link_status, lane);
+                if ((lane_status & DP_CHANNEL_EQ_BITS) != DP_CHANNEL_EQ_BITS)
+                        return false;
+        }
+        return true;
+}
+
+bool drm_dp_clock_recovery_ok(const u8 link_status[DP_LINK_STATUS_SIZE],
+                              int lane_count)
+{
+        int lane;
+        u8 lane_status;
+
+        for (lane = 0; lane < lane_count; lane++) {
+                lane_status = dp_get_lane_status(link_status, lane);
+                if ((lane_status & DP_LANE_CR_DONE) == 0)
+                        return false;
+        }
+        return true;
+}
+
+u8 drm_dp_get_adjust_request_voltage(const u8 link_status[DP_LINK_STATUS_SIZE],
+                                     int lane)
+{
+        int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
+        int s = ((lane & 1) ?
+                 DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT :
+                 DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT);
+        u8 l = dp_link_status(link_status, i);
+
+        return ((l >> s) & 0x3) << DP_TRAIN_VOLTAGE_SWING_SHIFT;
+}
+
+u8 drm_dp_get_adjust_request_pre_emphasis(const u8 link_status[DP_LINK_STATUS_SIZE],
+                                          int lane)
+{
+        int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1);
+        int s = ((lane & 1) ?
+                 DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT :
+                 DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT);
+        u8 l = dp_link_status(link_status, i);
+
+        return ((l >> s) & 0x3) << DP_TRAIN_PRE_EMPHASIS_SHIFT;
+}
+
+/* FIXME: */
+static void udelay(unsigned long usecs) {}
+static void mdelay(unsigned long msecs) {}
+
+void drm_dp_link_train_clock_recovery_delay(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) {
+        if (dpcd[DP_TRAINING_AUX_RD_INTERVAL] == 0)
+                udelay(100);
+        else
+                mdelay(dpcd[DP_TRAINING_AUX_RD_INTERVAL] * 4);
+}
+
+void drm_dp_link_train_channel_eq_delay(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) {
+        if (dpcd[DP_TRAINING_AUX_RD_INTERVAL] == 0)
+                udelay(400);
+        else
+                mdelay(dpcd[DP_TRAINING_AUX_RD_INTERVAL] * 4);
+}
+
diff --git a/unit-test-helper/Makefile.am b/unit-test-helper/Makefile.am
new file mode 100644
index 0000000..cf54f39
--- /dev/null
+++ b/unit-test-helper/Makefile.am
@@ -0,0 +1 @@ 
+SUBDIRS = i915
diff --git a/unit-test-helper/i915/Makefile.am b/unit-test-helper/i915/Makefile.am
new file mode 100644
index 0000000..cc14269
--- /dev/null
+++ b/unit-test-helper/i915/Makefile.am
@@ -0,0 +1,9 @@ 
+if HAVE_KERNEL_SRC_DIR
+
+BUILT_SOURCES = \
+	intel_dp_link_training.c
+
+%.c: $(I915_SRC_DIR)/%.c
+	cp $< $@
+
+endif
diff --git a/unit-test-helper/include/build_magic.h b/unit-test-helper/include/build_magic.h
new file mode 100644
index 0000000..a4a3836
--- /dev/null
+++ b/unit-test-helper/include/build_magic.h
@@ -0,0 +1,27 @@ 
+#ifndef _IGT_BUILD_MAGIC_H
+#define _IGT_BUILD_MAGIC_H
+
+#ifndef __KERNEL__
+#define __KERNEL__
+#endif
+
+#include "unit-test-helper/kernel-src/tools/include/linux/compiler.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+typedef unsigned long size_t;
+typedef long ssize_t;
+
+struct delayed_work {
+};
+
+struct notifier_block {
+};
+
+#endif /* _IGT_BUILD_MAGIC_H */
diff --git a/unit-test-helper/include/drm/drmP.h b/unit-test-helper/include/drm/drmP.h
new file mode 100644
index 0000000..fe48518
--- /dev/null
+++ b/unit-test-helper/include/drm/drmP.h
@@ -0,0 +1,24 @@ 
+#ifndef _IGT_DRM_P_H_
+#define _IGT_DRM_P_H_
+
+#include "igt_core.h"
+
+struct drm_device {
+};
+
+struct drm_connector {
+};
+
+struct drm_encoder {
+};
+
+struct drm_crtc {
+};
+
+struct drm_plane {
+};
+
+#define DRM_ERROR(x) igt_warn(x)
+#define DRM_DEBUG_KMS(x) igt_debug(x)
+
+#endif /* _IGT_DRM_P_H_ */
diff --git a/unit-test-helper/include/drm/drm_dp_helper.h b/unit-test-helper/include/drm/drm_dp_helper.h
new file mode 100644
index 0000000..54f297e
--- /dev/null
+++ b/unit-test-helper/include/drm/drm_dp_helper.h
@@ -0,0 +1,14 @@ 
+#ifndef _IGT_DRM_DP_HELPER_H_
+#define _IGT_DRM_DP_HELPER_H_
+
+#include <build_magic.h>
+
+struct i2c_adapter {
+};
+
+struct mutex {
+};
+
+#include "unit-test-helper/kernel-src/include/drm/drm_dp_helper.h"
+
+#endif /* _IGT_DRM_DP_HELPER_H_ */
diff --git a/unit-test-helper/include/drm/drm_dp_mst_helper.h b/unit-test-helper/include/drm/drm_dp_mst_helper.h
new file mode 100644
index 0000000..ac9d362
--- /dev/null
+++ b/unit-test-helper/include/drm/drm_dp_mst_helper.h
@@ -0,0 +1,7 @@ 
+#ifndef _IGT_DRM_DP_MST_HELPER_H_
+#define _IGT_DRM_DP_MST_HELPER_H_
+
+struct drm_dp_mst_topology_mgr {
+};
+
+#endif /* _IGT_DRM_DP_MST_HELPER_H_ */