usb: typec: ucsi: displayport: Fix deadlock
authorAndrei Kuchynski <akuchynski@chromium.org>
Thu, 24 Apr 2025 08:44:28 +0000 (08:44 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 1 May 2025 15:40:01 +0000 (17:40 +0200)
This patch introduces the ucsi_con_mutex_lock / ucsi_con_mutex_unlock
functions to the UCSI driver. ucsi_con_mutex_lock ensures the connector
mutex is only locked if a connection is established and the partner pointer
is valid. This resolves a deadlock scenario where
ucsi_displayport_remove_partner holds con->mutex waiting for
dp_altmode_work to complete while dp_altmode_work attempts to acquire it.

Cc: stable <stable@kernel.org>
Fixes: af8622f6a585 ("usb: typec: ucsi: Support for DisplayPort alt mode")
Signed-off-by: Andrei Kuchynski <akuchynski@chromium.org>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Link: https://lore.kernel.org/r/20250424084429.3220757-2-akuchynski@chromium.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/typec/ucsi/displayport.c
drivers/usb/typec/ucsi/ucsi.c
drivers/usb/typec/ucsi/ucsi.h

index 420af5139c70a3505e7aa9176108663481de4fe9..acd053d4e38c309375965d0a73f48c5530ef745b 100644 (file)
@@ -54,7 +54,8 @@ static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo)
        u8 cur = 0;
        int ret;
 
-       mutex_lock(&dp->con->lock);
+       if (!ucsi_con_mutex_lock(dp->con))
+               return -ENOTCONN;
 
        if (!dp->override && dp->initialized) {
                const struct typec_altmode *p = typec_altmode_get_partner(alt);
@@ -100,7 +101,7 @@ static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo)
        schedule_work(&dp->work);
        ret = 0;
 err_unlock:
-       mutex_unlock(&dp->con->lock);
+       ucsi_con_mutex_unlock(dp->con);
 
        return ret;
 }
@@ -112,7 +113,8 @@ static int ucsi_displayport_exit(struct typec_altmode *alt)
        u64 command;
        int ret = 0;
 
-       mutex_lock(&dp->con->lock);
+       if (!ucsi_con_mutex_lock(dp->con))
+               return -ENOTCONN;
 
        if (!dp->override) {
                const struct typec_altmode *p = typec_altmode_get_partner(alt);
@@ -144,7 +146,7 @@ static int ucsi_displayport_exit(struct typec_altmode *alt)
        schedule_work(&dp->work);
 
 out_unlock:
-       mutex_unlock(&dp->con->lock);
+       ucsi_con_mutex_unlock(dp->con);
 
        return ret;
 }
@@ -202,20 +204,21 @@ static int ucsi_displayport_vdm(struct typec_altmode *alt,
        int cmd = PD_VDO_CMD(header);
        int svdm_version;
 
-       mutex_lock(&dp->con->lock);
+       if (!ucsi_con_mutex_lock(dp->con))
+               return -ENOTCONN;
 
        if (!dp->override && dp->initialized) {
                const struct typec_altmode *p = typec_altmode_get_partner(alt);
 
                dev_warn(&p->dev,
                         "firmware doesn't support alternate mode overriding\n");
-               mutex_unlock(&dp->con->lock);
+               ucsi_con_mutex_unlock(dp->con);
                return -EOPNOTSUPP;
        }
 
        svdm_version = typec_altmode_get_svdm_version(alt);
        if (svdm_version < 0) {
-               mutex_unlock(&dp->con->lock);
+               ucsi_con_mutex_unlock(dp->con);
                return svdm_version;
        }
 
@@ -259,7 +262,7 @@ static int ucsi_displayport_vdm(struct typec_altmode *alt,
                break;
        }
 
-       mutex_unlock(&dp->con->lock);
+       ucsi_con_mutex_unlock(dp->con);
 
        return 0;
 }
index e8c7e9dc49309ca3c038fcf029794c0aabd03e1c..01ce858a1a2b3466155db340e213c767d1e79479 100644 (file)
@@ -1922,6 +1922,40 @@ void ucsi_set_drvdata(struct ucsi *ucsi, void *data)
 }
 EXPORT_SYMBOL_GPL(ucsi_set_drvdata);
 
+/**
+ * ucsi_con_mutex_lock - Acquire the connector mutex
+ * @con: The connector interface to lock
+ *
+ * Returns true on success, false if the connector is disconnected
+ */
+bool ucsi_con_mutex_lock(struct ucsi_connector *con)
+{
+       bool mutex_locked = false;
+       bool connected = true;
+
+       while (connected && !mutex_locked) {
+               mutex_locked = mutex_trylock(&con->lock) != 0;
+               connected = UCSI_CONSTAT(con, CONNECTED);
+               if (connected && !mutex_locked)
+                       msleep(20);
+       }
+
+       connected = connected && con->partner;
+       if (!connected && mutex_locked)
+               mutex_unlock(&con->lock);
+
+       return connected;
+}
+
+/**
+ * ucsi_con_mutex_unlock - Release the connector mutex
+ * @con: The connector interface to unlock
+ */
+void ucsi_con_mutex_unlock(struct ucsi_connector *con)
+{
+       mutex_unlock(&con->lock);
+}
+
 /**
  * ucsi_create - Allocate UCSI instance
  * @dev: Device interface to the PPM (Platform Policy Manager)
index 3a2c1762bec1bfbc556e3a062f57a5469f62400b..9c5278a0c5d4090eb1ba4f967bbc53b4b90b4573 100644 (file)
@@ -94,6 +94,8 @@ int ucsi_register(struct ucsi *ucsi);
 void ucsi_unregister(struct ucsi *ucsi);
 void *ucsi_get_drvdata(struct ucsi *ucsi);
 void ucsi_set_drvdata(struct ucsi *ucsi, void *data);
+bool ucsi_con_mutex_lock(struct ucsi_connector *con);
+void ucsi_con_mutex_unlock(struct ucsi_connector *con);
 
 void ucsi_connector_change(struct ucsi *ucsi, u8 num);