aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/core/hub.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r--drivers/usb/core/hub.c108
1 files changed, 49 insertions, 59 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 68e9121c1878..2633acde7ac1 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -23,6 +23,7 @@
#include <linux/usb.h>
#include <linux/usbdevice_fs.h>
#include <linux/usb/hcd.h>
+#include <linux/usb/onboard_hub.h>
#include <linux/usb/otg.h>
#include <linux/usb/quirks.h>
#include <linux/workqueue.h>
@@ -613,7 +614,7 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type,
return ret;
}
-static int hub_port_status(struct usb_hub *hub, int port1,
+int usb_hub_port_status(struct usb_hub *hub, int port1,
u16 *status, u16 *change)
{
return hub_ext_port_status(hub, port1, HUB_PORT_STATUS,
@@ -1126,7 +1127,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
u16 portstatus, portchange;
portstatus = portchange = 0;
- status = hub_port_status(hub, port1, &portstatus, &portchange);
+ status = usb_hub_port_status(hub, port1, &portstatus, &portchange);
if (status)
goto abort;
@@ -1752,6 +1753,8 @@ static void hub_disconnect(struct usb_interface *intf)
if (hub->quirk_disable_autosuspend)
usb_autopm_put_interface(intf);
+ onboard_hub_destroy_pdevs(&hub->onboard_hub_devs);
+
kref_put(&hub->kref, hub_release);
}
@@ -1869,6 +1872,7 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
INIT_DELAYED_WORK(&hub->leds, led_work);
INIT_DELAYED_WORK(&hub->init_work, NULL);
INIT_WORK(&hub->events, hub_event);
+ INIT_LIST_HEAD(&hub->onboard_hub_devs);
spin_lock_init(&hub->irq_urb_lock);
timer_setup(&hub->irq_urb_retry, hub_retry_irq_urb, 0);
usb_get_intf(intf);
@@ -1889,8 +1893,11 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
usb_autopm_get_interface_no_resume(intf);
}
- if (hub_configure(hub, &desc->endpoint[0].desc) >= 0)
+ if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) {
+ onboard_hub_create_pdevs(hdev, &hub->onboard_hub_devs);
+
return 0;
+ }
hub_disconnect(intf);
return -ENODEV;
@@ -2855,7 +2862,7 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
&portstatus, &portchange,
&ext_portstatus);
else
- ret = hub_port_status(hub, port1, &portstatus,
+ ret = usb_hub_port_status(hub, port1, &portstatus,
&portchange);
if (ret < 0)
return ret;
@@ -2956,7 +2963,8 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
* If the caller hasn't explicitly requested a warm reset,
* double check and see if one is needed.
*/
- if (hub_port_status(hub, port1, &portstatus, &portchange) == 0)
+ if (usb_hub_port_status(hub, port1, &portstatus,
+ &portchange) == 0)
if (hub_port_warm_reset_required(hub, port1,
portstatus))
warm = true;
@@ -3008,7 +3016,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
* If a USB 3.0 device migrates from reset to an error
* state, re-issue the warm reset.
*/
- if (hub_port_status(hub, port1,
+ if (usb_hub_port_status(hub, port1,
&portstatus, &portchange) < 0)
goto done;
@@ -3074,7 +3082,7 @@ done:
}
/* Check if a port is power on */
-static int port_is_power_on(struct usb_hub *hub, unsigned portstatus)
+int usb_port_is_power_on(struct usb_hub *hub, unsigned int portstatus)
{
int ret = 0;
@@ -3140,13 +3148,13 @@ static int check_port_resume_type(struct usb_device *udev,
}
/* Is the device still present? */
else if (status || port_is_suspended(hub, portstatus) ||
- !port_is_power_on(hub, portstatus)) {
+ !usb_port_is_power_on(hub, portstatus)) {
if (status >= 0)
status = -ENODEV;
} else if (!(portstatus & USB_PORT_STAT_CONNECTION)) {
if (retries--) {
usleep_range(200, 300);
- status = hub_port_status(hub, port1, &portstatus,
+ status = usb_hub_port_status(hub, port1, &portstatus,
&portchange);
goto retry;
}
@@ -3409,7 +3417,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
u16 portstatus, portchange;
portstatus = portchange = 0;
- ret = hub_port_status(hub, port1, &portstatus,
+ ret = usb_hub_port_status(hub, port1, &portstatus,
&portchange);
dev_dbg(&port_dev->dev,
@@ -3587,13 +3595,13 @@ static int wait_for_connected(struct usb_device *udev,
while (delay_ms < 2000) {
if (status || *portstatus & USB_PORT_STAT_CONNECTION)
break;
- if (!port_is_power_on(hub, *portstatus)) {
+ if (!usb_port_is_power_on(hub, *portstatus)) {
status = -ENODEV;
break;
}
msleep(20);
delay_ms += 20;
- status = hub_port_status(hub, port1, portstatus, portchange);
+ status = usb_hub_port_status(hub, port1, portstatus, portchange);
}
dev_dbg(&udev->dev, "Waited %dms for CONNECT\n", delay_ms);
return status;
@@ -3653,7 +3661,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
usb_lock_port(port_dev);
/* Skip the initial Clear-Suspend step for a remote wakeup */
- status = hub_port_status(hub, port1, &portstatus, &portchange);
+ status = usb_hub_port_status(hub, port1, &portstatus, &portchange);
if (status == 0 && !port_is_suspended(hub, portstatus)) {
if (portchange & USB_PORT_STAT_C_SUSPEND)
pm_wakeup_event(&udev->dev, 0);
@@ -3678,7 +3686,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
* stop resume signaling. Then finish the resume
* sequence.
*/
- status = hub_port_status(hub, port1, &portstatus, &portchange);
+ status = usb_hub_port_status(hub, port1, &portstatus, &portchange);
}
SuspendCleared:
@@ -3791,7 +3799,7 @@ static int check_ports_changed(struct usb_hub *hub)
u16 portstatus, portchange;
int status;
- status = hub_port_status(hub, port1, &portstatus, &portchange);
+ status = usb_hub_port_status(hub, port1, &portstatus, &portchange);
if (!status && portchange)
return 1;
}
@@ -3946,7 +3954,7 @@ static const char * const usb3_lpm_names[] = {
* This function will fail if the SEL or PEL values for udev are greater than
* the maximum allowed values for the link state to be enabled.
*/
-static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state)
+static int usb_req_set_sel(struct usb_device *udev)
{
struct usb_set_sel_req *sel_values;
unsigned long long u1_sel;
@@ -3955,7 +3963,7 @@ static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state)
unsigned long long u2_pel;
int ret;
- if (udev->state != USB_STATE_CONFIGURED)
+ if (!udev->parent || udev->speed < USB_SPEED_SUPER || !udev->lpm_capable)
return 0;
/* Convert SEL and PEL stored in ns to us */
@@ -3972,35 +3980,15 @@ static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state)
* latency for the link state, and could start a device-initiated
* U1/U2 when the exit latencies are too high.
*/
- if ((state == USB3_LPM_U1 &&
- (u1_sel > USB3_LPM_MAX_U1_SEL_PEL ||
- u1_pel > USB3_LPM_MAX_U1_SEL_PEL)) ||
- (state == USB3_LPM_U2 &&
- (u2_sel > USB3_LPM_MAX_U2_SEL_PEL ||
- u2_pel > USB3_LPM_MAX_U2_SEL_PEL))) {
- dev_dbg(&udev->dev, "Device-initiated %s disabled due to long SEL %llu us or PEL %llu us\n",
- usb3_lpm_names[state], u1_sel, u1_pel);
+ if (u1_sel > USB3_LPM_MAX_U1_SEL_PEL ||
+ u1_pel > USB3_LPM_MAX_U1_SEL_PEL ||
+ u2_sel > USB3_LPM_MAX_U2_SEL_PEL ||
+ u2_pel > USB3_LPM_MAX_U2_SEL_PEL) {
+ dev_dbg(&udev->dev, "Device-initiated U1/U2 disabled due to long SEL or PEL\n");
return -EINVAL;
}
/*
- * If we're enabling device-initiated LPM for one link state,
- * but the other link state has a too high SEL or PEL value,
- * just set those values to the max in the Set SEL request.
- */
- if (u1_sel > USB3_LPM_MAX_U1_SEL_PEL)
- u1_sel = USB3_LPM_MAX_U1_SEL_PEL;
-
- if (u1_pel > USB3_LPM_MAX_U1_SEL_PEL)
- u1_pel = USB3_LPM_MAX_U1_SEL_PEL;
-
- if (u2_sel > USB3_LPM_MAX_U2_SEL_PEL)
- u2_sel = USB3_LPM_MAX_U2_SEL_PEL;
-
- if (u2_pel > USB3_LPM_MAX_U2_SEL_PEL)
- u2_pel = USB3_LPM_MAX_U2_SEL_PEL;
-
- /*
* usb_enable_lpm() can be called as part of a failed device reset,
* which may be initiated by an error path of a mass storage driver.
* Therefore, use GFP_NOIO.
@@ -4021,6 +4009,10 @@ static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state)
sel_values, sizeof *(sel_values),
USB_CTRL_SET_TIMEOUT);
kfree(sel_values);
+
+ if (ret > 0)
+ udev->lpm_devinit_allow = 1;
+
return ret;
}
@@ -4136,6 +4128,9 @@ static bool usb_device_may_initiate_lpm(struct usb_device *udev,
unsigned int sel; /* us */
int i, j;
+ if (!udev->lpm_devinit_allow)
+ return false;
+
if (state == USB3_LPM_U1)
sel = DIV_ROUND_UP(udev->u1_params.sel, 1000);
else if (state == USB3_LPM_U2)
@@ -4184,7 +4179,7 @@ static bool usb_device_may_initiate_lpm(struct usb_device *udev,
static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
enum usb3_link_state state)
{
- int timeout, ret;
+ int timeout;
__u8 u1_mel = udev->bos->ss_cap->bU1devExitLat;
__le16 u2_mel = udev->bos->ss_cap->bU2DevExitLat;
@@ -4196,17 +4191,6 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
(state == USB3_LPM_U2 && u2_mel == 0))
return;
- /*
- * First, let the device know about the exit latencies
- * associated with the link state we're about to enable.
- */
- ret = usb_req_set_sel(udev, state);
- if (ret < 0) {
- dev_warn(&udev->dev, "Set SEL for device-initiated %s failed.\n",
- usb3_lpm_names[state]);
- return;
- }
-
/* We allow the host controller to set the U1/U2 timeout internally
* first, so that it can change its schedule to account for the
* additional latency to send data to a device in a lower power
@@ -4486,6 +4470,11 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
return 0;
}
+static int usb_req_set_sel(struct usb_device *udev)
+{
+ return 0;
+}
+
#endif /* CONFIG_PM */
/*
@@ -4554,7 +4543,7 @@ int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected)
struct usb_port *port_dev = hub->ports[port1 - 1];
for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) {
- ret = hub_port_status(hub, port1, &portstatus, &portchange);
+ ret = usb_hub_port_status(hub, port1, &portstatus, &portchange);
if (ret < 0)
return ret;
@@ -5011,6 +5000,7 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
udev->lpm_capable = usb_device_supports_lpm(udev);
udev->lpm_disable_count = 1;
usb_set_lpm_parameters(udev);
+ usb_req_set_sel(udev);
}
}
@@ -5240,7 +5230,7 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
* but only if the port isn't owned by someone else.
*/
if (hub_is_port_power_switchable(hub)
- && !port_is_power_on(hub, portstatus)
+ && !usb_port_is_power_on(hub, portstatus)
&& !port_dev->port_owner)
set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
@@ -5557,7 +5547,7 @@ static void port_event(struct usb_hub *hub, int port1)
clear_bit(port1, hub->event_bits);
clear_bit(port1, hub->wakeup_bits);
- if (hub_port_status(hub, port1, &portstatus, &portchange) < 0)
+ if (usb_hub_port_status(hub, port1, &portstatus, &portchange) < 0)
return;
if (portchange & USB_PORT_STAT_C_CONNECTION) {
@@ -5594,7 +5584,7 @@ static void port_event(struct usb_hub *hub, int port1)
USB_PORT_FEAT_C_OVER_CURRENT);
msleep(100); /* Cool down */
hub_power_on(hub, true);
- hub_port_status(hub, port1, &status, &unused);
+ usb_hub_port_status(hub, port1, &status, &unused);
if (status & USB_PORT_STAT_OVERCURRENT)
dev_err(&port_dev->dev, "over-current condition\n");
}
@@ -5638,7 +5628,7 @@ static void port_event(struct usb_hub *hub, int port1)
u16 unused;
msleep(20);
- hub_port_status(hub, port1, &portstatus, &unused);
+ usb_hub_port_status(hub, port1, &portstatus, &unused);
dev_dbg(&port_dev->dev, "Wait for inactive link disconnect detect\n");
continue;
} else if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION)