usb: chipidea: core: fix possible concurrent when switch role
authorXu Yang <xu.yang_2@nxp.com>
Fri, 17 Mar 2023 06:15:16 +0000 (14:15 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 23 Mar 2023 17:17:32 +0000 (18:17 +0100)
The user may call role_store() when driver is handling
ci_handle_id_switch() which is triggerred by otg event or power lost
event. Unfortunately, the controller may go into chaos in this case.
Fix this by protecting it with mutex lock.

Fixes: a932a8041ff9 ("usb: chipidea: core: add sysfs group")
cc: <stable@vger.kernel.org>
Acked-by: Peter Chen <peter.chen@kernel.org>
Signed-off-by: Xu Yang <xu.yang_2@nxp.com>
Link: https://lore.kernel.org/r/20230317061516.2451728-2-xu.yang_2@nxp.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/chipidea/ci.h
drivers/usb/chipidea/core.c
drivers/usb/chipidea/otg.c

index 005c67c..f210b74 100644 (file)
@@ -208,6 +208,7 @@ struct hw_bank {
  * @in_lpm: if the core in low power mode
  * @wakeup_int: if wakeup interrupt occur
  * @rev: The revision number for controller
+ * @mutex: protect code from concorrent running when doing role switch
  */
 struct ci_hdrc {
        struct device                   *dev;
@@ -260,6 +261,7 @@ struct ci_hdrc {
        bool                            in_lpm;
        bool                            wakeup_int;
        enum ci_revision                rev;
+       struct mutex                    mutex;
 };
 
 static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci)
index b6f2a41..281fc51 100644 (file)
@@ -987,8 +987,12 @@ static ssize_t role_store(struct device *dev,
        if (role == CI_ROLE_END)
                return -EINVAL;
 
-       if (role == ci->role)
+       mutex_lock(&ci->mutex);
+
+       if (role == ci->role) {
+               mutex_unlock(&ci->mutex);
                return n;
+       }
 
        pm_runtime_get_sync(dev);
        disable_irq(ci->irq);
@@ -998,6 +1002,7 @@ static ssize_t role_store(struct device *dev,
                ci_handle_vbus_change(ci);
        enable_irq(ci->irq);
        pm_runtime_put_sync(dev);
+       mutex_unlock(&ci->mutex);
 
        return (ret == 0) ? n : ret;
 }
@@ -1033,6 +1038,7 @@ static int ci_hdrc_probe(struct platform_device *pdev)
                return -ENOMEM;
 
        spin_lock_init(&ci->lock);
+       mutex_init(&ci->mutex);
        ci->dev = dev;
        ci->platdata = dev_get_platdata(dev);
        ci->imx28_write_fix = !!(ci->platdata->flags &
index 622c3b6..f5490f2 100644 (file)
@@ -167,8 +167,10 @@ static int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci)
 
 void ci_handle_id_switch(struct ci_hdrc *ci)
 {
-       enum ci_role role = ci_otg_role(ci);
+       enum ci_role role;
 
+       mutex_lock(&ci->mutex);
+       role = ci_otg_role(ci);
        if (role != ci->role) {
                dev_dbg(ci->dev, "switching from %s to %s\n",
                        ci_role(ci)->name, ci->roles[role]->name);
@@ -198,6 +200,7 @@ void ci_handle_id_switch(struct ci_hdrc *ci)
                if (role == CI_ROLE_GADGET)
                        ci_handle_vbus_change(ci);
        }
+       mutex_unlock(&ci->mutex);
 }
 /**
  * ci_otg_work - perform otg (vbus/id) event handle