On Wed, 2014-04-30 at 15:51 -0400, Alan Stern wrote:
> On Wed, 30 Apr 2014, Dan Williams wrote:
>
> > On Wed, 2014-04-30 at 10:04 -0400, Alan Stern wrote:
> > > On Tue, 29 Apr 2014, Dan Williams wrote:
> > >
> > > > > What happens if a thread tries to resume or suspend a port while the
> > > > > hub is being reset? With nothing to prevent it, the request sent to
> > > > > the hub will fail and the port may end up in a runtime PM error state.
> > > > >
> > > >
> > > > I'm expected to be protected by:
> > > >
> > > > /* Prevent autosuspend during the reset */
> > > > usb_autoresume_device(udev);
> > > >
> > > > ...in usb_reset_device() and usb_reset_and_verify_device()'s
> > > > requirement that the device in question not be suspended. The hub
> > > > should pin it's parent port active during the reset.
> > >
> > > My question wasn't clear enough. What happens if, while hub H is being
> > > reset, a thread tries to resume a port on hub H (not on H's parent)?
> > >
> >
> > Ah, yes, I think we need something like the following. I'll fold it
> > into the re-submit.
> >
> > diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
> > index 943366345beb..02b0833b0a67 100644
> > --- a/drivers/usb/core/hub.c
> > +++ b/drivers/usb/core/hub.c
> > @@ -5183,6 +5183,31 @@ static int descriptors_changed(struct usb_device
> > *udev,
> > return changed;
> > }
> >
> > +static void usb_hub_ports_pm_get_sync(struct usb_device *hdev)
> > +{
> > + struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
> > + int port1;
> > +
> > + for (port1 = 1; port1 <= hdev->maxchild; port1++) {
> > + struct usb_port *port_dev = hub->ports[port1 - 1];
> > +
> > + pm_runtime_get_sync(&port_dev->dev);
> > + }
> > +}
> > +
> > +static void usb_hub_ports_pm_put(struct usb_device *hdev)
> > +{
> > + struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
> > + int port1;
> > +
> > + for (port1 = 1; port1 <= hdev->maxchild; port1++) {
> > + struct usb_port *port_dev = hub->ports[port1 - 1];
> > +
> > + pm_runtime_put(&port_dev->dev);
> > + }
> > +}
> > +
> > +
> > /**
> > * usb_reset_and_verify_device - perform a USB port reset to reinitialize
> > a device
> > * @udev: device to reset (not in SUSPENDED or NOTATTACHED state)
> > @@ -5239,6 +5264,9 @@ static int usb_reset_and_verify_device(struct
> > usb_device *udev)
> >
> > parent_hub = usb_hub_to_struct_hub(parent_hdev);
> >
> > + /* Disable usb_port power management */
> > + usb_hub_ports_pm_get_sync(udev);
>
> I'm kind of doubtful about this. Remember, if the hub is being reset
> then it probably isn't working correctly. There's a good chance that
> asking it to change a port's power level will fail.
>
> It would be better if the port suspend and resume routines knew when a
> hub reset was in progress. If it was, they could set the power_is_on
> flag appropriately and immediately return 0. When the reset finished,
> hub_activate() would then automatically apply power to the right ports.
Great point. Continuing the line of thinking I think we should also
clear any previous pm runtime errors in the ports when the hub is reset.
Hmm, I'm struggling to make the update of the "hub->in_reset" state
atomic with respect to in-flight runtime pm transitions. How about this
incremental addition to the previous suggestion where we:
1/ Still try to runtime resume all the ports
2/ Force any ports that didn't make the transition active
3/ Clear any runtime errors as a result of that forcing, and mark the
ports "on" for the purpose of hub_power_on().
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 02b0833b0a67..a662c7172f5b 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -5183,31 +5183,36 @@ static int descriptors_changed(struct usb_device *udev,
return changed;
}
-static void usb_hub_ports_pm_get_sync(struct usb_device *hdev)
+static void usb_hub_pm_prep_reset(struct usb_device *hdev)
{
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
+ struct usb_port *port_dev;
int port1;
+ if (!hub)
+ return;
for (port1 = 1; port1 <= hdev->maxchild; port1++) {
- struct usb_port *port_dev = hub->ports[port1 - 1];
-
+ port_dev = hub->ports[port1 - 1];
pm_runtime_get_sync(&port_dev->dev);
+ pm_runtime_set_active(&port_dev->dev);
+ set_bit(port1, hub->power_bits);
}
}
-static void usb_hub_ports_pm_put(struct usb_device *hdev)
+static void usb_hub_pm_finish_reset(struct usb_device *hdev)
{
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
+ struct usb_port *port_dev;
int port1;
+ if (!hub)
+ return;
for (port1 = 1; port1 <= hdev->maxchild; port1++) {
- struct usb_port *port_dev = hub->ports[port1 - 1];
-
+ port_dev = hub->ports[port1 - 1];
pm_runtime_put(&port_dev->dev);
}
}
-
/**
* usb_reset_and_verify_device - perform a USB port reset to reinitialize a
device
* @udev: device to reset (not in SUSPENDED or NOTATTACHED state)
@@ -5265,7 +5270,7 @@ static int usb_reset_and_verify_device(struct usb_device
*udev)
parent_hub = usb_hub_to_struct_hub(parent_hdev);
/* Disable usb_port power management */
- usb_hub_ports_pm_get_sync(udev);
+ usb_hub_pm_prep_reset(udev);
/* Disable USB2 hardware LPM.
* It will be re-enabled by the enumeration process.
@@ -5386,11 +5391,11 @@ done:
usb_enable_ltm(udev);
usb_release_bos_descriptor(udev);
udev->bos = bos;
- usb_hub_ports_pm_put(udev);
+ usb_hub_pm_finish_reset(udev);
return 0;
re_enumerate:
- usb_hub_ports_pm_put(udev);
+ usb_hub_pm_finish_reset(udev);
/* LPM state doesn't matter when we're about to destroy the device. */
hub_port_logical_disconnect(parent_hub, port1);
usb_release_bos_descriptor(udev);
>
> Alan Stern
>
> PS: Regarding those atomic flags... Maybe the best way to save space
> is to move the existing atomic flags in usb_hub down into the usb_port
> structures. Along with the two flags that are already there, we'd end
> up with one long per port rather than 6 longs per hub. What do you
> think?
>
I did the conversion of putting them in struct usb_hub and aesthetically
I like the look of:
_bit(port1, hub->power_bits)
...better than:
_bit(USB_PORTDEV_POWER, &port_dev->flags)
So I'm inclined to keep them there regardless of the space argument.
Thoughts?
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html