diff options
author | Dan Williams <dan.j.williams@intel.com> | 2014-06-17 19:16:32 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-06-17 20:04:39 -0400 |
commit | e3d105055525d9ea9f8e9cb0db8237df3df1bb9f (patch) | |
tree | 37448f1ac032726d1e290124734a6a7a067d095d /drivers/usb/core | |
parent | 6c79fe4afcb0450bd638f6e959e512aad270ff2f (diff) |
usb: fix hub-port pm_runtime_enable() vs runtime pm transitions
Commit 9262c19d14c4 "usb: disable port power control if not supported in
wHubCharacteristics" gated enabling runtime pm for usb_port devices on
whether the parent hub supports power control, which causes a
regression. The port must still be allowed to carry out runtime pm
callbacks and receive a -EAGAIN or -EBUSY result. Otherwise the
usb_port device will transition to the pm error state and trigger the
same for the child usb_device.
Prior to the offending commit usb_hub_create_port_device() arranged for
runtime pm to be disabled is dev_pm_qos_expose_flags() failed. Instead,
force the default state of PM_QOS_FLAG_NO_POWER_OFF flag to be set prior
to enabling runtime pm. If that policy can not be set then fail
registration.
Report: http://marc.info/?l=linux-usb&m=140290586301336&w=2
Fixes: 9262c19d14c4 ("usb: disable port power control if not supported in wHubCharacteristics")
Reported-by: Bjørn Mork <bjorn@mork.no>
Reported-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/core')
-rw-r--r-- | drivers/usb/core/hub.c | 6 | ||||
-rw-r--r-- | drivers/usb/core/hub.h | 2 | ||||
-rw-r--r-- | drivers/usb/core/port.c | 65 |
3 files changed, 54 insertions, 19 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 78c3cd20d7ae..21b99b4b4082 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c | |||
@@ -1577,6 +1577,12 @@ static int hub_configure(struct usb_hub *hub, | |||
1577 | } | 1577 | } |
1578 | } | 1578 | } |
1579 | hdev->maxchild = i; | 1579 | hdev->maxchild = i; |
1580 | for (i = 0; i < hdev->maxchild; i++) { | ||
1581 | struct usb_port *port_dev = hub->ports[i]; | ||
1582 | |||
1583 | pm_runtime_put(&port_dev->dev); | ||
1584 | } | ||
1585 | |||
1580 | mutex_unlock(&usb_port_peer_mutex); | 1586 | mutex_unlock(&usb_port_peer_mutex); |
1581 | if (ret < 0) | 1587 | if (ret < 0) |
1582 | goto fail; | 1588 | goto fail; |
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 0a7cdc0ef0a9..326308e53961 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h | |||
@@ -84,6 +84,7 @@ struct usb_hub { | |||
84 | * @dev: generic device interface | 84 | * @dev: generic device interface |
85 | * @port_owner: port's owner | 85 | * @port_owner: port's owner |
86 | * @peer: related usb2 and usb3 ports (share the same connector) | 86 | * @peer: related usb2 and usb3 ports (share the same connector) |
87 | * @req: default pm qos request for hubs without port power control | ||
87 | * @connect_type: port's connect type | 88 | * @connect_type: port's connect type |
88 | * @location: opaque representation of platform connector location | 89 | * @location: opaque representation of platform connector location |
89 | * @status_lock: synchronize port_event() vs usb_port_{suspend|resume} | 90 | * @status_lock: synchronize port_event() vs usb_port_{suspend|resume} |
@@ -95,6 +96,7 @@ struct usb_port { | |||
95 | struct device dev; | 96 | struct device dev; |
96 | struct usb_dev_state *port_owner; | 97 | struct usb_dev_state *port_owner; |
97 | struct usb_port *peer; | 98 | struct usb_port *peer; |
99 | struct dev_pm_qos_request *req; | ||
98 | enum usb_port_connect_type connect_type; | 100 | enum usb_port_connect_type connect_type; |
99 | usb_port_location_t location; | 101 | usb_port_location_t location; |
100 | struct mutex status_lock; | 102 | struct mutex status_lock; |
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 9347ade7d5fe..fe1b6d0967e3 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c | |||
@@ -68,6 +68,7 @@ static void usb_port_device_release(struct device *dev) | |||
68 | { | 68 | { |
69 | struct usb_port *port_dev = to_usb_port(dev); | 69 | struct usb_port *port_dev = to_usb_port(dev); |
70 | 70 | ||
71 | kfree(port_dev->req); | ||
71 | kfree(port_dev); | 72 | kfree(port_dev); |
72 | } | 73 | } |
73 | 74 | ||
@@ -400,9 +401,13 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) | |||
400 | int retval; | 401 | int retval; |
401 | 402 | ||
402 | port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL); | 403 | port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL); |
403 | if (!port_dev) { | 404 | if (!port_dev) |
404 | retval = -ENOMEM; | 405 | return -ENOMEM; |
405 | goto exit; | 406 | |
407 | port_dev->req = kzalloc(sizeof(*(port_dev->req)), GFP_KERNEL); | ||
408 | if (!port_dev->req) { | ||
409 | kfree(port_dev); | ||
410 | return -ENOMEM; | ||
406 | } | 411 | } |
407 | 412 | ||
408 | hub->ports[port1 - 1] = port_dev; | 413 | hub->ports[port1 - 1] = port_dev; |
@@ -418,31 +423,53 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) | |||
418 | port1); | 423 | port1); |
419 | mutex_init(&port_dev->status_lock); | 424 | mutex_init(&port_dev->status_lock); |
420 | retval = device_register(&port_dev->dev); | 425 | retval = device_register(&port_dev->dev); |
421 | if (retval) | 426 | if (retval) { |
422 | goto error_register; | 427 | put_device(&port_dev->dev); |
428 | return retval; | ||
429 | } | ||
430 | |||
431 | /* Set default policy of port-poweroff disabled. */ | ||
432 | retval = dev_pm_qos_add_request(&port_dev->dev, port_dev->req, | ||
433 | DEV_PM_QOS_FLAGS, PM_QOS_FLAG_NO_POWER_OFF); | ||
434 | if (retval < 0) { | ||
435 | device_unregister(&port_dev->dev); | ||
436 | return retval; | ||
437 | } | ||
423 | 438 | ||
424 | find_and_link_peer(hub, port1); | 439 | find_and_link_peer(hub, port1); |
425 | 440 | ||
441 | /* | ||
442 | * Enable runtime pm and hold a refernce that hub_configure() | ||
443 | * will drop once the PM_QOS_NO_POWER_OFF flag state has been set | ||
444 | * and the hub has been fully registered (hdev->maxchild set). | ||
445 | */ | ||
426 | pm_runtime_set_active(&port_dev->dev); | 446 | pm_runtime_set_active(&port_dev->dev); |
447 | pm_runtime_get_noresume(&port_dev->dev); | ||
448 | pm_runtime_enable(&port_dev->dev); | ||
449 | device_enable_async_suspend(&port_dev->dev); | ||
427 | 450 | ||
428 | /* | 451 | /* |
429 | * Do not enable port runtime pm if the hub does not support | 452 | * Keep hidden the ability to enable port-poweroff if the hub |
430 | * power switching. Also, userspace must have final say of | 453 | * does not support power switching. |
431 | * whether a port is permitted to power-off. Do not enable | ||
432 | * runtime pm if we fail to expose pm_qos_no_power_off. | ||
433 | */ | 454 | */ |
434 | if (hub_is_port_power_switchable(hub) | 455 | if (!hub_is_port_power_switchable(hub)) |
435 | && dev_pm_qos_expose_flags(&port_dev->dev, | 456 | return 0; |
436 | PM_QOS_FLAG_NO_POWER_OFF) == 0) | ||
437 | pm_runtime_enable(&port_dev->dev); | ||
438 | 457 | ||
439 | device_enable_async_suspend(&port_dev->dev); | 458 | /* Attempt to let userspace take over the policy. */ |
440 | return 0; | 459 | retval = dev_pm_qos_expose_flags(&port_dev->dev, |
460 | PM_QOS_FLAG_NO_POWER_OFF); | ||
461 | if (retval < 0) { | ||
462 | dev_warn(&port_dev->dev, "failed to expose pm_qos_no_poweroff\n"); | ||
463 | return 0; | ||
464 | } | ||
441 | 465 | ||
442 | error_register: | 466 | /* Userspace owns the policy, drop the kernel 'no_poweroff' request. */ |
443 | put_device(&port_dev->dev); | 467 | retval = dev_pm_qos_remove_request(port_dev->req); |
444 | exit: | 468 | if (retval >= 0) { |
445 | return retval; | 469 | kfree(port_dev->req); |
470 | port_dev->req = NULL; | ||
471 | } | ||
472 | return 0; | ||
446 | } | 473 | } |
447 | 474 | ||
448 | void usb_hub_remove_port_device(struct usb_hub *hub, int port1) | 475 | void usb_hub_remove_port_device(struct usb_hub *hub, int port1) |