[PATCH i-g-t 11/12] tests/intel/kms_dp_fallback: add test for validating fallback
Kunal Joshi
kunal1.joshi at intel.com
Mon Aug 19 09:26:14 UTC 2024
add test to valdiate fallback for DP connector,
eDP subtest will be added later.
How does test validates fallback?
- test start by doing initial modeset on default mode
(if connector is DP then we enable just that connector,
if its DP-MST we enable all on the same topology)
- force link training failures and retrain until we reach
lowest param or retrain is disabled
- expect hotplug and link-status to turn bad
- expect link params reduce after fallback
Signed-off-by: Kunal Joshi <kunal1.joshi at intel.com>
Suggested-by: Imre Deak <imre.deak at intel.com>
---
tests/intel/kms_fallback.c | 555 +++++++++++++++++++++++++++++++++++++
tests/meson.build | 1 +
2 files changed, 556 insertions(+)
create mode 100644 tests/intel/kms_fallback.c
diff --git a/tests/intel/kms_fallback.c b/tests/intel/kms_fallback.c
new file mode 100644
index 000000000..ad6865cb1
--- /dev/null
+++ b/tests/intel/kms_fallback.c
@@ -0,0 +1,555 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright © 2024 Intel Corporation
+ */
+
+/**
+ * TEST: kms fallback
+ * Category: Display
+ * Description: Test link training fallback for eDP/DP connectors
+ * Driver requirement: i915, xe
+ * Functionality: link training
+ * Mega feature: General Display Features
+ * Test category: functionality test
+ */
+
+#include <sys/types.h>
+
+#include "igt.h"
+#include "igt_psr.h"
+
+/**
+ * SUBTEST: dp-fallback
+ * Description: Test fallback on DP connectors
+ */
+
+#define RETRAIN_COUNT 1
+#define LT_FAILURE_SAME_CAPS 1
+#define LT_FAILURE_REDUCED_CAPS 2
+
+static int traversed_mst_outputs[IGT_MAX_PIPES];
+static int traversed_mst_output_count;
+typedef struct {
+ int drm_fd;
+ igt_display_t display;
+ drmModeModeInfo *mode;
+ igt_output_t *output;
+ enum pipe pipe;
+ struct igt_fb fb;
+ struct igt_plane *primary;
+ int n_pipes;
+} data_t;
+
+typedef int (*condition_check_fn)(int drm_fd, igt_output_t *output);
+
+IGT_TEST_DESCRIPTION("Test link training fallback");
+
+static const char *str_link_rate(enum dp_link_rate link_rate)
+{
+ switch (link_rate) {
+ case DP_LINK_RATE_162000:
+ return "1.62 Gbps";
+ case DP_LINK_RATE_216000:
+ return "2.16 Gbps";
+ case DP_LINK_RATE_243000:
+ return "2.43 Gbps";
+ case DP_LINK_RATE_270000:
+ return "2.70 Gbps";
+ case DP_LINK_RATE_324000:
+ return "3.24 Gbps";
+ case DP_LINK_RATE_432000:
+ return "4.32 Gbps";
+ case DP_LINK_RATE_540000:
+ return "5.40 Gbps";
+ case DP_LINK_RATE_675000:
+ return "6.75 Gbps";
+ case DP_LINK_RATE_810000:
+ return "8.10 Gbps";
+ case DP_LINK_RATE_1000000:
+ return "10.00 Gbps";
+ case DP_LINK_RATE_1350000:
+ return "13.50 Gbps";
+ case DP_LINK_RATE_2000000:
+ return "20.00 Gbps";
+ default:
+ igt_assert_f(0, "Invalid link rate %d\n", link_rate);
+ }
+}
+
+static const char *str_link_to_write(enum dp_link_rate link_rate)
+{
+ switch (link_rate) {
+ case DP_LINK_RATE_162000:
+ return "162000";
+ case DP_LINK_RATE_216000:
+ return "216000";
+ case DP_LINK_RATE_243000:
+ return "243000";
+ case DP_LINK_RATE_270000:
+ return "270000";
+ case DP_LINK_RATE_324000:
+ return "324000";
+ case DP_LINK_RATE_432000:
+ return "432000";
+ case DP_LINK_RATE_540000:
+ return "540000";
+ case DP_LINK_RATE_675000:
+ return "675000";
+ case DP_LINK_RATE_810000:
+ return "810000";
+ case DP_LINK_RATE_1000000:
+ return "1000000";
+ case DP_LINK_RATE_1350000:
+ return "1350000";
+ case DP_LINK_RATE_2000000:
+ return "2000000";
+ default:
+ igt_assert_f(0, "Invalid link rate %d\n", link_rate);
+ }
+}
+
+static const char *str_lane_count(enum dp_lane_count lane_count)
+{
+ switch (lane_count) {
+ case DP_LANE_COUNT_1:
+ return "1";
+ case DP_LANE_COUNT_2:
+ return "2";
+ case DP_LANE_COUNT_4:
+ return "4";
+ default:
+ igt_assert_f(0, "Invalid lane count %d\n", lane_count);
+ }
+}
+
+static void find_mst_outputs(int drm_fd, data_t *data,
+ igt_output_t *output,
+ igt_output_t **mst_outputs,
+ int *num_mst_outputs)
+{
+ bool is_output_mst;
+ uint64_t path_blob_id;
+ igt_output_t *connector_output;
+ drmModePropertyPtr path_prop = NULL;
+ drmModePropertyPtr connector_path_prop = NULL;
+
+ igt_assert_f(output, "Invalid output\n");
+
+ /*
+ * Check if given output is MST by checking if it has PATH property
+ */
+ is_output_mst = kmstest_get_property(drm_fd,
+ output->config.connector->connector_id,
+ DRM_MODE_OBJECT_CONNECTOR, "PATH", NULL,
+ &path_blob_id, &path_prop);
+
+ if (!is_output_mst)
+ return;
+
+ /*
+ * If output is MST check all other connected output which shares
+ * same path and fill mst_outputs and num_mst_outputs
+ */
+ for_each_connected_output(&data->display, connector_output) {
+
+ connector_path_prop = NULL;
+
+ kmstest_get_property(drm_fd,
+ connector_output->config.connector->connector_id,
+ DRM_MODE_OBJECT_CONNECTOR, "PATH",
+ NULL, &path_blob_id,
+ &connector_path_prop);
+
+ if (connector_path_prop && path_prop &&
+ connector_path_prop->prop_id == path_prop->prop_id)
+ mst_outputs[(*num_mst_outputs)++] = connector_output;
+
+ if (connector_path_prop)
+ drmModeFreeProperty(connector_path_prop);
+ }
+ if (path_prop)
+ drmModeFreeProperty(path_prop);
+}
+
+static bool setup_mst_outputs(data_t *data, igt_output_t *mst_output[],
+ int *dp_mst_outputs)
+{
+ int i;
+ igt_output_t *output;
+
+ igt_require_f(igt_check_output_is_dp_mst(data->output),
+ "Not a valid MST connector\n");
+
+ /*
+ * Check if this is already traversed
+ */
+ for (i = 0; i < traversed_mst_output_count; i++)
+ if (traversed_mst_outputs[i] == data->output->config.connector->connector_id)
+ return false;
+
+ find_mst_outputs(data->drm_fd, data, data->output,
+ mst_output, dp_mst_outputs);
+
+ for (i = 0; i < *dp_mst_outputs; i++) {
+ output = mst_output[i];
+ traversed_mst_outputs[traversed_mst_output_count++] = output->config.connector->connector_id;
+ igt_info("Output %s is in same topology as %s\n",
+ igt_output_name(output),
+ igt_output_name(data->output));
+ }
+
+ return true;
+}
+
+static void setup_pipe_on_mst_outputs(data_t *data,
+ igt_output_t *mst_output[],
+ int *dp_mst_outputs)
+{
+ int i = 0;
+
+ igt_require_f(data->n_pipes >= *dp_mst_outputs,
+ "Need %d pipes to assign to %d MST outputs\n",
+ data->n_pipes, *dp_mst_outputs);
+
+ for_each_pipe(&data->display, data->pipe) {
+ if (i >= *dp_mst_outputs)
+ break;
+ igt_info("Setting pipe %s on output %s\n",
+ kmstest_pipe_name(data->pipe),
+ igt_output_name(mst_output[i]));
+ igt_output_set_pipe(mst_output[i++], data->pipe);
+ }
+}
+
+static void setup_modeset_on_mst_outputs(data_t *data,
+ igt_output_t *mst_output[],
+ int *dp_mst_outputs,
+ drmModeModeInfo *mode[],
+ struct igt_fb fb[],
+ struct igt_plane *primary[])
+{
+ int i;
+
+ for (i = 0; i < *dp_mst_outputs; i++) {
+ mst_output[i]->force_reprobe = true;
+ igt_output_refresh(mst_output[i]);
+ mode[i] = igt_output_get_mode(mst_output[i]);
+ igt_info("Mode %dx%d@%d on output %s\n",
+ mode[i]->hdisplay, mode[i]->vdisplay,
+ mode[i]->vrefresh,
+ igt_output_name(mst_output[i]));
+ primary[i] = igt_output_get_plane_type(mst_output[i],
+ DRM_PLANE_TYPE_PRIMARY);
+ igt_create_color_fb(data->drm_fd,
+ mode[i]->hdisplay,
+ mode[i]->vdisplay,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_MOD_LINEAR, 0.0, 1.0, 0.0,
+ &fb[i]);
+ igt_plane_set_fb(primary[i], &fb[i]);
+ }
+}
+
+static bool validate_modeset_mst_output(data_t *data,
+ igt_output_t *mst_output[],
+ int *dp_mst_outputs,
+ drmModeModeInfo *mode[],
+ struct igt_fb fb[],
+ struct igt_plane *primary[])
+{
+ int ret;
+
+ igt_require_f(*dp_mst_outputs > 0, "No MST outputs found\n");
+ setup_pipe_on_mst_outputs(data, mst_output, dp_mst_outputs);
+ setup_modeset_on_mst_outputs(data, mst_output,
+ dp_mst_outputs,
+ mode, fb, primary);
+ if (igt_display_try_commit_atomic(&data->display,
+ DRM_MODE_ATOMIC_TEST_ONLY |
+ DRM_MODE_ATOMIC_ALLOW_MODESET,
+ NULL) != 0) {
+ bool found = igt_override_all_active_output_modes_to_fit_bw(&data->display);
+
+ igt_require_f(found,
+ "No valid mode combo found for MST modeset\n");
+ ret = igt_display_try_commit2(&data->display, COMMIT_ATOMIC);
+ igt_require_f(ret == 0,
+ "Commit failure during MST modeset\n");
+ }
+ return true;
+}
+
+static bool setup_mst(data_t *data, bool is_mst,
+ igt_output_t *mst_output[],
+ int *dp_mst_outputs, drmModeModeInfo *mode[],
+ struct igt_fb fb[], struct igt_plane *primary[])
+{
+ bool ret;
+
+ igt_display_reset(&data->display);
+ igt_display_commit2(&data->display, COMMIT_ATOMIC);
+
+ *dp_mst_outputs = 0;
+ ret = setup_mst_outputs(data, mst_output, dp_mst_outputs);
+ if (!ret) {
+ igt_info("Skipping MST output %s as already tested\n",
+ igt_output_name(data->output));
+ return false;
+ }
+
+ ret = validate_modeset_mst_output(data, mst_output,
+ dp_mst_outputs, mode,
+ fb, primary);
+ if (!ret) {
+ igt_info("Skipping MST output %s as validpipe/output combo not found\n",
+ igt_output_name(data->output));
+ return false;
+ }
+
+ igt_display_commit2(&data->display, COMMIT_ATOMIC);
+ return true;
+}
+
+static void set(data_t *data, enum dp_link_rate link_rate,
+ enum dp_lane_count lane_count)
+{
+ kmstest_force_connector_link_rate(data->drm_fd,
+ data->output->config.connector,
+ str_link_to_write(link_rate));
+ kmstest_force_connector_lane_count(data->drm_fd,
+ data->output->config.connector,
+ str_lane_count(lane_count));
+ kmstest_force_connector_retrain(data->drm_fd,
+ data->output->config.connector,
+ 1);
+}
+
+static int check_condition_with_timeout(int drm_fd, igt_output_t *output,
+ condition_check_fn check_fn,
+ double interval, double timeout)
+{
+ struct timespec start_time, current_time;
+ double elapsed_time;
+
+ clock_gettime(CLOCK_MONOTONIC, &start_time);
+
+ while (1) {
+ if (igt_get_dp_pending_retrain(drm_fd, output) == 0) {
+ return 0;
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, ¤t_time);
+ elapsed_time = (current_time.tv_sec - start_time.tv_sec) +
+ (current_time.tv_nsec - start_time.tv_nsec) / 1e9;
+
+ if (elapsed_time >= timeout) {
+ return -1;
+ }
+
+ usleep((useconds_t)(interval * 1000000));
+ }
+}
+
+static void test_fallback(data_t *data, bool is_mst)
+{
+ int dp_mst_outputs;
+ igt_output_t *mst_outputs[IGT_MAX_PIPES];
+ enum dp_link_rate link_rate, curr_link_rate, prev_link_rate;
+ enum dp_lane_count lane_count, curr_lane_count, prev_lane_count;
+ uint32_t link_status_prop_id;
+ uint64_t link_status_value;
+ drmModeModeInfo *mst_modes[IGT_MAX_PIPES], *mode;
+ drmModePropertyPtr link_status_prop;
+ struct igt_fb mst_fbs[IGT_MAX_PIPES], fb;
+ struct igt_plane *mst_primarys[IGT_MAX_PIPES], *primary;
+ struct udev_monitor *mon;
+
+ if (is_mst) {
+ if (!setup_mst(data, is_mst, mst_outputs,
+ &dp_mst_outputs, mst_modes, mst_fbs,
+ mst_primarys))
+ return;
+ } else {
+ igt_display_reset(&data->display);
+ data->pipe = PIPE_A;
+ igt_output_set_pipe(data->output, data->pipe);
+ mode = igt_output_get_mode(data->output);
+ primary = igt_output_get_plane_type(data->output,
+ DRM_PLANE_TYPE_PRIMARY);
+ igt_create_color_fb(data->drm_fd,
+ mode->hdisplay, mode->vdisplay,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_MOD_LINEAR, 0.0, 1.0, 0.0,
+ &fb);
+ igt_plane_set_fb(primary, &fb);
+ igt_display_commit2(&data->display, COMMIT_ATOMIC);
+ }
+
+ igt_info("Testing link training fallback on %s\n",
+ igt_output_name(data->output));
+
+ link_rate = igt_get_dp_max_link_rate(data->drm_fd, data->output);
+ lane_count = igt_get_dp_max_lane_count(data->drm_fd, data->output);
+ set(data, link_rate, lane_count);
+
+ igt_assert_eq(check_condition_with_timeout(data->drm_fd,
+ data->output,
+ igt_get_dp_pending_retrain,
+ 1.0, 20.0), 0);
+
+ while (!igt_get_dp_link_retrain_disabled(data->drm_fd,
+ data->output)) {
+
+ prev_link_rate = igt_get_dp_link_rate_set_for_output(data->drm_fd, data->output);
+ prev_lane_count = igt_get_dp_lane_count_set_for_output(data->drm_fd, data->output);
+
+ igt_info("Current link rate: %s, Current lane count: %s\n",
+ str_link_rate(prev_link_rate),
+ str_lane_count(prev_lane_count));
+ mon = igt_watch_uevents();
+ igt_force_lt_failure(data->drm_fd, data->output,
+ LT_FAILURE_REDUCED_CAPS);
+ igt_force_link_retrain(data->drm_fd, data->output,
+ RETRAIN_COUNT);
+
+ igt_assert_eq(check_condition_with_timeout(data->drm_fd,
+ data->output,
+ igt_get_dp_pending_retrain,
+ 1.0, 20.0), 0);
+ igt_assert_eq(check_condition_with_timeout(data->drm_fd,
+ data->output,
+ igt_get_dp_pending_lt_failures,
+ 1.0, 20.0), 0);
+
+ if (igt_get_dp_link_retrain_disabled(data->drm_fd,
+ data->output)) {
+ igt_reset_connectors();
+ return;
+ }
+
+ igt_assert_f(igt_hotplug_detected(mon, 20),
+ "Didn't get hotplug for force link training failure\n");
+
+ kmstest_get_property(data->drm_fd,
+ data->output->config.connector->connector_id,
+ DRM_MODE_OBJECT_CONNECTOR, "link-status",
+ &link_status_prop_id, &link_status_value,
+ &link_status_prop);
+
+ igt_assert_eq(link_status_value, DRM_MODE_LINK_STATUS_BAD);
+
+ igt_flush_uevents(mon);
+
+ igt_display_reset(&data->display);
+ igt_display_commit2(&data->display, COMMIT_ATOMIC);
+
+ if (is_mst) {
+ igt_assert_f(validate_modeset_mst_output(data,
+ mst_outputs,
+ &dp_mst_outputs,
+ mst_modes,
+ mst_fbs,
+ mst_primarys),
+ "MST modeset failed\n");
+ } else {
+ data->output->force_reprobe = true;
+ igt_output_refresh(data->output);
+ data->pipe = PIPE_A;
+ igt_output_set_pipe(data->output, data->pipe);
+ mode = igt_output_get_mode(data->output);
+ igt_info("Mode %dx%d@%d on output %s\n",
+ mode->hdisplay, mode->vdisplay,
+ mode->vrefresh,
+ igt_output_name(data->output));
+ primary = igt_output_get_plane_type(data->output,
+ DRM_PLANE_TYPE_PRIMARY);
+ igt_create_color_fb(data->drm_fd,
+ mode->hdisplay,
+ mode->vdisplay,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_MOD_LINEAR,
+ 0.0, 1.0, 0.0, &fb);
+ igt_plane_set_fb(primary, &fb);
+ }
+
+ kmstest_set_connector_link_status(data->drm_fd,
+ data->output->config.connector,
+ DRM_MODE_LINK_STATUS_GOOD);
+ igt_display_commit2(&data->display, COMMIT_ATOMIC);
+
+ kmstest_get_property(data->drm_fd,
+ data->output->config.connector->connector_id,
+ DRM_MODE_OBJECT_CONNECTOR, "link-status",
+ &link_status_prop_id, &link_status_value,
+ &link_status_prop);
+ igt_assert_eq(link_status_value, DRM_MODE_LINK_STATUS_GOOD);
+
+ curr_link_rate = igt_get_dp_link_rate_set_for_output(data->drm_fd, data->output);
+ curr_lane_count = igt_get_dp_lane_count_set_for_output(data->drm_fd, data->output);
+
+ igt_assert_f(curr_link_rate < prev_link_rate ||
+ curr_lane_count < prev_lane_count,
+ "Fallback unsuccessful\n");
+
+ prev_link_rate = curr_link_rate;
+ prev_lane_count = curr_lane_count;
+ }
+}
+
+igt_main
+{
+ data_t data = {};
+
+ igt_fixture {
+ data.drm_fd = drm_open_driver_master(DRIVER_INTEL |
+ DRIVER_XE);
+ kmstest_set_vt_graphics_mode();
+ igt_display_require(&data.display, data.drm_fd);
+ igt_display_require_output(&data.display);
+ for_each_pipe(&data.display, data.pipe) {
+ data.n_pipes++;
+ }
+ }
+
+ igt_subtest("dp-fallback") {
+ bool ran = false;
+ igt_output_t *output;
+
+ for_each_connected_output(&data.display, output) {
+
+ data.output = output;
+ if (!igt_has_force_link_training_failure_debugfs(data.drm_fd,
+ data.output)) {
+ igt_info("Output %s unsupported\n", igt_output_name(data.output));
+ continue;
+ }
+
+ if (output->config.connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort) {
+ igt_info("Skipping output %s as it's not DP\n", output->name);
+ continue;
+ }
+
+ ran = true;
+
+ /*
+ * Check output is MST
+ */
+ if (igt_check_output_is_dp_mst(data.output)) {
+ igt_info("Testing MST output %s\n",
+ igt_output_name(data.output));
+ test_fallback(&data, true);
+ } else {
+ igt_info("Testing DP output %s\n",
+ igt_output_name(data.output));
+ test_fallback(&data, false);
+ }
+ }
+ igt_require_f(ran, "No output supports fallback\n");
+ }
+
+ igt_fixture {
+ igt_remove_fb(data.drm_fd, &data.fb);
+ igt_display_fini(&data.display);
+ close(data.drm_fd);
+ }
+}
diff --git a/tests/meson.build b/tests/meson.build
index e649466be..e19ea080e 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -249,6 +249,7 @@ intel_kms_progs = [
'kms_dirtyfb',
'kms_draw_crc',
'kms_dsc',
+ 'kms_fallback',
'kms_fb_coherency',
'kms_fbcon_fbt',
'kms_fence_pin_leak',
--
2.43.0
More information about the igt-dev
mailing list