summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/typec/tcpm/tcpm.c30
1 files changed, 30 insertions, 0 deletions
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index 51400cce582e..87ed5e2f79ca 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -1251,6 +1251,27 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port,
if (PD_VDO_SVDM(p[0]))
rlen = tcpm_pd_svdm(port, adev, p, cnt, response, &adev_action);
+ /*
+ * We are done with any state stored in the port struct now, except
+ * for any port struct changes done by the tcpm_queue_vdm() call
+ * below, which is a separate operation.
+ *
+ * So we can safely release the lock here; and we MUST release the
+ * lock here to avoid an AB BA lock inversion:
+ *
+ * If we keep the lock here then the lock ordering in this path is:
+ * 1. tcpm_pd_rx_handler take the tcpm port lock
+ * 2. One of the typec_altmode_* calls below takes the alt-mode's lock
+ *
+ * And we also have this ordering:
+ * 1. alt-mode driver takes the alt-mode's lock
+ * 2. alt-mode driver calls tcpm_altmode_enter which takes the
+ * tcpm port lock
+ *
+ * Dropping our lock here avoids this.
+ */
+ mutex_unlock(&port->lock);
+
if (adev) {
switch (adev_action) {
case ADEV_NONE:
@@ -1275,6 +1296,15 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port,
}
}
+ /*
+ * We must re-take the lock here to balance the unlock in
+ * tcpm_pd_rx_handler, note that no changes, other then the
+ * tcpm_queue_vdm call, are made while the lock is held again.
+ * All that is done after the call is unwinding the call stack until
+ * we return to tcpm_pd_rx_handler and do the unlock there.
+ */
+ mutex_lock(&port->lock);
+
if (rlen > 0)
tcpm_queue_vdm(port, response[0], &response[1], rlen - 1);
}