[v2,10/10] usb: dwc2/gadget: rework suspend/resume code to correctly restore gadget state

Message ID 1413801940-31086-11-git-send-email-m.szyprowski@samsung.com
State New
Headers show

Commit Message

Marek Szyprowski Oct. 20, 2014, 10:45 a.m.
Suspend/resume code assumed that the gadget was always enabled and
connected to usb bus. This means that the actual state of the gadget
(soft-enabled/disabled or connected/disconnected) was not correctly
preserved on suspend/resume cycle. This patch fixes this issue.

Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
---
 drivers/usb/dwc2/core.h   |  4 +++-
 drivers/usb/dwc2/gadget.c | 43 +++++++++++++++++++++++++++----------------
 2 files changed, 30 insertions(+), 17 deletions(-)

Comments

Paul Zimmerman Oct. 25, 2014, 1:16 a.m. | #1
> From: Marek Szyprowski [mailto:m.szyprowski@samsung.com]
> Sent: Monday, October 20, 2014 3:46 AM
> 
> Suspend/resume code assumed that the gadget was always enabled and
> connected to usb bus. This means that the actual state of the gadget
> (soft-enabled/disabled or connected/disconnected) was not correctly
> preserved on suspend/resume cycle. This patch fixes this issue.
> 
> Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
> ---
>  drivers/usb/dwc2/core.h   |  4 +++-
>  drivers/usb/dwc2/gadget.c | 43 +++++++++++++++++++++++++++----------------
>  2 files changed, 30 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h
> index bf015ab3b44c..3648b76a18b4 100644
> --- a/drivers/usb/dwc2/core.h
> +++ b/drivers/usb/dwc2/core.h
> @@ -210,7 +210,9 @@ struct s3c_hsotg {
>  	u8                      ctrl_buff[8];
> 
>  	struct usb_gadget       gadget;
> -	unsigned int            setup;
> +	unsigned int            setup:1;
> +	unsigned int		connected:1;
> +	unsigned int		enabled:1;
>  	unsigned long           last_rst;
>  	struct s3c_hsotg_ep     *eps;
>  };
> diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c
> index 0d34cfc71bfb..c6c6cf982c90 100644
> --- a/drivers/usb/dwc2/gadget.c
> +++ b/drivers/usb/dwc2/gadget.c
> @@ -2925,6 +2925,8 @@ static int s3c_hsotg_udc_start(struct usb_gadget *gadget,
>  	spin_lock_irqsave(&hsotg->lock, flags);
>  	s3c_hsotg_init(hsotg);
>  	s3c_hsotg_core_init_disconnected(hsotg);
> +	hsotg->enabled = 1;
> +	hsotg->connected = 0;
>  	spin_unlock_irqrestore(&hsotg->lock, flags);
> 
>  	dev_info(hsotg->dev, "bound driver %s\n", driver->driver.name);
> @@ -2961,6 +2963,8 @@ static int s3c_hsotg_udc_stop(struct usb_gadget *gadget,
> 
>  	hsotg->driver = NULL;
>  	hsotg->gadget.speed = USB_SPEED_UNKNOWN;
> +	hsotg->enabled = 0;
> +	hsotg->connected = 0;
> 
>  	spin_unlock_irqrestore(&hsotg->lock, flags);
> 
> @@ -2999,11 +3003,14 @@ static int s3c_hsotg_pullup(struct usb_gadget *gadget, int is_on)
>  	dev_dbg(hsotg->dev, "%s: is_on: %d\n", __func__, is_on);
> 
>  	spin_lock_irqsave(&hsotg->lock, flags);
> +
>  	if (is_on) {
>  		clk_enable(hsotg->clk);
> +		hsotg->connected = 1;
>  		s3c_hsotg_core_connect(hsotg);
>  	} else {
>  		s3c_hsotg_core_disconnect(hsotg);
> +		hsotg->connected = 0;
>  		clk_disable(hsotg->clk);
>  	}
> 
> @@ -3652,16 +3659,18 @@ static int s3c_hsotg_suspend(struct platform_device *pdev, pm_message_t state)
>  		dev_info(hsotg->dev, "suspending usb gadget %s\n",
>  			 hsotg->driver->driver.name);
> 
> -	spin_lock_irqsave(&hsotg->lock, flags);
> -	s3c_hsotg_core_disconnect(hsotg);
> -	s3c_hsotg_disconnect(hsotg);
> -	hsotg->gadget.speed = USB_SPEED_UNKNOWN;
> -	spin_unlock_irqrestore(&hsotg->lock, flags);
> +	if (hsotg->enabled) {

Hmm. Are you sure it's safe to check ->enabled outside of the spinlock?
What happens if s3c_hsotg_udc_stop() runs right after this, before the
spinlock is taken, and disables stuff? Sure, it's a tiny window, but
still...
Marek Szyprowski Oct. 27, 2014, 7:18 a.m. | #2
Hello,

On 2014-10-25 03:16, Paul Zimmerman wrote:
>> From: Marek Szyprowski [mailto:m.szyprowski@samsung.com]
>> Sent: Monday, October 20, 2014 3:46 AM
>>
>> Suspend/resume code assumed that the gadget was always enabled and
>> connected to usb bus. This means that the actual state of the gadget
>> (soft-enabled/disabled or connected/disconnected) was not correctly
>> preserved on suspend/resume cycle. This patch fixes this issue.
>>
>> Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
>> ---
>>   drivers/usb/dwc2/core.h   |  4 +++-
>>   drivers/usb/dwc2/gadget.c | 43 +++++++++++++++++++++++++++----------------
>>   2 files changed, 30 insertions(+), 17 deletions(-)
>>
>> diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h
>> index bf015ab3b44c..3648b76a18b4 100644
>> --- a/drivers/usb/dwc2/core.h
>> +++ b/drivers/usb/dwc2/core.h
>> @@ -210,7 +210,9 @@ struct s3c_hsotg {
>>   	u8                      ctrl_buff[8];
>>
>>   	struct usb_gadget       gadget;
>> -	unsigned int            setup;
>> +	unsigned int            setup:1;
>> +	unsigned int		connected:1;
>> +	unsigned int		enabled:1;
>>   	unsigned long           last_rst;
>>   	struct s3c_hsotg_ep     *eps;
>>   };
>> diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c
>> index 0d34cfc71bfb..c6c6cf982c90 100644
>> --- a/drivers/usb/dwc2/gadget.c
>> +++ b/drivers/usb/dwc2/gadget.c
>> @@ -2925,6 +2925,8 @@ static int s3c_hsotg_udc_start(struct usb_gadget *gadget,
>>   	spin_lock_irqsave(&hsotg->lock, flags);
>>   	s3c_hsotg_init(hsotg);
>>   	s3c_hsotg_core_init_disconnected(hsotg);
>> +	hsotg->enabled = 1;
>> +	hsotg->connected = 0;
>>   	spin_unlock_irqrestore(&hsotg->lock, flags);
>>
>>   	dev_info(hsotg->dev, "bound driver %s\n", driver->driver.name);
>> @@ -2961,6 +2963,8 @@ static int s3c_hsotg_udc_stop(struct usb_gadget *gadget,
>>
>>   	hsotg->driver = NULL;
>>   	hsotg->gadget.speed = USB_SPEED_UNKNOWN;
>> +	hsotg->enabled = 0;
>> +	hsotg->connected = 0;
>>
>>   	spin_unlock_irqrestore(&hsotg->lock, flags);
>>
>> @@ -2999,11 +3003,14 @@ static int s3c_hsotg_pullup(struct usb_gadget *gadget, int is_on)
>>   	dev_dbg(hsotg->dev, "%s: is_on: %d\n", __func__, is_on);
>>
>>   	spin_lock_irqsave(&hsotg->lock, flags);
>> +
>>   	if (is_on) {
>>   		clk_enable(hsotg->clk);
>> +		hsotg->connected = 1;
>>   		s3c_hsotg_core_connect(hsotg);
>>   	} else {
>>   		s3c_hsotg_core_disconnect(hsotg);
>> +		hsotg->connected = 0;
>>   		clk_disable(hsotg->clk);
>>   	}
>>
>> @@ -3652,16 +3659,18 @@ static int s3c_hsotg_suspend(struct platform_device *pdev, pm_message_t state)
>>   		dev_info(hsotg->dev, "suspending usb gadget %s\n",
>>   			 hsotg->driver->driver.name);
>>
>> -	spin_lock_irqsave(&hsotg->lock, flags);
>> -	s3c_hsotg_core_disconnect(hsotg);
>> -	s3c_hsotg_disconnect(hsotg);
>> -	hsotg->gadget.speed = USB_SPEED_UNKNOWN;
>> -	spin_unlock_irqrestore(&hsotg->lock, flags);
>> +	if (hsotg->enabled) {
> Hmm. Are you sure it's safe to check ->enabled outside of the spinlock?
> What happens if s3c_hsotg_udc_stop() runs right after this, before the
> spinlock is taken, and disables stuff? Sure, it's a tiny window, but
> still...

This code is called only in system suspend path, when userspace has been 
already frozen.
udc_stop() can be called only as a result of userspace action, so it is 
imho safe to
assume that the above code doesn't need additional locking.

Best regards
Marek Szyprowski Oct. 31, 2014, 10:08 a.m. | #3
Hello,

On 2014-10-27 08:18, Marek Szyprowski wrote:
>
> On 2014-10-25 03:16, Paul Zimmerman wrote:
>>> From: Marek Szyprowski [mailto:m.szyprowski@samsung.com]
>>> Sent: Monday, October 20, 2014 3:46 AM
>>>
>>> Suspend/resume code assumed that the gadget was always enabled and
>>> connected to usb bus. This means that the actual state of the gadget
>>> (soft-enabled/disabled or connected/disconnected) was not correctly
>>> preserved on suspend/resume cycle. This patch fixes this issue.
>>>
>>> Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
>>> ---
>>>   drivers/usb/dwc2/core.h   |  4 +++-
>>>   drivers/usb/dwc2/gadget.c | 43 
>>> +++++++++++++++++++++++++++----------------
>>>   2 files changed, 30 insertions(+), 17 deletions(-)
>>>
>>> diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h
>>> index bf015ab3b44c..3648b76a18b4 100644
>>> --- a/drivers/usb/dwc2/core.h
>>> +++ b/drivers/usb/dwc2/core.h
>>> @@ -210,7 +210,9 @@ struct s3c_hsotg {
>>>       u8                      ctrl_buff[8];
>>>
>>>       struct usb_gadget       gadget;
>>> -    unsigned int            setup;
>>> +    unsigned int            setup:1;
>>> +    unsigned int        connected:1;
>>> +    unsigned int        enabled:1;
>>>       unsigned long           last_rst;
>>>       struct s3c_hsotg_ep     *eps;
>>>   };
>>> diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c
>>> index 0d34cfc71bfb..c6c6cf982c90 100644
>>> --- a/drivers/usb/dwc2/gadget.c
>>> +++ b/drivers/usb/dwc2/gadget.c
>>> @@ -2925,6 +2925,8 @@ static int s3c_hsotg_udc_start(struct 
>>> usb_gadget *gadget,
>>>       spin_lock_irqsave(&hsotg->lock, flags);
>>>       s3c_hsotg_init(hsotg);
>>>       s3c_hsotg_core_init_disconnected(hsotg);
>>> +    hsotg->enabled = 1;
>>> +    hsotg->connected = 0;
>>>       spin_unlock_irqrestore(&hsotg->lock, flags);
>>>
>>>       dev_info(hsotg->dev, "bound driver %s\n", driver->driver.name);
>>> @@ -2961,6 +2963,8 @@ static int s3c_hsotg_udc_stop(struct 
>>> usb_gadget *gadget,
>>>
>>>       hsotg->driver = NULL;
>>>       hsotg->gadget.speed = USB_SPEED_UNKNOWN;
>>> +    hsotg->enabled = 0;
>>> +    hsotg->connected = 0;
>>>
>>>       spin_unlock_irqrestore(&hsotg->lock, flags);
>>>
>>> @@ -2999,11 +3003,14 @@ static int s3c_hsotg_pullup(struct 
>>> usb_gadget *gadget, int is_on)
>>>       dev_dbg(hsotg->dev, "%s: is_on: %d\n", __func__, is_on);
>>>
>>>       spin_lock_irqsave(&hsotg->lock, flags);
>>> +
>>>       if (is_on) {
>>>           clk_enable(hsotg->clk);
>>> +        hsotg->connected = 1;
>>>           s3c_hsotg_core_connect(hsotg);
>>>       } else {
>>>           s3c_hsotg_core_disconnect(hsotg);
>>> +        hsotg->connected = 0;
>>>           clk_disable(hsotg->clk);
>>>       }
>>>
>>> @@ -3652,16 +3659,18 @@ static int s3c_hsotg_suspend(struct 
>>> platform_device *pdev, pm_message_t state)
>>>           dev_info(hsotg->dev, "suspending usb gadget %s\n",
>>>                hsotg->driver->driver.name);
>>>
>>> -    spin_lock_irqsave(&hsotg->lock, flags);
>>> -    s3c_hsotg_core_disconnect(hsotg);
>>> -    s3c_hsotg_disconnect(hsotg);
>>> -    hsotg->gadget.speed = USB_SPEED_UNKNOWN;
>>> -    spin_unlock_irqrestore(&hsotg->lock, flags);
>>> +    if (hsotg->enabled) {
>> Hmm. Are you sure it's safe to check ->enabled outside of the spinlock?
>> What happens if s3c_hsotg_udc_stop() runs right after this, before the
>> spinlock is taken, and disables stuff? Sure, it's a tiny window, but
>> still...
>
> This code is called only in system suspend path, when userspace has 
> been already frozen.
> udc_stop() can be called only as a result of userspace action, so it 
> is imho safe to
> assume that the above code doesn't need additional locking.

However if you prefer the version with explicit locking, I will send v3 
in a few minutes.

Best regards

Patch

diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h
index bf015ab3b44c..3648b76a18b4 100644
--- a/drivers/usb/dwc2/core.h
+++ b/drivers/usb/dwc2/core.h
@@ -210,7 +210,9 @@  struct s3c_hsotg {
 	u8                      ctrl_buff[8];
 
 	struct usb_gadget       gadget;
-	unsigned int            setup;
+	unsigned int            setup:1;
+	unsigned int		connected:1;
+	unsigned int		enabled:1;
 	unsigned long           last_rst;
 	struct s3c_hsotg_ep     *eps;
 };
diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c
index 0d34cfc71bfb..c6c6cf982c90 100644
--- a/drivers/usb/dwc2/gadget.c
+++ b/drivers/usb/dwc2/gadget.c
@@ -2925,6 +2925,8 @@  static int s3c_hsotg_udc_start(struct usb_gadget *gadget,
 	spin_lock_irqsave(&hsotg->lock, flags);
 	s3c_hsotg_init(hsotg);
 	s3c_hsotg_core_init_disconnected(hsotg);
+	hsotg->enabled = 1;
+	hsotg->connected = 0;
 	spin_unlock_irqrestore(&hsotg->lock, flags);
 
 	dev_info(hsotg->dev, "bound driver %s\n", driver->driver.name);
@@ -2961,6 +2963,8 @@  static int s3c_hsotg_udc_stop(struct usb_gadget *gadget,
 
 	hsotg->driver = NULL;
 	hsotg->gadget.speed = USB_SPEED_UNKNOWN;
+	hsotg->enabled = 0;
+	hsotg->connected = 0;
 
 	spin_unlock_irqrestore(&hsotg->lock, flags);
 
@@ -2999,11 +3003,14 @@  static int s3c_hsotg_pullup(struct usb_gadget *gadget, int is_on)
 	dev_dbg(hsotg->dev, "%s: is_on: %d\n", __func__, is_on);
 
 	spin_lock_irqsave(&hsotg->lock, flags);
+
 	if (is_on) {
 		clk_enable(hsotg->clk);
+		hsotg->connected = 1;
 		s3c_hsotg_core_connect(hsotg);
 	} else {
 		s3c_hsotg_core_disconnect(hsotg);
+		hsotg->connected = 0;
 		clk_disable(hsotg->clk);
 	}
 
@@ -3652,16 +3659,18 @@  static int s3c_hsotg_suspend(struct platform_device *pdev, pm_message_t state)
 		dev_info(hsotg->dev, "suspending usb gadget %s\n",
 			 hsotg->driver->driver.name);
 
-	spin_lock_irqsave(&hsotg->lock, flags);
-	s3c_hsotg_core_disconnect(hsotg);
-	s3c_hsotg_disconnect(hsotg);
-	hsotg->gadget.speed = USB_SPEED_UNKNOWN;
-	spin_unlock_irqrestore(&hsotg->lock, flags);
+	if (hsotg->enabled) {
+		int ep;
 
-	s3c_hsotg_phy_disable(hsotg);
+		spin_lock_irqsave(&hsotg->lock, flags);
+		if (hsotg->connected)
+			s3c_hsotg_core_disconnect(hsotg);
+		s3c_hsotg_disconnect(hsotg);
+		hsotg->gadget.speed = USB_SPEED_UNKNOWN;
+		spin_unlock_irqrestore(&hsotg->lock, flags);
+
+		s3c_hsotg_phy_disable(hsotg);
 
-	if (hsotg->driver) {
-		int ep;
 		for (ep = 0; ep < hsotg->num_of_eps; ep++)
 			s3c_hsotg_ep_disable(&hsotg->eps[ep].ep);
 
@@ -3679,21 +3688,23 @@  static int s3c_hsotg_resume(struct platform_device *pdev)
 	unsigned long flags;
 	int ret = 0;
 
-	if (hsotg->driver) {
+	if (hsotg->driver)
 		dev_info(hsotg->dev, "resuming usb gadget %s\n",
 			 hsotg->driver->driver.name);
 
+	if (hsotg->enabled) {
 		clk_enable(hsotg->clk);
 		ret = regulator_bulk_enable(ARRAY_SIZE(hsotg->supplies),
-				      hsotg->supplies);
-	}
+					    hsotg->supplies);
 
-	s3c_hsotg_phy_enable(hsotg);
+		s3c_hsotg_phy_enable(hsotg);
 
-	spin_lock_irqsave(&hsotg->lock, flags);
-	s3c_hsotg_core_init_disconnected(hsotg);
-	s3c_hsotg_core_connect(hsotg);
-	spin_unlock_irqrestore(&hsotg->lock, flags);
+		spin_lock_irqsave(&hsotg->lock, flags);
+		s3c_hsotg_core_init_disconnected(hsotg);
+		if (hsotg->connected)
+			s3c_hsotg_core_connect(hsotg);
+		spin_unlock_irqrestore(&hsotg->lock, flags);
+	}
 
 	return ret;
 }