[PATCH i-g-t v5 3/4] tests/intel/kms_dp_linktrain_fallback: Add UHBR-to-HBR fallback subtest

Sowmiya S <[email protected]>
Newsgroups org.freedesktop.lists.igt-dev
Message-ID <[email protected]>
Add 'uhbr-to-hbr-fallback' to verify the 128b/132b->8b/10b encoding
transition on UHBR-capable DP connectors by pinning the link at the
highest sustainable UHBR rate, forcing LT_FAILURE_REDUCED_CAPS failures
and asserting the rate drops below UHBR10. Supports SST and MST.

v2: resets MST traversal state per test run (Kunal)
v3: - use i915_dp_is_uhbr_rate()/i915_dp_get_next_lower_rate()
      from lib
    - single UHBR capability gate using max_link_rate after reset
    - add igt_reset_connectors() on pin-loop early-return
    - hard-assert for 'fallback not reached'
    - remove dead run_dsc reset; merge run_* funcs
    - fix link rate log units (10 kbit/s).
    - Extend run_lt_fallback_test() with a force_uhbr parameter
    - and remove the duplicate run_uhbr_to_hbr_fallback_test()
      (Kunal)

Signed-off-by: Sowmiya S <[email protected]>
---
 tests/intel/kms_dp_linktrain_fallback.c | 198 +++++++++++++++++++++---
 1 file changed, 174 insertions(+), 24 deletions(-)

diff --git a/tests/intel/kms_dp_linktrain_fallback.c b/tests/intel/kms_dp_linktrain_fallback.c
index 44b875ab9..2e72957e1 100644
--- a/tests/intel/kms_dp_linktrain_fallback.c
+++ b/tests/intel/kms_dp_linktrain_fallback.c
@@ -24,6 +24,15 @@
  *
  * SUBTEST: dsc-fallback
  * Description: Test fallback to DSC when BW isn't sufficient
+ *
+ * SUBTEST: uhbr-to-hbr-fallback
+ * Description: Verify fallback from UHBR (128b/132b, link rate >= 10 Gbps) to
+ *              HBR3 or lower (8b/10b) on a UHBR-capable DP connector by
+ *              pinning the link at the highest sustainable UHBR rate, forcing
+ *              repeated link training failures and checking that the rate
+ *              drops below UHBR10. Supports both SST and MST (topology-wide)
+ *              outputs; MST siblings share the physical link so fallback
+ *              applies to the whole topology.
  */
 
 #define RETRAIN_COUNT 1
@@ -347,7 +356,20 @@ static bool fix_link_status_and_recommit(data_t *data,
 	return true;
 }
 
-static void test_fallback(data_t *data, bool is_mst)
+/*
+ * test_fallback:
+ * @force_uhbr: when false (dp-fallback), the output is always counted as
+ *		exercised. When true (uhbr-to-hbr-fallback), the link is first
+ *		pinned at the highest sustainable UHBR rate; the return value
+ *		then reports whether the run was skippable (false, e.g. no
+ *		sustainable UHBR rate) so the caller can turn it into a SKIP.
+ *		A genuine failure (UHBR reached but never dropped to HBR) is
+ *		asserted here rather than returned.
+ *
+ * Returns: true if the output was exercised/verified, false for the
+ *	    skippable cases.
+ */
+static bool test_fallback(data_t *data, bool is_mst, bool force_uhbr)
 {
 	int output_count, retries;
 	int max_link_rate, curr_link_rate, prev_link_rate;
@@ -361,14 +383,84 @@ static void test_fallback(data_t *data, bool is_mst)
 	retries = SPURIOUS_HPD_RETRY;
 
 	igt_display_reset(&data->display);
-	i915_dp_reset_link_params(data->drm_fd, data->output);
-	if (!setup_outputs(data, is_mst, outputs,
-			   &output_count, modes, fbs,
-			   primaries))
-		return;
 
-	igt_info("Testing link training fallback on %s\n",
-		 igt_output_name(data->output));
+	/*
+	 * For the UHBR-to-HBR fallback case, pin the link at the highest
+	 * sustainable UHBR rate before forcing failures. The caller
+	 * (run_lt_fallback_test()) has already reset the link params for the
+	 * UHBR gate, so i915_dp_get_max_link_rate() here returns the full
+	 * sink-negotiated max and not a value already reduced by an earlier
+	 * fallback. If the physical link can't sustain that rate (cable
+	 * limit), step down to the next-lower supported UHBR rate and retry.
+	 * Skip the test if no UHBR rate can be sustained.
+	 */
+	if (force_uhbr) {
+		int try_rate = i915_dp_get_max_link_rate(data->drm_fd,
+							 data->output);
+		char rate_str[16];
+		/* Preserve MST state for fallback retries. */
+		int saved_mst_count = traversed_mst_output_count;
+
+		curr_link_rate = 0;
+		while (i915_dp_is_uhbr_rate(try_rate)) {
+			/* Allow MST re-discovery on every UHBR-rate attempt. */
+			traversed_mst_output_count = saved_mst_count;
+
+			snprintf(rate_str, sizeof(rate_str), "%d", try_rate);
+			i915_dp_set_link_params(data->drm_fd, data->output,
+						rate_str, "auto");
+
+			if (!setup_outputs(data, is_mst, outputs,
+					   &output_count, modes, fbs, primaries)) {
+				i915_dp_reset_link_params(data->drm_fd,
+							  data->output);
+				igt_reset_connectors();
+				return false;
+			}
+
+			curr_link_rate = i915_dp_get_current_link_rate(data->drm_fd,
+								       data->output);
+			if (i915_dp_is_uhbr_rate(curr_link_rate)) {
+				igt_info("Link trained at UHBR (link rate %d) on %s\n",
+					 curr_link_rate,
+					 igt_output_name(data->output));
+				break;
+			}
+
+			igt_info("Link rate %d not sustained (got %d) on %s, trying next-lower UHBR\n",
+				 try_rate, curr_link_rate,
+				 igt_output_name(data->output));
+			igt_display_reset(&data->display);
+
+			try_rate = i915_dp_get_next_lower_rate(data->drm_fd,
+							       data->output,
+							       try_rate);
+		}
+
+		if (!i915_dp_is_uhbr_rate(curr_link_rate)) {
+			igt_info("Output %s cannot sustain any UHBR rate, skipping\n",
+				 igt_output_name(data->output));
+			i915_dp_reset_link_params(data->drm_fd, data->output);
+			return false;
+		}
+
+		/*
+		 * Clear the pin so the forced-failure loop below can reduce the
+		 * rate. This re-enables all link configs, so max_link_rate
+		 * (read next) is the full negotiated max, not the pinned rate.
+		 * The "curr == max" spurious-HPD escape below therefore won't
+		 * trigger on this path, which is fine - we only care that the
+		 * rate drops out of UHBR.
+		 */
+		i915_dp_reset_link_params(data->drm_fd, data->output);
+	} else {
+		i915_dp_reset_link_params(data->drm_fd, data->output);
+
+		if (!setup_outputs(data, is_mst, outputs,
+				   &output_count, modes, fbs,
+				   primaries))
+			return false;
+	}
 	max_link_rate = i915_dp_get_max_link_rate(data->drm_fd, data->output);
 	max_lane_count = i915_dp_get_max_lane_count(data->drm_fd, data->output);
 	prev_link_rate = i915_dp_get_current_link_rate(data->drm_fd, data->output);
@@ -389,8 +481,11 @@ static void test_fallback(data_t *data, bool is_mst)
 
 		if (i915_dp_get_link_retrain_disabled(data->drm_fd,
 						      data->output)) {
+			igt_assert_f(!force_uhbr,
+				     "Link retrain disabled before UHBR to HBR fallback on %s\n",
+				     igt_output_name(data->output));
 			igt_reset_connectors();
-			return;
+			return false;
 		}
 
 		igt_assert_f(wait_for_hotplug_and_check_bad(data->drm_fd,
@@ -421,24 +516,57 @@ static void test_fallback(data_t *data, bool is_mst)
 			     ((curr_link_rate == max_link_rate && curr_lane_count == max_lane_count) && --retries),
 			     "Fallback unsuccessful\n");
 
+		/*
+		 * This subtest only cares about the single UHBR -> HBR
+		 * transition (128b/132b to 8b/10b encoding). Stop as soon as
+		 * we've dropped below UHBR10, instead of cascading all the
+		 * way down through the legacy HBR/HBR2/HBR3 rates, which is
+		 * already covered by dp-fallback.
+		 */
+		if (force_uhbr && !i915_dp_is_uhbr_rate(curr_link_rate)) {
+			igt_info("UHBR to HBR fallback confirmed on %s: link rate %d -> %d\n",
+				 igt_output_name(data->output),
+				 prev_link_rate, curr_link_rate);
+			/*
+			 * Link params are already 'auto' (the pin was cleared
+			 * before this loop) and the per-output UHBR gate in
+			 * run_lt_fallback_test() resets again for the next
+			 * output, so this run is already self-contained.
+			 */
+			return true;
+		}
+
 		prev_link_rate = curr_link_rate;
 		prev_lane_count = curr_lane_count;
 	}
+
+	/*
+	 * force_uhbr: the loop only exits here if link retrain got disabled
+	 * before the rate ever dropped out of UHBR, i.e. we trained at UHBR and
+	 * forced failures but never saw the UHBR -> HBR transition. That is a
+	 * real failure, not a skip.
+	 */
+	igt_assert_f(!force_uhbr,
+		     "UHBR to HBR fallback not reached on %s (last link rate %d)\n",
+		     igt_output_name(data->output), prev_link_rate);
+
+	return true;
 }
 
