[PATCH 06/13] drm/msm/dp: Report stream enable failures through link status
From: Xilin Wu
Date: Wed Sep 30 2026 - 08:57:02 EST
Atomic bridge enable callbacks cannot return an error to userspace.
After a failed enable, leaving link-status unchanged gives userspace
no indication that it needs to retry the configuration.
Mark the connector link status bad from a work item after unwinding
the failed enable. Send one connector hotplug notification per failure
episode: fbdev can synchronously retry the modeset from the
notification, so notifying on every failure would create an unbounded
retry loop. Subsequent failures still restore BAD after a retry has
set the property to GOOD.
Allow notifications again after a successful enable or an external
sink connection change. Do not reset the notification latch during
eDP's internal plug and unplug handling, which runs on every retry.
Ignore queued work superseded by recovery or an external unplug.
Serialize the failure state with plugged_lock and update link-status
under the connection mutex before notifying clients with both locks
released.
Initialize the work at probe and cancel it before unbinding the
display.
Assisted-by: LLM
Signed-off-by: Xilin Wu <sophon@xxxxxxxxx>
---
drivers/gpu/drm/msm/dp/dp_display.c | 63 +++++++++++++++++++++++++++++++++++--
1 file changed, 61 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
index ae967ca652c9..1bfa6696d904 100644
--- a/drivers/gpu/drm/msm/dp/dp_display.c
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -12,9 +12,12 @@
#include <linux/phy/phy.h>
#include <linux/delay.h>
#include <linux/string_choices.h>
+#include <linux/workqueue.h>
#include <drm/display/drm_dp_aux_bus.h>
#include <drm/display/drm_hdmi_audio_helper.h>
#include <drm/drm_edid.h>
+#include <drm/drm_modeset_lock.h>
+#include <drm/drm_probe_helper.h>
#include "msm_drv.h"
#include "msm_kms.h"
@@ -54,9 +57,13 @@ struct msm_dp_display_private {
bool audio_supported;
bool stream_pm_active;
bool stream_link_attempted;
+ struct work_struct link_status_work;
struct mutex plugged_lock;
bool plugged;
+ /* Protected by plugged_lock, including accesses from link_status_work. */
+ bool link_failed;
+ bool link_status_notified;
struct drm_device *drm_dev;
@@ -204,6 +211,39 @@ void msm_dp_display_signal_audio_complete(struct msm_dp *msm_dp_display)
complete_all(&dp->audio_comp);
}
+static void msm_dp_display_reset_link_status(struct msm_dp_display_private *dp)
+{
+ lockdep_assert_held(&dp->plugged_lock);
+
+ dp->link_failed = false;
+ dp->link_status_notified = false;
+}
+
+static void msm_dp_display_link_status_work(struct work_struct *work)
+{
+ struct msm_dp_display_private *dp = container_of(work,
+ struct msm_dp_display_private, link_status_work);
+ struct drm_connector *connector = dp->msm_dp_display.connector;
+ struct drm_device *dev = connector->dev;
+ bool notify = false;
+
+ /* Match atomic check's connection_mutex -> plugged_lock ordering. */
+ drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
+ scoped_guard(mutex, &dp->plugged_lock) {
+ /* A successful enable or unplug may have superseded this work. */
+ if (dp->link_failed) {
+ connector->state->link_status = DRM_MODE_LINK_STATUS_BAD;
+ notify = !dp->link_status_notified;
+ dp->link_status_notified = true;
+ }
+ }
+ drm_modeset_unlock(&dev->mode_config.connection_mutex);
+
+ /* fbdev can retry the modeset synchronously from this notification. */
+ if (notify)
+ drm_kms_helper_connector_hotplug_event(connector);
+}
+
static int msm_dp_display_bind(struct device *dev, struct device *master,
void *data)
{
@@ -234,6 +274,7 @@ static void msm_dp_display_unbind(struct device *dev, struct device *master,
struct msm_dp_display_private *dp = dev_get_dp_display_private(dev);
struct msm_drm_private *priv = dev_get_drvdata(master);
+ cancel_work_sync(&dp->link_status_work);
of_dp_aux_depopulate_bus(dp->aux);
msm_dp_aux_unregister(dp->aux);
@@ -417,6 +458,10 @@ static int msm_dp_hpd_plug_handle(struct msm_dp_display_private *dp)
guard(mutex)(&dp->plugged_lock);
+ /* eDP calls this for every enable, including automatic retries. */
+ if (!dp->msm_dp_display.is_edp && !dp->plugged)
+ msm_dp_display_reset_link_status(dp);
+
ret = pm_runtime_resume_and_get(&pdev->dev);
if (ret) {
DRM_ERROR("failed to pm_runtime_resume\n");
@@ -463,6 +508,9 @@ static int msm_dp_hpd_unplug_handle(struct msm_dp_display_private *dp)
guard(mutex)(&dp->plugged_lock);
+ if (!dp->msm_dp_display.is_edp)
+ msm_dp_display_reset_link_status(dp);
+
dp->panel->video_test = false;
msm_dp_aux_enable_xfers(dp->aux, false);
@@ -524,10 +572,12 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp)
if (!rc) {
sink_request = dp->link->sink_request;
drm_dbg_dp(dp->drm_dev, "sink_request=%d\n", sink_request);
- if (sink_request & DS_PORT_STATUS_CHANGED)
+ if (sink_request & DS_PORT_STATUS_CHANGED) {
+ msm_dp_display_reset_link_status(dp);
rc = msm_dp_display_process_hpd_high(dp);
- else
+ } else {
rc = msm_dp_display_handle_irq_hpd(dp);
+ }
}
drm_dbg_dp(dp->drm_dev, "After, type=%d, sink_count=%d\n",
@@ -1246,6 +1296,7 @@ static int msm_dp_display_probe(struct platform_device *pdev)
dp->hpd_isr_status = 0;
mutex_init(&dp->plugged_lock);
+ INIT_WORK(&dp->link_status_work, msm_dp_display_link_status_work);
rc = msm_dp_display_get_io(dp);
if (rc)
@@ -1534,11 +1585,19 @@ void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display,
goto err;
}
+ scoped_guard(mutex, &dp->plugged_lock)
+ msm_dp_display_reset_link_status(dp);
+
drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type);
return;
err:
msm_dp_display_abort_enable(dp);
+ scoped_guard(mutex, &dp->plugged_lock) {
+ dp->link_failed = true;
+ /* Keep reporting BAD, but notify only once until recovery. */
+ schedule_work(&dp->link_status_work);
+ }
}
void msm_dp_display_atomic_disable(struct msm_dp *dp)
--
2.55.0