--- On Fri, 2015-09-11 at 17:11 +0300, Ander Conselvan de Oliveira wrote: > 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@xxxxxxxxx> 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. Alternatively, this could be in the kernel source tree directly. This patch adds a test subdir to the i915 source dir, containing the link training test. The test is compiled as part of the normal build using the extra-y variable so that it doesn't get linked to the final kernel. When make is run from the tests directory, a thin wrapper around the tests is built and linked to the object file compiled as part of the kernel build. Running make run_tests from the test dir runs the test and reports success or failure. Any thoughts? Ander --- drivers/gpu/drm/i915/Makefile | 2 + drivers/gpu/drm/i915/tests/Makefile | 39 ++ drivers/gpu/drm/i915/tests/empty.c | 0 drivers/gpu/drm/i915/tests/loader.c | 108 +++++ .../drm/i915/tests/test_intel_dp_link_training.c | 464 +++++++++++++++++++++ 5 files changed, 613 insertions(+) create mode 100644 drivers/gpu/drm/i915/tests/Makefile create mode 100644 drivers/gpu/drm/i915/tests/empty.c create mode 100644 drivers/gpu/drm/i915/tests/loader.c create mode 100644 drivers/gpu/drm/i915/tests/test_intel_dp_link_training.c diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile index 0851de07..7298ce5 100644 --- a/drivers/gpu/drm/i915/Makefile +++ b/drivers/gpu/drm/i915/Makefile @@ -97,6 +97,8 @@ i915-y += i915_vgpu.o # legacy horrors i915-y += i915_dma.o +obj-y += tests/ + obj-$(CONFIG_DRM_I915) += i915.o CFLAGS_i915_trace_points.o := -I$(src) diff --git a/drivers/gpu/drm/i915/tests/Makefile b/drivers/gpu/drm/i915/tests/Makefile new file mode 100644 index 0000000..3062741 --- /dev/null +++ b/drivers/gpu/drm/i915/tests/Makefile @@ -0,0 +1,39 @@ +# Please kbuild +# TODO: check for a better way to include this directory in the build +# without forcing the need for this empty file +obj-y += empty.o + + +# FIXME: The stack protector code causes tests to crash due to an improperly +# initialized %gs, so keep it disabled for now. +ccflags-y := -fstack-protector-explicit + +extra-y += test_intel_dp_link_training.o + +# If make is run from this directory +ifeq (0, $(MAKELEVEL)) + +# For strlcpy +LDFLAGS = -lbsd + +TEST_PROGS = \ + test_intel_dp_link_training + +all: tests + +tests: $(TEST_PROGS) + +run_tests: tests + @for TEST in $(TEST_PROGS); do \ + (./$$TEST && echo "$$TEST [PASS]") || echo "$$TEST [FAIL]"; \ + done + +test_%.o: test_%.c ../%.c + make -C ../../../../../ drivers/gpu/drm/i915/tests/$@ + +loader.o: loader.c + $(CC) $(CFLAGS) -c -o $@ $< + +%: %.o loader.o + $(CC) -o $@ $^ $(LDFLAGS) +endif diff --git a/drivers/gpu/drm/i915/tests/empty.c b/drivers/gpu/drm/i915/tests/empty.c new file mode 100644 index 0000000..e69de29 diff --git a/drivers/gpu/drm/i915/tests/loader.c b/drivers/gpu/drm/i915/tests/loader.c new file mode 100644 index 0000000..9e38816 --- /dev/null +++ b/drivers/gpu/drm/i915/tests/loader.c @@ -0,0 +1,108 @@ +/* + * 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@xxxxxxxxx> + * + */ + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> + +void run_test(void); + +void assert(bool condition) +{ + if (!condition) { + printf("Assert failed\n"); + abort(); + } +} + +struct { + uint8_t padding[32]; +} param_ops_int; + +unsigned int drm_debug = 0; /* 1 to enable debug output */ + +void drm_err(const char *format, ...) +{ + va_list args; + + va_start(args, format); + vprintf(format, args); + va_end(args); + +} + +void drm_ut_debug_printk(const char *function_name, const char *format, ...) +{ + va_list args; + + va_start(args, format); + printf("[%s] ", function_name); + vprintf(format, args); + va_end(args); +} + +void __const_udelay(unsigned long xloops) +{ +} + +struct mutex; +void mutex_lock_nested(struct mutex *lock, unsigned int subclass) +{ +} + +void mutex_unlock(struct mutex *lock) +{ +} + +struct lock_class_key; +void +__mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key) +{ +} + +void usleep_range(unsigned int min, unsigned int max) +{ +} + +struct i2c_adapter; +int i2c_add_adapter(struct i2c_adapter *adapter) +{ + return 0; +} + +void i2c_del_adapter(struct i2c_adapter *adapater) +{ +} + +int +main(int argc, char *argv[]) +{ + run_test(); + return 0; +} diff --git a/drivers/gpu/drm/i915/tests/test_intel_dp_link_training.c b/drivers/gpu/drm/i915/tests/test_intel_dp_link_training.c new file mode 100644 index 0000000..11868f8 --- /dev/null +++ b/drivers/gpu/drm/i915/tests/test_intel_dp_link_training.c @@ -0,0 +1,464 @@ +/* + * 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@xxxxxxxxx> + * + */ + +#include "../intel_dp_link_training.c" + +#define drm_dp_dpcd_write real_drm_dp_dpcd_write +#include "../../drm_dp_helper.c" +#undef drm_dp_dpcd_write + +void assert(bool condition); + + +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; + + 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) +{ + int lane; + + if (!sink->data.lane_count_and_bw_set || + sink->data.training_pattern_1_set) + return; + + assert(sink_device_get_training_pattern(sink) <= DP_TRAINING_PATTERN_1); + + if (sink_device_get_training_pattern(sink) != DP_TRAINING_PATTERN_1) + return; + + 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); + + 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 (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; + + 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; + + 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; + int lane; + + for (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 (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; + int lane; + + for (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 (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) +{ + int lane; + + for (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) +{ + int lane; + + for (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) +{ + int lane; + + 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); + + assert(sink->data.cr_done); + assert(sink->data.channel_eq_done); + + for (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); + + assert(cur_v == test_dp.max_voltage); + assert(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, +}; + +void +run_test(void) +{ + int lane, bw, voltage, emph; + + for (lane = 0; lane < ARRAY_SIZE(test_lanes); lane++) + for (bw = 0; bw < ARRAY_SIZE(test_bw); bw++) + for (voltage = 0; voltage < ARRAY_SIZE(test_max_voltage); voltage++) + for (emph = 0; emph < ARRAY_SIZE(test_max_pre_emphasis); emph++) { + DRM_DEBUG_KMS("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]); + } +} -- 2.4.3 _______________________________________________ Intel-gfx mailing list Intel-gfx@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/intel-gfx