usb: musb: fix remote wakeup racing with suspend
authorDaniel Glöckner <dg@emlix.com>
Mon, 14 May 2018 14:40:05 +0000 (09:40 -0500)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 14 May 2018 15:04:06 +0000 (17:04 +0200)
It has been observed that writing 0xF2 to the power register while it
reads as 0xF4 results in the register having the value 0xF0, i.e. clearing
RESUME and setting SUSPENDM in one go does not work. It might also violate
the USB spec to transition directly from resume to suspend, especially
when not taking T_DRSMDN into account. But this is what happens when a
remote wakeup occurs between SetPortFeature USB_PORT_FEAT_SUSPEND on the
root hub and musb_bus_suspend being called.

This commit returns -EBUSY when musb_bus_suspend is called while remote
wakeup is signalled and thus avoids to reset the RESUME bit. Ignoring
this error when musb_port_suspend is called from musb_hub_control is ok.

Signed-off-by: Daniel Glöckner <dg@emlix.com>
Signed-off-by: Bin Liu <b-liu@ti.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/musb/musb_host.c
drivers/usb/musb/musb_host.h
drivers/usb/musb/musb_virthub.c

index e7f99d55922af9c82a19722f83794905879d24fc..15a42cee0a9c27ad930fd28ae637fb6d1d9262f9 100644 (file)
@@ -2524,8 +2524,11 @@ static int musb_bus_suspend(struct usb_hcd *hcd)
 {
        struct musb     *musb = hcd_to_musb(hcd);
        u8              devctl;
+       int             ret;
 
-       musb_port_suspend(musb, true);
+       ret = musb_port_suspend(musb, true);
+       if (ret)
+               return ret;
 
        if (!is_host_active(musb))
                return 0;
index 72392bbcd0a4b72bfead8dc2c26c7d40ea0947ee..2999845632cefb2fafa17d7da8622c06772dcda5 100644 (file)
@@ -67,7 +67,7 @@ extern void musb_host_rx(struct musb *, u8);
 extern void musb_root_disconnect(struct musb *musb);
 extern void musb_host_resume_root_hub(struct musb *musb);
 extern void musb_host_poke_root_hub(struct musb *musb);
-extern void musb_port_suspend(struct musb *musb, bool do_suspend);
+extern int musb_port_suspend(struct musb *musb, bool do_suspend);
 extern void musb_port_reset(struct musb *musb, bool do_reset);
 extern void musb_host_finish_resume(struct work_struct *work);
 #else
@@ -99,7 +99,10 @@ static inline void musb_root_disconnect(struct musb *musb)   {}
 static inline void musb_host_resume_root_hub(struct musb *musb)        {}
 static inline void musb_host_poll_rh_status(struct musb *musb) {}
 static inline void musb_host_poke_root_hub(struct musb *musb)  {}
-static inline void musb_port_suspend(struct musb *musb, bool do_suspend) {}
+static inline int musb_port_suspend(struct musb *musb, bool do_suspend)
+{
+       return 0;
+}
 static inline void musb_port_reset(struct musb *musb, bool do_reset) {}
 static inline void musb_host_finish_resume(struct work_struct *work) {}
 #endif
index 5165d2b07ade01985d2e9b104552b33d92850f47..2f8dd9826e9481a99e28f16a65be89e1e13a8955 100644 (file)
@@ -48,14 +48,14 @@ void musb_host_finish_resume(struct work_struct *work)
        spin_unlock_irqrestore(&musb->lock, flags);
 }
 
-void musb_port_suspend(struct musb *musb, bool do_suspend)
+int musb_port_suspend(struct musb *musb, bool do_suspend)
 {
        struct usb_otg  *otg = musb->xceiv->otg;
        u8              power;
        void __iomem    *mbase = musb->mregs;
 
        if (!is_host_active(musb))
-               return;
+               return 0;
 
        /* NOTE:  this doesn't necessarily put PHY into low power mode,
         * turning off its clock; that's a function of PHY integration and
@@ -66,16 +66,20 @@ void musb_port_suspend(struct musb *musb, bool do_suspend)
        if (do_suspend) {
                int retries = 10000;
 
-               power &= ~MUSB_POWER_RESUME;
-               power |= MUSB_POWER_SUSPENDM;
-               musb_writeb(mbase, MUSB_POWER, power);
+               if (power & MUSB_POWER_RESUME)
+                       return -EBUSY;
 
-               /* Needed for OPT A tests */
-               power = musb_readb(mbase, MUSB_POWER);
-               while (power & MUSB_POWER_SUSPENDM) {
+               if (!(power & MUSB_POWER_SUSPENDM)) {
+                       power |= MUSB_POWER_SUSPENDM;
+                       musb_writeb(mbase, MUSB_POWER, power);
+
+                       /* Needed for OPT A tests */
                        power = musb_readb(mbase, MUSB_POWER);
-                       if (retries-- < 1)
-                               break;
+                       while (power & MUSB_POWER_SUSPENDM) {
+                               power = musb_readb(mbase, MUSB_POWER);
+                               if (retries-- < 1)
+                                       break;
+                       }
                }
 
                musb_dbg(musb, "Root port suspended, power %02x", power);
@@ -111,6 +115,7 @@ void musb_port_suspend(struct musb *musb, bool do_suspend)
                schedule_delayed_work(&musb->finish_resume_work,
                                      msecs_to_jiffies(USB_RESUME_TIMEOUT));
        }
+       return 0;
 }
 
 void musb_port_reset(struct musb *musb, bool do_reset)