-static bool run_lt_fallback_test(data_t *data)
+static bool run_lt_fallback_test(data_t *data, bool force_uhbr)
 {
 	bool ran = false;
 	igt_output_t *output;
 
 	/*
 	 * Reset per invocation so MST traversal state from a previous subtest
-	 * (e.g. dp-fallback followed by dsc-fallback) doesn't leak and cause
-	 * MST siblings to be silently skipped as "already visited".
+	 * doesn't leak into this run.
 	 */
 	traversed_mst_output_count = 0;
 
 	for_each_connected_output(&data->display, output) {
+		bool is_mst, tested;
+
 		data->output = output;
 
 		if (!i915_dp_has_force_link_training_failure_debugfs(data->drm_fd,
@@ -450,23 +578,40 @@ static bool run_lt_fallback_test(data_t *data)
 
 		if (output->config.connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort) {
 			igt_info("Skipping output %s as it's not DP\n", output->name);
+			continue;
+		}
+
+		if (force_uhbr) {
+			/*
+			 * Single UHBR-capability gate: reset first so the max
+			 * link rate reflects the full sink-negotiated caps and
+			 * not a value already reduced by an earlier fallback.
+			 */
+			i915_dp_reset_link_params(data->drm_fd, data->output);
+			if (!i915_dp_is_uhbr_rate(i915_dp_get_max_link_rate(data->drm_fd,
+									    data->output))) {
+				igt_info("Skipping output %s: does not support UHBR\n",
+					 igt_output_name(data->output));
 				continue;
+			}
 		}
 
-		ran = true;
+		is_mst = igt_check_output_is_dp_mst(data->output);
+		igt_info("Testing %s%s output %s\n",
+			 force_uhbr ? "UHBR-to-HBR fallback on " : "",
+			 is_mst ? "MST" : "DP",
+			 igt_output_name(data->output));
+
+		tested = test_fallback(data, is_mst, force_uhbr);
 
 		/*
-		 * Check output is MST
+		 * dp-fallback counts every capable output as exercised; the
+		 * UHBR subtest only counts outputs where fallback was actually
+		 * driven (test_fallback() returns false for the skippable
+		 * cases, e.g. no sustainable UHBR rate).
 		 */
-		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);
-		}
+		if (!force_uhbr || tested)
+			ran = true;
 	}
 	return ran;
 }
@@ -644,7 +789,7 @@ int igt_main()
 	}
 
 	igt_subtest("dp-fallback") {
-		igt_require_f(run_lt_fallback_test(&data),
+		igt_require_f(run_lt_fallback_test(&data, false),
 			      "Skipping test as no output found or none supports fallback\n");
 	}
 
@@ -653,6 +798,11 @@ int igt_main()
 			      "Skipping test as DSC fallback conditions not met.\n");
 	}
 
+	igt_subtest("uhbr-to-hbr-fallback") {
+		igt_require_f(run_lt_fallback_test(&data, true),
+			      "Skipping test: no UHBR-capable DP output found\n");
+	}
+
 	igt_fixture() {
 		igt_remove_fb(data.drm_fd, &data.fb);
 		igt_display_fini(&data.display);
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.