Intel-XE Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Xiao Lu" <xiaolu.xie@intel.com>,
	intel-xe@lists.freedesktop.org, intel-gfx@lists.freedesktop.org
Cc: dri-devel@lists.freedesktop.org
Subject: Re: [PATCH RESEND 2/2] drm/i915/dp_mst: Skip link retrain and ACT for disconnected MST streams
Date: Sat, 31 Oct 2026 16:17:35 +0000	[thread overview]
Message-ID: <sashiko-outbox-192037@kernel.org> (raw)
In-Reply-To: <20261031154456.465469-3-xiaolu.xie@intel.com>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] drm/i915/dp_mst: mst_stream_post_disable() skips VC payload deallocation
- [High] drm/i915/dp_mst: skipped link training in intel_dp_retrain_link() on partial pipe_mask
- [Medium] drm/i915/dp_mst: unguarded ACT wait in mst_stream_enable() on failed payload
- [Medium] drm/i915/dp_mst: race with delayed connector unregister in mst_connector_stream_disconnected()
- [Medium] drm/i915/dp_mst: ACT wait not skipped when payload was only allocated locally
--

commit 5ef32ca1205b405f34627fa195a565342452c430
Author: Xiao Lu <xiaolu.xie@intel.com>

drm/i915/dp_mst: Skip link retrain and ACT for disconnected MST streams

This commit filters out pipes driving disconnected MST streams in
intel_dp_retrain_link() and skips generating and waiting for an ACT in
mst_stream_post_disable() when drm_dp_remove_payload_part1() reports that the
payload was not removed from the branch device.

Link: https://gitlab.freedesktop.org/drm/i915/kernel/-/work_items/17741

> 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 ac1aff6edf052..41bfb8b1ae0c3 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);

[Severity: High]
What happens in intel_dp_retrain_link() when an MST topology has both a
connected active stream and a disconnected (unregistered) active stream on the
same intel_dp?

Masking out the disconnected pipe leaves only the connected pipe in pipe_mask,
so intel_modeset_commit_pipes_for_atomic_state() sets
crtc_state->uapi.connectors_changed = true only on the connected pipe.

During intel_atomic_check(), mst_connector_atomic_topology_check() pulls the
disconnected pipe into the atomic state with crtc_state->uapi.mode_changed =
true and crtc_state->uapi.connectors_changed = false:

drivers/gpu/drm/i915/display/intel_dp_mst.c:mst_connector_atomic_topology_check() {
	...
		ret = drm_atomic_add_affected_planes(&state->base, &crtc->base);
		if (ret)
			break;
		crtc_state->uapi.mode_changed = true;
	...
}

In intel_link_bw_init_limits(), because intel_dp_mst_stream_disconnected() is
true for the disconnected pipe, its max_bpp_x16 limit is preserved from
crtc_state->max_link_bpp_x16. When link rate and lane count are unchanged,
intel_pipe_config_compare() succeeds and intel_crtc_check_fastset() downgrades
the disconnected pipe to a fastset by clearing uapi.mode_changed = false.

In the post-fastset dependency loop in intel_atomic_check(), only slave
transcoders check whether their mst_master_transcoder needs a modeset:

drivers/gpu/drm/i915/display/intel_display.c:intel_atomic_check() {
	...
		if (intel_dp_mst_is_slave_trans(new_crtc_state)) {
			enum transcoder master = new_crtc_state->mst_master_transcoder;

			if (intel_cpu_transcoders_need_modeset(state, BIT(master)))
				intel_crtc_flag_modeset(new_crtc_state);
		}
	...
}

If the disconnected stream is on the MST master transcoder (or on any pipe on
DISPLAY_VER < 12), the disconnected pipe remains a fastset and is not disabled
during the commit. In mst_stream_post_disable() and mst_stream_pre_enable()
for the connected pipe, intel_dp->mst.active_streams transitions 2 -> 1 -> 2,
so last_mst_stream and first_mst_stream are both false.

Since primary_encoder->pre_enable() (which runs intel_dp_start_link_train())
is only called when first_mst_stream is true in mst_stream_pre_enable(), won't
this skip link training completely while still clearing force_retrain in
intel_dp_retrain_link()?

[Severity: Medium]
In addition, can mst_stream_enable() still hit the 3000 ms ACT timeout when a
disconnected MST stream is forced into a full modeset through other paths?

While mst_stream_post_disable() is updated to skip the ACT wait,
mst_stream_enable() still unconditionally triggers ACT and calls
drm_dp_check_act_status() even when drm_dp_add_payload_part1() in
mst_stream_pre_enable() failed to allocate the payload at the branch device:

