After a DP-alt sink is disconnected with the link still active,
intel_tc_port_link_reset_work() tries to recover the link via a
modeset, flagging the active CRTCs with connectors_changed in
reset_link_commit(). By that point intel_dp_detect() has already
reset the sink capabilities (EDID, dfp.*, DSC DPCD - see the FIXME in
intel_dp_detect()), so the recovery modeset is computed without them.
Depending on which capabilities the connected mode requires, this
either fails the atomic check with -EINVAL, triggering the WARN in
intel_tc_port_link_reset_work():
i915 0000:00:02.0: [drm] drm_WARN_ON(ret)
WARNING: ... at drivers/gpu/drm/i915/display/intel_tc.c:1838
intel_tc_port_link_reset_work+0x38c/0x420
or commits a configuration the disconnected link can't sustain: link
training fails and the output is left enabled on the disconnected
port. Either way the output stays enabled, keeping the TC PHY
ownership held and the TC mode locked. AUX transfers then get
rejected based on intel_digital_port_connected_locked(), so detecting
a newly connected sink keeps failing as well: the port can't be
recovered without disabling the output by some other means (in
practice a reboot).
Disable the affected outputs instead of modesetting them, matching
how commit c598c335da42 ("drm/i915/tc: Reset TypeC PHYs left enabled
in DP-alt mode after the sink disconnects") handles the equivalent
situation during boot/resume sanitization, for the same reason. The
disable also releases the PHY ownership synchronously - via the
encoder's post-PLL-disable hook - avoiding the IOM/TCSS firmware
timeout the above commit worked around, and unblocking the HPD status
updates of other TypeC ports. The output gets re-enabled via the
normal hotplug flow once a sink is connected again.
Preserving the sink capabilities across the disconnect instead (the
direction proposed for the DSC caps in the gitlab reports below)
would avoid the -EINVAL, but not the second failure mode: the
recovery modeset would still be committed against a dead link,
leaving the enabled output behind after a failed link training.
Disabling the output covers both.
Observed on PTL with a DP-alt -> HDMI 2.1 PCON adapter on a TV power
cycle (both failure modes above); reports with the matching WARN on
ADL and MTL in the links below.
Fixes: c598c335da42 ("drm/i915/tc: Reset TypeC PHYs left enabled in DP-alt mode
after the sink disconnects")
Link: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/14807
Link: https://gitlab.freedesktop.org/drm/i915/kernel/-/issues/11551
Cc: Imre Deak <[email protected]>
Cc: Ville Syrjälä <[email protected]>
Signed-off-by: Alexander Kaplan <[email protected]>
---
diff --git a/drivers/gpu/drm/i915/display/intel_tc.c
b/drivers/gpu/drm/i915/display/intel_tc.c
index a21dd4e3fe4c..ae9da59ca8e3 100644
--- a/drivers/gpu/drm/i915/display/intel_tc.c
+++ b/drivers/gpu/drm/i915/display/intel_tc.c
@@ -5,6 +5,7 @@
#include <linux/iopoll.h>
+#include <drm/drm_atomic_uapi.h>
#include <drm/drm_print.h>
#include "intel_atomic.h"
@@ -1764,9 +1765,13 @@ static int reset_link_commit(struct intel_tc_port *tc,
struct intel_display *display = to_intel_display(tc->dig_port);
struct intel_digital_port *dig_port = tc->dig_port;
struct intel_dp *intel_dp = enc_to_intel_dp(&dig_port->base);
+ struct drm_connector_state *conn_state;
+ struct drm_connector *connector;
+ struct drm_plane_state *plane_state;
+ struct drm_plane *plane;
struct intel_crtc *crtc;
u8 pipe_mask;
- int ret;
+ int i, ret;
ret = drm_modeset_lock(&display->drm->mode_config.connection_mutex,
ctx);
if (ret)
@@ -1779,6 +1784,13 @@ static int reset_link_commit(struct intel_tc_port *tc,
if (!pipe_mask)
return 0;
+ /*
+ * The sink is gone, so intel_dp_detect() has already reset the sink
+ * capabilities, and recomputing the config for the still active mode
+ * would fail (see the FIXME in intel_dp_detect()). Disable the
+ * outputs instead; the next sink connect re-enables them via the
+ * normal hotplug flow.
+ */
for_each_intel_crtc_in_pipe_mask(display, crtc, pipe_mask) {
struct intel_crtc_state *crtc_state;
@@ -1786,7 +1798,33 @@ static int reset_link_commit(struct intel_tc_port *tc,
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
- crtc_state->uapi.connectors_changed = true;
+ crtc_state->uapi.active = false;
+
+ ret = drm_atomic_set_mode_prop_for_crtc(&crtc_state->uapi,
NULL);
+ if (ret)
+ return ret;
+
+ ret = drm_atomic_add_affected_planes(&state->base, &crtc->base);
+ if (ret)
+ return ret;
+
+ ret = drm_atomic_add_affected_connectors(&state->base,
&crtc->base);
+ if (ret)
+ return ret;
+ }
+
+ for_each_new_connector_in_state(&state->base, connector, conn_state, i)
{
+ ret = drm_atomic_set_crtc_for_connector(conn_state, NULL);
+ if (ret)
+ return ret;
+ }
+
+ for_each_new_plane_in_state(&state->base, plane, plane_state, i) {
+ ret = drm_atomic_set_crtc_for_plane(plane_state, NULL);
+ if (ret)
+ return ret;
+
+ drm_atomic_set_fb_for_plane(plane_state, NULL);
}
if (!__intel_tc_port_link_needs_reset(tc))