diff mbox

[v3] USB: Force disconnect Huawei 4G modem during suspend

Message ID 20171018071501.4900-1-drake@endlessm.com (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Daniel Drake Oct. 18, 2017, 7:15 a.m. UTC
When going into S3 suspend, the Acer TravelMate P648-M and P648-G3
laptops immediately wake up 3-4 seconds later for no obvious reason.

Unbinding the integrated Huawei 4G LTE modem before suspend avoids
the issue, even though we are not using the modem at all (checked
from rescue.target/runlevel1). The problem also occurs when the option
and cdc-ether modem drivers aren't loaded; it reproduces just with the
base usb driver. Under Windows the system can suspend fine.

Seeking a better fix, we've tried a lot of things, including:
 - Check that the device's power/wakeup is disabled
 - Check that remote wakeup is off at the USB level
 - All the quirks in drivers/usb/core/quirks.c e.g. USB_QUIRK_RESET_RESUME,
   USB_QUIRK_RESET, USB_QUIRK_IGNORE_REMOTE_WAKEUP, USB_QUIRK_NO_LPM.

but none of that makes any difference.

There are no errors in the logs showing any suspend/resume-related issues.
When the system wakes up due to the modem, log-wise it appears to be a
normal resume.

Introduce a quirk to disable the port during suspend when the modem is
detected.

The modem from the P648-G3 model is:
T:  Bus=01 Lev=01 Prnt=01 Port=08 Cnt=04 Dev#=  5 Spd=480  MxCh= 0
D:  Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=ff MxPS=64 #Cfgs=  3
P:  Vendor=12d1 ProdID=15c3 Rev= 1.02
S:  Manufacturer=Huawei Technologies Co., Ltd.
S:  Product=HUAWEI Mobile
S:  SerialNumber=0123456789ABCDEF
C:  #Ifs= 5 Cfg#= 1 Atr=a0 MxPwr=  2mA
I:  If#= 0 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=06 Prot=10 Driver=
E:  Ad=82(I) Atr=03(Int.) MxPS=  10 Ivl=32ms
E:  Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E:  Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
I:  If#= 1 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=06 Prot=13 Driver=
E:  Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E:  Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
I:  If#= 2 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=06 Prot=12 Driver=
E:  Ad=84(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E:  Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
I:  If#= 3 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=06 Prot=16 Driver=
E:  Ad=86(I) Atr=03(Int.) MxPS=  16 Ivl=2ms
I:  If#= 3 Alt= 1 #EPs= 3 Cls=ff(vend.) Sub=06 Prot=16 Driver=
E:  Ad=86(I) Atr=03(Int.) MxPS=  16 Ivl=2ms
E:  Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E:  Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
I:  If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=06 Prot=1b Driver=
E:  Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E:  Ad=05(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
C:* #Ifs= 6 Cfg#= 2 Atr=a0 MxPwr=  2mA
I:* If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=06 Prot=00 Driver=cdc_ether
E:  Ad=82(I) Atr=03(Int.) MxPS=  16 Ivl=2ms
I:* If#= 1 Alt= 0 #EPs= 2 Cls=0a(data ) Sub=06 Prot=00 Driver=cdc_ether
E:  Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E:  Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
I:* If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=06 Prot=10 Driver=option
E:  Ad=84(I) Atr=03(Int.) MxPS=  10 Ivl=32ms
E:  Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E:  Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
I:* If#= 3 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=06 Prot=13 Driver=option
E:  Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E:  Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
I:* If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=06 Prot=12 Driver=option
E:  Ad=86(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E:  Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
I:* If#= 5 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=06 Prot=1b Driver=option
E:  Ad=87(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E:  Ad=05(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
C:  #Ifs= 2 Cfg#= 3 Atr=a0 MxPwr=  2mA
A:  FirstIf#= 0 IfCount= 2 Cls=02(comm.) Sub=0e Prot=00
I:  If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=0e Prot=00 Driver=
E:  Ad=82(I) Atr=03(Int.) MxPS=  16 Ivl=2ms
I:  If#= 1 Alt= 0 #EPs= 0 Cls=0a(data ) Sub=00 Prot=02 Driver=
I:  If#= 1 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=02 Driver=
E:  Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E:  Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms

Based on an earlier patch by Chris Chiu.

Signed-off-by: Daniel Drake <drake@endlessm.com>
---

Notes:
    v2:
    - Handle quirk later in suspend, to avoid interfering with other parts
      of the suspend routine.
    - Don't do the disconnect on runtime suspend, only for S3 suspend
    
    v3:
    - Ignore return value from usb_port_disable()
    - Correct kerneldoc Context information
    - Don't mark device as disconnected

 drivers/usb/core/driver.c  | 10 +++++++++-
 drivers/usb/core/hub.c     | 13 +++++++++++++
 drivers/usb/core/quirks.c  |  6 ++++++
 drivers/usb/core/usb.h     |  1 +
 include/linux/usb/quirks.h |  6 ++++++
 5 files changed, 35 insertions(+), 1 deletion(-)

Comments

Oliver Neukum Oct. 18, 2017, 9:31 a.m. UTC | #1
Am Mittwoch, den 18.10.2017, 15:15 +0800 schrieb Daniel Drake:
> Notes:
>     v2:
>     - Handle quirk later in suspend, to avoid interfering with other parts
>       of the suspend routine.
>     - Don't do the disconnect on runtime suspend, only for S3 suspend

Hi,

well, can we effectively runtime suspend these devices?
Furthermore, it seems to me that we indeed cannot do a runtime
suspend on external devices needing this quirk, but what about
internal devices?
And if we cannot do runtime suspend, should we not block it?

	Regards
		Oliver
Alan Stern Oct. 18, 2017, 2:55 p.m. UTC | #2
On Wed, 18 Oct 2017, Daniel Drake wrote:

> When going into S3 suspend, the Acer TravelMate P648-M and P648-G3
> laptops immediately wake up 3-4 seconds later for no obvious reason.
> 
> Unbinding the integrated Huawei 4G LTE modem before suspend avoids
> the issue, even though we are not using the modem at all (checked
> from rescue.target/runlevel1). The problem also occurs when the option
> and cdc-ether modem drivers aren't loaded; it reproduces just with the
> base usb driver. Under Windows the system can suspend fine.
> 
> Seeking a better fix, we've tried a lot of things, including:
>  - Check that the device's power/wakeup is disabled
>  - Check that remote wakeup is off at the USB level
>  - All the quirks in drivers/usb/core/quirks.c e.g. USB_QUIRK_RESET_RESUME,
>    USB_QUIRK_RESET, USB_QUIRK_IGNORE_REMOTE_WAKEUP, USB_QUIRK_NO_LPM.
> 
> but none of that makes any difference.
> 
> There are no errors in the logs showing any suspend/resume-related issues.
> When the system wakes up due to the modem, log-wise it appears to be a
> normal resume.
> 
> Introduce a quirk to disable the port during suspend when the modem is
> detected.

...

> Based on an earlier patch by Chris Chiu.
> 
> Signed-off-by: Daniel Drake <drake@endlessm.com>
> ---
> 
> Notes:
>     v2:
>     - Handle quirk later in suspend, to avoid interfering with other parts
>       of the suspend routine.
>     - Don't do the disconnect on runtime suspend, only for S3 suspend
>     
>     v3:
>     - Ignore return value from usb_port_disable()
>     - Correct kerneldoc Context information
>     - Don't mark device as disconnected
> 
>  drivers/usb/core/driver.c  | 10 +++++++++-
>  drivers/usb/core/hub.c     | 13 +++++++++++++
>  drivers/usb/core/quirks.c  |  6 ++++++
>  drivers/usb/core/usb.h     |  1 +
>  include/linux/usb/quirks.h |  6 ++++++
>  5 files changed, 35 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
> index eb87a259d55c..353993f983c8 100644
> --- a/drivers/usb/core/driver.c
> +++ b/drivers/usb/core/driver.c
> @@ -1461,6 +1461,7 @@ static void choose_wakeup(struct usb_device *udev, pm_message_t msg)
>  int usb_suspend(struct device *dev, pm_message_t msg)
>  {
>  	struct usb_device	*udev = to_usb_device(dev);
> +	int r;
>  
>  	unbind_no_pm_drivers_interfaces(udev);
>  
> @@ -1469,7 +1470,14 @@ int usb_suspend(struct device *dev, pm_message_t msg)
>  	 * so we may still need to unbind and rebind upon resume
>  	 */
>  	choose_wakeup(udev, msg);
> -	return usb_suspend_both(udev, msg);
> +	r = usb_suspend_both(udev, msg);
> +	if (r)
> +		return r;
> +
> +	if (udev->quirks & USB_QUIRK_DISCONNECT_SUSPEND)
> +		usb_port_disable(udev);
> +
> +	return 0;
>  }

Bikeshedding: I would write this as:

	if (r == 0 && (udev->quirks & USB_QUIRK_DISCONNECT_SUSPEND))
		usb_port_disable(udev);
	return r;

But it doesn't matter; the compiler probably will generate the same
code either way.  In any case:

Acked-by: Alan Stern <stern@rowland.harvard.edu>

Alan Stern
Daniel Drake Oct. 24, 2017, 4:16 a.m. UTC | #3
Hi Oliver,

On Wed, Oct 18, 2017 at 5:31 PM, Oliver Neukum <oneukum@suse.com> wrote:
> Am Mittwoch, den 18.10.2017, 15:15 +0800 schrieb Daniel Drake:
>> Notes:
>>     v2:
>>     - Handle quirk later in suspend, to avoid interfering with other parts
>>       of the suspend routine.
>>     - Don't do the disconnect on runtime suspend, only for S3 suspend
>
> well, can we effectively runtime suspend these devices?

How can I test for effective runtime suspend?

> Furthermore, it seems to me that we indeed cannot do a runtime
> suspend on external devices needing this quirk, but what about
> internal devices?

In this case the modem is an internal device.

Daniel
Oliver Neukum Oct. 24, 2017, 8:07 a.m. UTC | #4
Am Dienstag, den 24.10.2017, 12:16 +0800 schrieb Daniel Drake:
> Hi Oliver,
> 
> On Wed, Oct 18, 2017 at 5:31 PM, Oliver Neukum <oneukum@suse.com> wrote:
> > 
> > Am Mittwoch, den 18.10.2017, 15:15 +0800 schrieb Daniel Drake:
> > > 
> > > Notes:
> > >     v2:
> > >     - Handle quirk later in suspend, to avoid interfering with other parts
> > >       of the suspend routine.
> > >     - Don't do the disconnect on runtime suspend, only for S3 suspend
> > 
> > well, can we effectively runtime suspend these devices?
> 
> How can I test for effective runtime suspend?

No, I meant, does it make sense to do that?

> > Furthermore, it seems to me that we indeed cannot do a runtime
> > suspend on external devices needing this quirk, but what about
> > internal devices?
> 
> In this case the modem is an internal device.

It seems to me that obviously if you disable a device's port you
cannot support remote wakeup. But I do not see why, if the device
is internal you would want to block runtime suspend, even if
remote wakeup is not necessary.

	Regards
		Oliver
diff mbox

Patch

diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index eb87a259d55c..353993f983c8 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -1461,6 +1461,7 @@  static void choose_wakeup(struct usb_device *udev, pm_message_t msg)
 int usb_suspend(struct device *dev, pm_message_t msg)
 {
 	struct usb_device	*udev = to_usb_device(dev);
+	int r;
 
 	unbind_no_pm_drivers_interfaces(udev);
 
@@ -1469,7 +1470,14 @@  int usb_suspend(struct device *dev, pm_message_t msg)
 	 * so we may still need to unbind and rebind upon resume
 	 */
 	choose_wakeup(udev, msg);
-	return usb_suspend_both(udev, msg);
+	r = usb_suspend_both(udev, msg);
+	if (r)
+		return r;
+
+	if (udev->quirks & USB_QUIRK_DISCONNECT_SUSPEND)
+		usb_port_disable(udev);
+
+	return 0;
 }
 
 /* The device lock is held by the PM core */
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index b5c733613823..941968f011df 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -4180,6 +4180,19 @@  static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
 	return ret;
 }
 
+/*
+ * usb_port_disable - disable a usb device's upstream port
+ * @udev: device to disable
+ * Context: @udev locked, must be able to sleep.
+ *
+ * Disables a USB device that isn't in active use.
+ */
+int usb_port_disable(struct usb_device *udev)
+{
+	struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
+
+	return hub_port_disable(hub, udev->portnum, 0);
+}
 
 /* USB 2.0 spec, 7.1.7.3 / fig 7-29:
  *
diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
index 82806e311202..746d2b19109c 100644
--- a/drivers/usb/core/quirks.c
+++ b/drivers/usb/core/quirks.c
@@ -203,6 +203,12 @@  static const struct usb_device_id usb_quirk_list[] = {
 	{ USB_DEVICE(0x10d6, 0x2200), .driver_info =
 			USB_QUIRK_STRING_FETCH_255 },
 
+	/* Huawei 4G LTE module */
+	{ USB_DEVICE(0x12d1, 0x15bb), .driver_info =
+			USB_QUIRK_DISCONNECT_SUSPEND },
+	{ USB_DEVICE(0x12d1, 0x15c3), .driver_info =
+			USB_QUIRK_DISCONNECT_SUSPEND },
+
 	/* SKYMEDI USB_DRIVE */
 	{ USB_DEVICE(0x1516, 0x8628), .driver_info = USB_QUIRK_RESET_RESUME },
 
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index dc6949248823..f71890a2db4e 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -73,6 +73,7 @@  extern void usb_hub_cleanup(void);
 extern int usb_major_init(void);
 extern void usb_major_cleanup(void);
 extern int usb_device_supports_lpm(struct usb_device *udev);
+extern int usb_port_disable(struct usb_device *udev);
 
 #ifdef	CONFIG_PM
 
diff --git a/include/linux/usb/quirks.h b/include/linux/usb/quirks.h
index de2a722fe3cf..bdc639cc80b4 100644
--- a/include/linux/usb/quirks.h
+++ b/include/linux/usb/quirks.h
@@ -56,4 +56,10 @@ 
  */
 #define USB_QUIRK_LINEAR_FRAME_INTR_BINTERVAL	BIT(11)
 
+/*
+ * Device needs to be disconnected before suspend to prevent spurious
+ * wakeup.
+ */
+#define USB_QUIRK_DISCONNECT_SUSPEND		BIT(12)
+
 #endif /* __LINUX_USB_QUIRKS_H */