drivers/gpu/drm/i915/display/intel_dp_mst.c:mst_stream_pre_enable() {
	...
	ret = drm_dp_add_payload_part1(&intel_dp->mst.mgr, mst_state,
				       drm_atomic_get_mst_payload_state(mst_state, connector->mst.port));
	if (ret < 0)
		intel_dp_queue_modeset_retry_for_link(state, primary_encoder, pipe_config);
	...
}

drivers/gpu/drm/i915/display/intel_dp_mst.c:mst_stream_enable() {
	...
	intel_de_rmw(display, TRANS_DDI_FUNC_CTL(display, trans), 0,
		     TRANS_DDI_DP_VC_PAYLOAD_ALLOC);

	intel_ddi_wait_for_act_sent(encoder, pipe_config);
	drm_dp_check_act_status(&intel_dp->mst.mgr);
	...
}

A disconnected MST stream can still undergo a full modeset in several cases:

1. When two or more MST streams are active when HPD drops and userspace
   disables the CRTC on mst_master_transcoder first:
   mst_connector_atomic_topology_check() pulls the remaining disconnected
   CRTCs on the same intel_dp into the commit with uapi.mode_changed = true,
   mst_stream_compute_config_late() assigns a new mst_master_transcoder, and
   intel_pipe_config_compare() rejects fastset because mst_master_transcoder
   changed.

2. When intel_dp_retrain_link() runs with a connected stream on the MST master
   transcoder and a disconnected stream on a slave transcoder (or when link or
   DSC parameters change), forcing a full modeset on the disconnected slave
   pipe.

3. When reset_link_commit() in drivers/gpu/drm/i915/display/intel_tc.c commits
   a modeset on all active pipes returned by intel_dp_get_active_pipes()
   without filtering disconnected MST pipes.

In all of these cases, drm_dp_add_payload_part1() fails with -EIO without
writing to DPCD 0x1c0-0x1c2, and mst_stream_enable() will still poll
drm_dp_check_act_status() for 3000 ms. Should mst_stream_enable() also avoid
waiting for ACT when the payload was not allocated on the branch device?

[ ... ]

> diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c
> index 7023619aaaba9..a1d4c37d001ac 100644
> --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c
> +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c
> @@ -1109,6 +1109,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);
>  
> @@ -1124,16 +1125,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);
> +	}

[Severity: High]
Is it safe to skip clearing TRANS_DDI_DP_VC_PAYLOAD_ALLOC in
TRANS_DDI_FUNC_CTL when payload_removed is false in mst_stream_post_disable()?

When an MST stream is enabled in mst_stream_enable(), the driver sets
TRANS_DDI_DP_VC_PAYLOAD_ALLOC in TRANS_DDI_FUNC_CTL. Neither
intel_ddi_disable_transcoder_func() nor intel_ddi_post_disable_dp() clears
TRANS_DDI_DP_VC_PAYLOAD_ALLOC:

drivers/gpu/drm/i915/display/intel_ddi.c:intel_ddi_disable_transcoder_func() {
	...
	ctl = intel_de_read(display,
			    TRANS_DDI_FUNC_CTL(display, cpu_transcoder));

	drm_WARN_ON(crtc->base.dev, ctl & TRANS_DDI_HDCP_SIGNALLING);

	ctl &= ~TRANS_DDI_FUNC_ENABLE;
	...
}

When payload_removed is false, skipping the intel_de_rmw() call leaves
TRANS_DDI_DP_VC_PAYLOAD_ALLOC set when intel_ddi_disable_transcoder_func()
disables TRANS_DDI_FUNC_ENABLE, and if another stream remains active on the
same MST link (!last_mst_stream), the source hardware's MST packetizer never
deallocates this transcoder's VC payload slots even though
drm_dp_remove_payload_part2() shifts the remaining payload slots in software.

Since clearing TRANS_DDI_DP_VC_PAYLOAD_ALLOC and waiting for
DP_TP_STATUS_ACT_SENT via intel_ddi_wait_for_act_sent() is a local source MMIO
operation (with a 1 ms timeout), shouldn't TRANS_DDI_DP_VC_PAYLOAD_ALLOC still
be cleared unconditionally, or only the DPCD poll in drm_dp_check_act_status()
be skipped when the branch payload table wasn't updated?

[Severity: Medium]
Also in mst_stream_post_disable(), does the return value of
drm_dp_remove_payload_part1() accurately reflect whether the branch device's
payload table was modified?

Looking at drm_dp_remove_payload_part1() and
drm_dp_destroy_payload_at_remote_and_dfp() in
drivers/gpu/drm/display/drm_dp_mst_topology.c:

drivers/gpu/drm/display/drm_dp_mst_topology.c:drm_dp_remove_payload_part1() {
	bool send_remove = false;

	mutex_lock(&mgr->lock);
	send_remove = drm_dp_mst_port_downstream_of_branch(payload->port, mgr->mst_primary);
	mutex_unlock(&mgr->lock);

	if (send_remove)
		drm_dp_destroy_payload_at_remote_and_dfp(mgr, mst_state, payload);
	...
	payload->payload_allocation_status = DRM_DP_MST_PAYLOAD_ALLOCATION_LOCAL;

	return send_remove;
}

