Intel-GFX Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 2/2] drm/i915/dp_mst: Skip link retrain and ACT for disconnected MST streams
@ 2026-10-31 15:35 Xiao Lu
  2026-10-31 16:07 ` sashiko-bot
  0 siblings, 1 reply; 2+ messages in thread
From: Xiao Lu @ 2026-10-31 15:35 UTC (permalink / raw)
  To: intel-xe, intel-gfx
  Cc: dri-devel, jani.nikula, rodrigo.vivi, imre.deak, ville.syrjala,
	maarten.lankhorst, mripard, tzimmermann, Xiao Lu

When a dock drops the upstream HPD, for instance when a TV behind it is
powered on, the MST topology is torn down and the MST connectors get
unregistered, while the CRTCs stay bound to them in the atomic state until
userspace disables them.

After the HPD is back, the link status of the link reads as all zeros, so
intel_dp_retrain_link() selects the pipe still bound to the old MST
connector and forces a modeset on it, while the topology is being
rediscovered. drm_dp_add_payload_part1() fails, as the port is not in the
topology anymore, but the enable sequence carries on and waits for an ACT
that is never triggered, since nothing was written to DPCD 0x1c0-0x1c2:

  [drm] *ERROR* Failed to get ACT after 3000 ms, last status: 04

The same wait happens when userspace disables the stale pipe afterwards,
as drm_dp_remove_payload_part1() doesn't remove the payload of a port that
is not in the topology. The new connector only gets registered after these
waits are over.

Fix this by not selecting the pipes of disconnected MST streams, using the
same condition as intel_dp_mst_stream_disconnected(), when retraining the
link, and by skipping the ACT generation and the wait on the disable path
if drm_dp_remove_payload_part1() didn't remove the payload from the branch
device.

Link: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/17741
Signed-off-by: Xiao Lu <xiaolu.xie@intel.com>
---
 .../drm/i915/display/intel_dp_link_training.c |  3 +
 drivers/gpu/drm/i915/display/intel_dp_mst.c   | 68 +++++++++++++++----
 drivers/gpu/drm/i915/display/intel_dp_mst.h   |  1 +
 3 files changed, 58 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.c b/drivers/gpu/drm/i915/display/intel_dp_link_training.c
index ac1aff6ed..41bfb8b1a 100644
--- a/drivers/gpu/drm/i915/display/intel_dp_link_training.c
+++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.c
@@ -2576,6 +2576,9 @@ static int intel_dp_retrain_link(struct intel_encoder *encoder,
 	if (ret)
 		return ret;
 
+	/* Nothing to retrain for streams whose MST sink has left the topology */
+	pipe_mask &= ~intel_dp_mst_disconnected_pipes(intel_dp);
+
 	if (pipe_mask == 0)
 		return 0;
 
diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c
index 1e6190689..f6612a4d7 100644
--- a/drivers/gpu/drm/i915/display/intel_dp_mst.c
+++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c
@@ -1108,6 +1108,7 @@ static void mst_stream_post_disable(struct intel_atomic_state *state,
 		drm_atomic_get_mst_payload_state(new_mst_state, connector->mst.port);
 	struct intel_crtc *pipe_crtc;
 	bool last_mst_stream;
+	bool payload_removed;
 
 	last_mst_stream = intel_dp_mst_dec_active_streams(intel_dp);
 
@@ -1123,16 +1124,20 @@ static void mst_stream_post_disable(struct intel_atomic_state *state,
 
 	intel_disable_transcoder(old_crtc_state);
 
-	drm_dp_remove_payload_part1(&intel_dp->mst.mgr, new_mst_state, new_payload);
+	payload_removed = drm_dp_remove_payload_part1(&intel_dp->mst.mgr, new_mst_state,
+						      new_payload);
 
-	intel_ddi_clear_act_sent(encoder, old_crtc_state);
+	/* No ACT is needed if the payload table of the branch wasn't changed */
+	if (payload_removed) {
+		intel_ddi_clear_act_sent(encoder, old_crtc_state);
 
-	intel_de_rmw(display,
-		     TRANS_DDI_FUNC_CTL(display, old_crtc_state->cpu_transcoder),
-		     TRANS_DDI_DP_VC_PAYLOAD_ALLOC, 0);
+		intel_de_rmw(display,
+			     TRANS_DDI_FUNC_CTL(display, old_crtc_state->cpu_transcoder),
+			     TRANS_DDI_DP_VC_PAYLOAD_ALLOC, 0);
 
-	intel_ddi_wait_for_act_sent(encoder, old_crtc_state);
-	drm_dp_check_act_status(&intel_dp->mst.mgr);
+		intel_ddi_wait_for_act_sent(encoder, old_crtc_state);
+		drm_dp_check_act_status(&intel_dp->mst.mgr);
+	}
 
 	drm_dp_remove_payload_part2(&intel_dp->mst.mgr, new_mst_state,
 				    old_payload, new_payload);
@@ -2176,6 +2181,20 @@ bool intel_dp_mst_crtc_needs_modeset(struct intel_atomic_state *state,
 	return false;
 }
 
+static bool mst_connector_stream_disconnected(struct intel_connector *connector)
+{
+	if (!connector->mst.dp)
+		return false;
+
+	if (!connector->mst.dp->mst.mgr.mst_state)
+		return true;
+
+	if (drm_connector_is_unregistered(&connector->base))
+		return true;
+
+	return false;
+}
+
 bool intel_dp_mst_stream_disconnected(struct intel_atomic_state *state,
 				      const struct intel_crtc *crtc)
 {
@@ -2185,16 +2204,37 @@ bool intel_dp_mst_stream_disconnected(struct intel_atomic_state *state,
 	if (!connector)
 		return false;
 
-	if (!connector->mst.dp)
-		return false;
+	return mst_connector_stream_disconnected(connector);
+}
 
-	if (!connector->mst.dp->mst.mgr.mst_state)
-		return true;
+/**
+ * intel_dp_mst_disconnected_pipes - get the pipes driving disconnected MST streams
+ * @intel_dp: DP port object
+ *
+ * Returns the mask of pipes whose MST connector has left the topology, so
+ * there is no sink behind them to retrain the link for. The caller must hold
+ * &drm_mode_config.connection_mutex.
+ */
+u8 intel_dp_mst_disconnected_pipes(struct intel_dp *intel_dp)
+{
+	struct intel_display *display = to_intel_display(intel_dp);
+	struct drm_connector_list_iter conn_iter;
+	struct intel_connector *connector;
+	u8 pipe_mask = 0;
 
-	if (drm_connector_is_unregistered(&connector->base))
-		return true;
+	drm_connector_list_iter_begin(display->drm, &conn_iter);
+	for_each_intel_connector_iter(connector, &conn_iter) {
+		struct drm_connector_state *conn_state = connector->base.state;
 
-	return false;
+		if (connector->mst.dp != intel_dp || !conn_state->crtc)
+			continue;
+
+		if (mst_connector_stream_disconnected(connector))
+			pipe_mask |= BIT(to_intel_crtc(conn_state->crtc)->pipe);
+	}
+	drm_connector_list_iter_end(&conn_iter);
+
+	return pipe_mask;
 }
 
 /**
diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.h b/drivers/gpu/drm/i915/display/intel_dp_mst.h
index 8ce89242c..2c796a3e5 100644
--- a/drivers/gpu/drm/i915/display/intel_dp_mst.h
+++ b/drivers/gpu/drm/i915/display/intel_dp_mst.h
@@ -30,6 +30,7 @@ bool intel_dp_mst_crtc_needs_modeset(struct intel_atomic_state *state,
 				     struct intel_crtc *crtc);
 bool intel_dp_mst_stream_disconnected(struct intel_atomic_state *state,
 				      const struct intel_crtc *crtc);
+u8 intel_dp_mst_disconnected_pipes(struct intel_dp *intel_dp);
 void intel_dp_mst_prepare_probe(struct intel_dp *intel_dp);
 bool intel_dp_mst_verify_dpcd_state(struct intel_dp *intel_dp);
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-10-31 16:07 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-31 15:35 [PATCH 2/2] drm/i915/dp_mst: Skip link retrain and ACT for disconnected MST streams Xiao Lu
2026-10-31 16:07 ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox