A bridge driver can protect its HPD hook with drm_bridge_enter(), but the core HPD helpers acquire their own mutexes before that hook and release them after it returns. A driver-only section therefore does not make drm_bridge_unplug() wait for those mutex users before drm_bridge_remove() destroys the HPD mutexes. Enter the unplug critical section before acquiring HPD mutexes in enable, disable and notify, and leave it after the final unlock. An unplugged bridge then rejects later calls before mutex or hook use, and unplug waits for the complete helpers already in progress. Found by the author's in-house static analysis tool. Fixes: d36137085a4a ("drm/bridge: add drm_bridge_unplug() and drm_bridge_enter/exit()") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/20260728135214.1850-1-getshell@seu.edu.cn/ Signed-off-by: Hongyan Xu Signed-off-by: Zehao Gao --- Discovery: found by the author's in-house static analysis tool. drivers/gpu/drm/drm_bridge.c | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/drivers/gpu/drm/drm_bridge.c b/drivers/gpu/drm/drm_bridge.c index 1b4ee74..e2adb94 100644 --- a/drivers/gpu/drm/drm_bridge.c +++ b/drivers/gpu/drm/drm_bridge.c @@ -300,6 +300,16 @@ void drm_bridge_unplug(struct drm_bridge *bridge) synchronize_srcu(&drm_bridge_unplug_srcu); + /* + * No helper can enter any more and every in-flight one has finished. + * Drop the HPD registration now, while drm_bridge_remove() has not + * destroyed the mutexes that protect it. + */ + mutex_lock(&bridge->hpd_mutex); + bridge->hpd_cb = NULL; + bridge->hpd_data = NULL; + mutex_unlock(&bridge->hpd_mutex); + drm_bridge_remove(bridge); } EXPORT_SYMBOL(drm_bridge_unplug); @@ -1551,6 +1561,10 @@ EXPORT_SYMBOL_GPL(drm_bridge_edid_read); * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in * bridge->ops. This function shall not be called when the flag is not set. * + * Calls are serialized against drm_bridge_unplug(); once the bridge has been + * unplugged, this function returns without installing the callback or calling + * &drm_bridge_funcs.hpd_enable. + * * Only one hot plug detection callback can be registered at a time, it is an * error to call this function when hot plug detection is already enabled for * the bridge. @@ -1560,9 +1574,14 @@ void drm_bridge_hpd_enable(struct drm_bridge *bridge, enum drm_connector_status status), void *data) { + int idx; + if (!(bridge->ops & DRM_BRIDGE_OP_HPD)) return; + if (!drm_bridge_enter(bridge, &idx)) + return; + mutex_lock(&bridge->hpd_state_mutex); mutex_lock(&bridge->hpd_mutex); @@ -1582,6 +1601,7 @@ void drm_bridge_hpd_enable(struct drm_bridge *bridge, unlock: mutex_unlock(&bridge->hpd_state_mutex); + drm_bridge_exit(idx); } EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable); @@ -1596,12 +1616,23 @@ EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable); * * Hot plug detection is supported only if the DRM_BRIDGE_OP_HPD flag is set in * bridge->ops. This function shall not be called when the flag is not set. + * + * Calls are serialized against drm_bridge_unplug(), which waits for any + * in-flight helper and clears the registered callback before removing the + * bridge. Once the bridge has been unplugged, this function therefore + * returns without calling &drm_bridge_funcs.hpd_disable. */ void drm_bridge_hpd_disable(struct drm_bridge *bridge) { + int idx; + if (!(bridge->ops & DRM_BRIDGE_OP_HPD)) return; + /* drm_bridge_unplug() has already dropped the registration. */ + if (!drm_bridge_enter(bridge, &idx)) + return; + mutex_lock(&bridge->hpd_state_mutex); if (bridge->funcs->hpd_disable) bridge->funcs->hpd_disable(bridge); @@ -1611,6 +1642,7 @@ void drm_bridge_hpd_disable(struct drm_bridge *bridge) bridge->hpd_data = NULL; mutex_unlock(&bridge->hpd_mutex); mutex_unlock(&bridge->hpd_state_mutex); + drm_bridge_exit(idx); } EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable); @@ -1624,14 +1656,23 @@ EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable); * enabled by drm_bridge_hpd_enable(). * * This function shall be called in a context that can sleep. + * + * Once the bridge has been unplugged the event is dropped: the registered + * callback is not invoked. */ void drm_bridge_hpd_notify(struct drm_bridge *bridge, enum drm_connector_status status) { + int idx; + + if (!drm_bridge_enter(bridge, &idx)) + return; + mutex_lock(&bridge->hpd_mutex); if (bridge->hpd_cb) bridge->hpd_cb(bridge->hpd_data, status); mutex_unlock(&bridge->hpd_mutex); + drm_bridge_exit(idx); } EXPORT_SYMBOL_GPL(drm_bridge_hpd_notify); -- 2.50.1.windows.1