drivers/gpu/drm/display/drm_dp_mst_topology.c:drm_dp_destroy_payload_at_remote_and_dfp() {
	if (payload->payload_allocation_status == DRM_DP_MST_PAYLOAD_ALLOCATION_REMOTE) {
		drm_dp_payload_send_msg(mgr, payload->port, payload->vcpi, 0);
		payload->payload_allocation_status = DRM_DP_MST_PAYLOAD_ALLOCATION_DFP;
	}

	if (payload->payload_allocation_status == DRM_DP_MST_PAYLOAD_ALLOCATION_DFP)
		drm_dp_dpcd_write_payload(mgr->aux, payload->vcpi, payload->vc_start_slot, 0);
}

If an earlier modeset on this stream failed in drm_dp_add_payload_part1() (for
example due to a DPCD AUX failure in drm_dp_create_payload_at_dfp(), or
because a downstream port or secondary branch device was unlinked while
mgr->mst_primary remained the same root branch),
payload->payload_allocation_status is left as
DRM_DP_MST_PAYLOAD_ALLOCATION_LOCAL and nothing was written to DPCD
0x1c0-0x1c2.

Because port->parent and mstb->port_parent are not cleared when a port or
secondary branch is unlinked, drm_dp_mst_port_downstream_of_branch() still
returns true as long as mgr->mst_primary has not been replaced. When the
stream is later disabled, drm_dp_destroy_payload_at_remote_and_dfp() does not
write to DPCD 0x1c0-0x1c2 because payload_allocation_status is LOCAL, yet
drm_dp_remove_payload_part1() still returns true, causing
mst_stream_post_disable() to wait 3000 ms for an ACT in
drm_dp_check_act_status().

>  
>  	drm_dp_remove_payload_part2(&intel_dp->mst.mgr, new_mst_state,
>  				    old_payload, new_payload);
> @@ -2172,6 +2177,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;
> +}

[Severity: Medium]
Can mst_connector_stream_disconnected() miss a disconnected stream if
intel_dp_retrain_link() runs before mgr->delayed_destroy_work finishes
unregistering the connector?

When an MST topology is torn down in drm_dp_mst_topology_mgr_set_mst(mgr,
false) or when a downstream branch is disconnected in
drm_dp_mst_handle_conn_stat(), mgr->mst_primary (or the parent port->mstb) is
cleared under mgr->lock, and drm_dp_destroy_mst_branch_device() queues
mgr->delayed_destroy_work to call drm_connector_unregister() asynchronously in
drm_dp_delayed_destroy_port().

When the HPD comes back and intel_dp_detect() calls
drm_dp_mst_topology_mgr_set_mst(mgr, true) (or on a downstream branch unplug
where mgr->mst_state remains true throughout), mgr->mst_state is true while
mgr->delayed_destroy_work may not yet have run drm_connector_unregister() on
the old connector.

If intel_dp_retrain_link() -> intel_dp_mst_disconnected_pipes() runs during
that window, mst_connector_stream_disconnected() sees mgr.mst_state == true
and drm_connector_is_unregistered(&connector->base) == false, so it returns
false even though connector->mst.port is no longer in the topology, allowing
intel_dp_retrain_link() to select the stale pipe and trigger the 3000 ms ACT
timeout in mst_stream_enable().

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261031154456.465469-1-xiaolu.xie@intel.com?part=2

  reply	other threads:[~2026-10-31 16:17 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-31 15:44 [PATCH 0/2] drm/i915/dp_mst: Avoid the 3 s ACT timeouts after the upstream HPD drops Xiao Lu
2026-10-31 15:44 ` [PATCH RESEND 1/2] drm/dp_mst: Return whether drm_dp_remove_payload_part1() removed the payload Xiao Lu
2026-10-31 16:05   ` sashiko-bot
2026-10-31 15:44 ` [PATCH RESEND 2/2] drm/i915/dp_mst: Skip link retrain and ACT for disconnected MST streams Xiao Lu
2026-10-31 16:17   ` sashiko-bot [this message]
2026-10-31 15:55 ` ✓ CI.KUnit: success for drm/i915/dp_mst: Avoid the 3 s ACT timeouts after the upstream HPD drops (rev2) Patchwork
2026-10-31 16:32 ` ✓ Xe.CI.BAT: " Patchwork
2026-10-31 22:43 ` ✗ Xe.CI.FULL: failure " Patchwork

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-192037@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=intel-gfx@lists.freedesktop.org \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=xiaolu.xie@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox