diff mbox

[net] net: qmi_wwan: fix Oops while disconnecting

Message ID CACVXFVPkC8NtvOzTsOyNcftZy60fDjtEkV_Da1Z32iYpHFHcUw@mail.gmail.com
State RFC, archived
Delegated to: David Miller
Headers show

Commit Message

Ming Lei June 22, 2012, 12:45 p.m. UTC
On Fri, Jun 22, 2012 at 5:11 PM, Bjørn Mork <bjorn@mork.no> wrote:
> usbnet_disconnect() will set intfdata to NULL before calling
> the minidriver unbind function.  The cdc_wdm subdriver cannot
> know that it is disconnecting until the qmi_wwan unbind
> function has called its disconnect function.  This means that
> we must be able to support the cdc_wdm subdriver operating
> normally while usbnet_disconnect() is running, and in
> particular that intfdata may be NULL.
>
> The only place this matters is in qmi_wwan_cdc_wdm_manage_power
> which is called from cdc_wdm.  Simply testing for NULL
> intfdata there is sufficient to allow it to continue working
> at all times.
>
> Fixes this Oops where a cdc-wdm device was closed while the
> USB device was disconnecting, causing wdm_release to call
> qmi_wwan_cdc_wdm_manage_power after intfdata was set to
> NULL by usbnet_disconnect:

In fact, it should be a general problem in usbnet, there are races
between usbnet_disconnect and .open/.close. Considered that
unregister_netdev, which will sync with .open/.close,  is called in
usbnet_disconnect,  the simplest fix is to move usb_set_intfdata(NULL)
after unregister_netdev.

> Reported-by: Marius Bjørnstad Kotsbak <marius.kotsbak@gmail.com>
> Cc: <stable@vger.kernel.org> # v3.4
> Signed-off-by: Bjørn Mork <bjorn@mork.no>
> ---
>  drivers/net/usb/qmi_wwan.c |    4 ++++
>  1 file changed, 4 insertions(+)
>
> diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c
> index 3767a12..b01960f 100644
> --- a/drivers/net/usb/qmi_wwan.c
> +++ b/drivers/net/usb/qmi_wwan.c
> @@ -197,6 +197,10 @@ err:
>  static int qmi_wwan_cdc_wdm_manage_power(struct usb_interface *intf, int on)
>  {
>        struct usbnet *dev = usb_get_intfdata(intf);
> +
> +       /* can be called while disconnecting */
> +       if (!dev)
> +               return 0;
>        return qmi_wwan_manage_power(dev, on);
>  }

Considered that usb_set_intfdata(NULL) will be called after
executing .disconnect in unbind path, it can be removed simply.

So how about the below?


Thanks,

Comments

Bjørn Mork June 22, 2012, 1:42 p.m. UTC | #1
Ming Lei <tom.leiming@gmail.com> writes:

> On Fri, Jun 22, 2012 at 5:11 PM, Bjørn Mork <bjorn@mork.no> wrote:
>> usbnet_disconnect() will set intfdata to NULL before calling
>> the minidriver unbind function.  The cdc_wdm subdriver cannot
>> know that it is disconnecting until the qmi_wwan unbind
>> function has called its disconnect function.  This means that
>> we must be able to support the cdc_wdm subdriver operating
>> normally while usbnet_disconnect() is running, and in
>> particular that intfdata may be NULL.
>>
>> The only place this matters is in qmi_wwan_cdc_wdm_manage_power
>> which is called from cdc_wdm.  Simply testing for NULL
>> intfdata there is sufficient to allow it to continue working
>> at all times.
>>
>> Fixes this Oops where a cdc-wdm device was closed while the
>> USB device was disconnecting, causing wdm_release to call
>> qmi_wwan_cdc_wdm_manage_power after intfdata was set to
>> NULL by usbnet_disconnect:
>
> In fact, it should be a general problem in usbnet, there are races
> between usbnet_disconnect and .open/.close. Considered that
> unregister_netdev, which will sync with .open/.close,  is called in
> usbnet_disconnect,  the simplest fix is to move usb_set_intfdata(NULL)
> after unregister_netdev.

Is there really a race there?  The usbnet .open/.close don't use the
intfdata, do they?  I looked briefly through usbnet for related
potentional problems while fixing this in qmi_wwan, but could only find
suspend/resume.  Which I believe are protected against running on
disconnect.

So I think usbnet in general is OK.

> Considered that usb_set_intfdata(NULL) will be called after
> executing .disconnect in unbind path, it can be removed simply.
>
> So how about the below?
>
> diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c
> index e92c057..2eb9e1e 100644
> --- a/drivers/net/usb/usbnet.c
> +++ b/drivers/net/usb/usbnet.c
> @@ -1286,7 +1286,6 @@ void usbnet_disconnect (struct usb_interface *intf)
>  	struct net_device	*net;
>
>  	dev = usb_get_intfdata(intf);
> -	usb_set_intfdata(intf, NULL);
>  	if (!dev)
>  		return;

I believe that call is there to prevent disconnect from running twice
for the common two-interface CDC ethernet model, like e.g. cdc_ether.
So I don't think it can be removed.  Not without touching the
minidrivers depending on that behaviour, anyway.


Bjørn
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ming Lei June 22, 2012, 3:09 p.m. UTC | #2
On Fri, Jun 22, 2012 at 9:42 PM, Bjørn Mork <bjorn@mork.no> wrote:
> Ming Lei <tom.leiming@gmail.com> writes:
>
>> On Fri, Jun 22, 2012 at 5:11 PM, Bjørn Mork <bjorn@mork.no> wrote:
>>> usbnet_disconnect() will set intfdata to NULL before calling
>>> the minidriver unbind function.  The cdc_wdm subdriver cannot
>>> know that it is disconnecting until the qmi_wwan unbind
>>> function has called its disconnect function.  This means that
>>> we must be able to support the cdc_wdm subdriver operating
>>> normally while usbnet_disconnect() is running, and in
>>> particular that intfdata may be NULL.
>>>
>>> The only place this matters is in qmi_wwan_cdc_wdm_manage_power
>>> which is called from cdc_wdm.  Simply testing for NULL
>>> intfdata there is sufficient to allow it to continue working
>>> at all times.
>>>
>>> Fixes this Oops where a cdc-wdm device was closed while the
>>> USB device was disconnecting, causing wdm_release to call
>>> qmi_wwan_cdc_wdm_manage_power after intfdata was set to
>>> NULL by usbnet_disconnect:
>>
>> In fact, it should be a general problem in usbnet, there are races
>> between usbnet_disconnect and .open/.close. Considered that
>> unregister_netdev, which will sync with .open/.close,  is called in
>> usbnet_disconnect,  the simplest fix is to move usb_set_intfdata(NULL)
>> after unregister_netdev.
>
> Is there really a race there?  The usbnet .open/.close don't use the
> intfdata, do they?  I looked briefly through usbnet for related

Suppose intfdata is not used in .open/.close, there are no the race.

> potentional problems while fixing this in qmi_wwan, but could only find
> suspend/resume.  Which I believe are protected against running on
> disconnect.
>
> So I think usbnet in general is OK.

Yes.

Looks I understand the problem incorrectly, your problem is
that qmi_wwan_cdc_wdm_manage_power is called from
wdm_open, not from usbnet_open. It is a problem crossing
2 class drivers.

>
>> Considered that usb_set_intfdata(NULL) will be called after
>> executing .disconnect in unbind path, it can be removed simply.
>>
>> So how about the below?
>>
>> diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c
>> index e92c057..2eb9e1e 100644
>> --- a/drivers/net/usb/usbnet.c
>> +++ b/drivers/net/usb/usbnet.c
>> @@ -1286,7 +1286,6 @@ void usbnet_disconnect (struct usb_interface *intf)
>>       struct net_device       *net;
>>
>>       dev = usb_get_intfdata(intf);
>> -     usb_set_intfdata(intf, NULL);
>>       if (!dev)
>>               return;
>
> I believe that call is there to prevent disconnect from running twice
> for the common two-interface CDC ethernet model, like e.g. cdc_ether.

No, intfdata is per interface, and .unbind will clear it for each interface.

> So I don't think it can be removed.  Not without touching the

It can be removed, but not necessary since it can't fix your problem.


Thanks,
Bjørn Mork June 22, 2012, 4:18 p.m. UTC | #3
Ming Lei <tom.leiming@gmail.com> writes:

> Looks I understand the problem incorrectly, your problem is
> that qmi_wwan_cdc_wdm_manage_power is called from
> wdm_open, not from usbnet_open. It is a problem crossing
> 2 class drivers.

That's correct.  Trying to use two drivers at once will necessarily lead
to the situation where one drivers shutdown is called from the other
drivers shutdown.  And the fact that one of the drivers is a usbnet
minidriver complicates this a bit.

But I believe most of it should be OK now.  I tried my best to let the
two drivers share as little as possible.  The only shared state is in
the power management code, which have to access the active/idle state of
both drivers.

>>> diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c
>>> index e92c057..2eb9e1e 100644
>>> --- a/drivers/net/usb/usbnet.c
>>> +++ b/drivers/net/usb/usbnet.c
>>> @@ -1286,7 +1286,6 @@ void usbnet_disconnect (struct usb_interface *intf)
>>>       struct net_device       *net;
>>>
>>>       dev = usb_get_intfdata(intf);
>>> -     usb_set_intfdata(intf, NULL);
>>>       if (!dev)
>>>               return;
>>
>> I believe that call is there to prevent disconnect from running twice
>> for the common two-interface CDC ethernet model, like e.g. cdc_ether.
>
> No, intfdata is per interface, and .unbind will clear it for each interface.


Yes, intfdata is per interface, but in the case of cdc_ether (and
probably other similar minidrivers) there are two interfaces pointing to
the *same* usbnet private data.

What about the situation where disconnect is called simultaneously for
both interfaces? Or can't that happen? If it can, then we'll do

 driver->disconnect(intf1)                driver->disconnect(intf2)
     dev = usb_get_intfdata(intf1)           dev = usb_get_intfdata(intf2)
     dev->driver_info->unbind()              dev->driver_info->unbind()
     net = dev->net                          net = dev->net
     free_netdev(net)                        free_netdev(dev->net)

where "dev" and "net" will be pointing to the same private data and
netdevice.

I assume that is what the code above is trying to protect against.  But
I could be wrong.  That has happened before, believe it or not :-)


Bjørn
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ming Lei June 22, 2012, 4:52 p.m. UTC | #4
On Sat, Jun 23, 2012 at 12:18 AM, Bjørn Mork <bjorn@mork.no> wrote:
> Ming Lei <tom.leiming@gmail.com> writes:

> Yes, intfdata is per interface, but in the case of cdc_ether (and
> probably other similar minidrivers) there are two interfaces pointing to
> the *same* usbnet private data.

Yes, the priv pointers of both interface points to the usbnet instance, but
the same pointer is stored into two places.

>
> What about the situation where disconnect is called simultaneously for
> both interfaces? Or can't that happen? If it can, then we'll do

It can't happen because both parent lock and its lock need to be held in
hotplug situation or unbind situation.

>
>  driver->disconnect(intf1)                driver->disconnect(intf2)
>     dev = usb_get_intfdata(intf1)           dev = usb_get_intfdata(intf2)
>     dev->driver_info->unbind()              dev->driver_info->unbind()
>     net = dev->net                          net = dev->net
>     free_netdev(net)                        free_netdev(dev->net)
>
> where "dev" and "net" will be pointing to the same private data and
> netdevice.

Suppose driver->disconnect(intf1) is called first, .ubind() inside
.disconnect will clear intfdata of another interface(intf2) and call
usb_driver_release_interface(intf2), which will cause .disconnect(intf2)
called, but it will return immediately.

>
> I assume that is what the code above is trying to protect against.  But

I don't see the protection in the code, :-)

> I could be wrong.  That has happened before, believe it or not :-)

Could you provide a link or commit about it?


Thanks,
Bjørn Mork June 22, 2012, 5:31 p.m. UTC | #5
Ming Lei <tom.leiming@gmail.com> writes:

> On Sat, Jun 23, 2012 at 12:18 AM, Bjørn Mork <bjorn@mork.no> wrote:
>> Ming Lei <tom.leiming@gmail.com> writes:
>
>> Yes, intfdata is per interface, but in the case of cdc_ether (and
>> probably other similar minidrivers) there are two interfaces pointing to
>> the *same* usbnet private data.
>
> Yes, the priv pointers of both interface points to the usbnet instance, but
> the same pointer is stored into two places.
>
>>
>> What about the situation where disconnect is called simultaneously for
>> both interfaces? Or can't that happen? If it can, then we'll do
>
> It can't happen because both parent lock and its lock need to be held in
> hotplug situation or unbind situation.

OK, then it can probably go away.  But then again it doesn't do any harm
for any other minidriver.  qmi_wwan is special because of the
cooperation with cdc_wdm.

>>  driver->disconnect(intf1)                driver->disconnect(intf2)
>>     dev = usb_get_intfdata(intf1)           dev = usb_get_intfdata(intf2)
>>     dev->driver_info->unbind()              dev->driver_info->unbind()
>>     net = dev->net                          net = dev->net
>>     free_netdev(net)                        free_netdev(dev->net)
>>
>> where "dev" and "net" will be pointing to the same private data and
>> netdevice.
>
> Suppose driver->disconnect(intf1) is called first, .ubind() inside
> .disconnect will clear intfdata of another interface(intf2) and call
> usb_driver_release_interface(intf2), which will cause .disconnect(intf2)
> called, but it will return immediately.

Yes, if that's the only possible call sequence then I agree that
removing the call won't be a problem.  But I don't see much gain either.
And it has been there since the beginning of git history, if that counts
for anything around here.

>> I assume that is what the code above is trying to protect against.  But
>
> I don't see the protection in the code, :-)

I am trying my best to be positive :-)


Bjørn
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c
index e92c057..2eb9e1e 100644
--- a/drivers/net/usb/usbnet.c
+++ b/drivers/net/usb/usbnet.c
@@ -1286,7 +1286,6 @@  void usbnet_disconnect (struct usb_interface *intf)
 	struct net_device	*net;

 	dev = usb_get_intfdata(intf);
-	usb_set_intfdata(intf, NULL);
 	if (!dev)
 		return;