add toggle for disabling newly added USB devices
[GitHub/exynos8895/android_kernel_samsung_universal8895.git] / drivers / usb / core / hub.c
index 9e62c93af96eb0cbeb2ef1c7dc0df8e10ee57bcd..4b2f5cc9e0ea8b704f035c74341fdff81d9b218a 100644 (file)
 
 #include "hub.h"
 #include "otg_whitelist.h"
+#if defined(CONFIG_USB_OTG_WHITELIST_FOR_MDM)
+#include "otg_whitelist_for_mdm.h"
+#endif
 
 #define USB_VENDOR_GENESYS_LOGIC               0x05e3
 #define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND       0x01
 
+extern int deny_new_usb;
+
 /* Protect struct usb_device->state and ->children members
  * Note: Both are also protected by ->dev.sem, except that ->state can
  * change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */
@@ -358,7 +363,8 @@ static void usb_set_lpm_parameters(struct usb_device *udev)
 }
 
 /* USB 2.0 spec Section 11.24.4.5 */
-static int get_hub_descriptor(struct usb_device *hdev, void *data)
+static int get_hub_descriptor(struct usb_device *hdev,
+               struct usb_hub_descriptor *desc)
 {
        int i, ret, size;
        unsigned dtype;
@@ -374,10 +380,18 @@ static int get_hub_descriptor(struct usb_device *hdev, void *data)
        for (i = 0; i < 3; i++) {
                ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
                        USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB,
-                       dtype << 8, 0, data, size,
+                       dtype << 8, 0, desc, size,
                        USB_CTRL_GET_TIMEOUT);
-               if (ret >= (USB_DT_HUB_NONVAR_SIZE + 2))
+               if (hub_is_superspeed(hdev)) {
+                       if (ret == size)
+                               return ret;
+               } else if (ret >= USB_DT_HUB_NONVAR_SIZE + 2) {
+                       /* Make sure we have the DeviceRemovable field. */
+                       size = USB_DT_HUB_NONVAR_SIZE + desc->bNbrPorts / 8 + 1;
+                       if (ret < size)
+                               return -EMSGSIZE;
                        return ret;
+               }
        }
        return -EINVAL;
 }
@@ -890,7 +904,11 @@ static int hub_set_port_link_state(struct usb_hub *hub, int port1,
  */
 static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
 {
+#ifdef CONFIG_USB_DEBUG_DETAILED_LOG
+       dev_info(&hub->ports[port1 - 1]->dev, "logical disconnect\n");
+#else
        dev_dbg(&hub->ports[port1 - 1]->dev, "logical disconnect\n");
+#endif
        hub_port_disable(hub, port1, 1);
 
        /* FIXME let caller ask to power down the port:
@@ -1048,6 +1066,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
 
                portstatus = portchange = 0;
                status = hub_port_status(hub, port1, &portstatus, &portchange);
+               if (status)
+                       goto abort;
+
                if (udev || (portstatus & USB_PORT_STAT_CONNECTION))
                        dev_dbg(&port_dev->dev, "status %04x change %04x\n",
                                        portstatus, portchange);
@@ -1180,7 +1201,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
 
        /* Scan all ports that need attention */
        kick_hub_wq(hub);
-
+ abort:
        if (type == HUB_INIT2 || type == HUB_INIT3) {
                /* Allow autosuspend if it was suppressed */
  disconnected:
@@ -1292,7 +1313,7 @@ static int hub_configure(struct usb_hub *hub,
        }
        mutex_init(&hub->status_mutex);
 
-       hub->descriptor = kmalloc(sizeof(*hub->descriptor), GFP_KERNEL);
+       hub->descriptor = kzalloc(sizeof(*hub->descriptor), GFP_KERNEL);
        if (!hub->descriptor) {
                ret = -ENOMEM;
                goto fail;
@@ -1300,13 +1321,19 @@ static int hub_configure(struct usb_hub *hub,
 
        /* Request the entire hub descriptor.
         * hub->descriptor can handle USB_MAXCHILDREN ports,
-        * but the hub can/will return fewer bytes here.
+        * but a (non-SS) hub can/will return fewer bytes here.
         */
        ret = get_hub_descriptor(hdev, hub->descriptor);
        if (ret < 0) {
                message = "can't read hub descriptor";
                goto fail;
-       } else if (hub->descriptor->bNbrPorts > USB_MAXCHILDREN) {
+       }
+
+       maxchild = USB_MAXCHILDREN;
+       if (hub_is_superspeed(hdev))
+               maxchild = min_t(unsigned, maxchild, USB_SS_MAXPORTS);
+
+       if (hub->descriptor->bNbrPorts > maxchild) {
                message = "hub has too many ports!";
                ret = -ENODEV;
                goto fail;
@@ -2068,6 +2095,12 @@ void usb_disconnect(struct usb_device **pdev)
        dev_info(&udev->dev, "USB disconnect, device number %d\n",
                        udev->devnum);
 
+       /*
+        * Ensure that the pm runtime code knows that the USB device
+        * is in the process of being disconnected.
+        */
+       pm_runtime_barrier(&udev->dev);
+
        usb_lock_device(udev);
 
        hub_disconnect_children(udev);
@@ -2275,7 +2308,19 @@ static int usb_enumerate_device(struct usb_device *udev)
                }
                return -ENOTSUPP;
        }
-
+#if defined(CONFIG_USB_OTG_WHITELIST_FOR_MDM)
+       if (IS_ENABLED(CONFIG_USB_OTG_WHITELIST_FOR_MDM) &&
+               /*hcd->tpl_support &&*/
+               !is_targeted_for_samsung_mdm(udev)) {
+               if (IS_ENABLED(CONFIG_USB_OTG) && (udev->bus->b_hnp_enable
+                       || udev->bus->is_b_host)) {
+                       err = usb_port_suspend(udev, PMSG_AUTO_SUSPEND);
+                       if (err < 0)
+                               dev_dbg(&udev->dev, "HNP fail, %d\n", err);
+               }
+               return -ENOTSUPP;
+       }
+#endif
        usb_detect_interface_quirks(udev);
 
        return 0;
@@ -2602,8 +2647,15 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
                if (ret < 0)
                        return ret;
 
-               /* The port state is unknown until the reset completes. */
-               if (!(portstatus & USB_PORT_STAT_RESET))
+               /*
+                * The port state is unknown until the reset completes.
+                *
+                * On top of that, some chips may require additional time
+                * to re-establish a connection after the reset is complete,
+                * so also wait for the connection to be re-established.
+                */
+               if (!(portstatus & USB_PORT_STAT_RESET) &&
+                   (portstatus & USB_PORT_STAT_CONNECTION))
                        break;
 
                /* switch to the long delay after two short delay failures */
@@ -2625,13 +2677,16 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
        if (!(portstatus & USB_PORT_STAT_CONNECTION))
                return -ENOTCONN;
 
-       /* bomb out completely if the connection bounced.  A USB 3.0
-        * connection may bounce if multiple warm resets were issued,
+       /* Retry if connect change is set but status is still connected.
+        * A USB 3.0 connection may bounce if multiple warm resets were issued,
         * but the device may have successfully re-connected. Ignore it.
         */
        if (!hub_is_superspeed(hub->hdev) &&
-                       (portchange & USB_PORT_STAT_C_CONNECTION))
-               return -ENOTCONN;
+           (portchange & USB_PORT_STAT_C_CONNECTION)) {
+               usb_clear_port_feature(hub->hdev, port1,
+                                      USB_PORT_FEAT_C_CONNECTION);
+               return -EAGAIN;
+       }
 
        if (!(portstatus & USB_PORT_STAT_ENABLE))
                return -EBUSY;
@@ -4535,7 +4590,8 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
                if (!retval) {
                        udev->lpm_capable = usb_device_supports_lpm(udev);
                        usb_set_lpm_parameters(udev);
-               }
+               } else
+                       goto fail;
        }
 
        retval = 0;
@@ -4630,13 +4686,19 @@ hub_power_remaining(struct usb_hub *hub)
 static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
                u16 portchange)
 {
-       int status, i;
+       int status = -ENODEV;
+       int i;
        unsigned unit_load;
        struct usb_device *hdev = hub->hdev;
        struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
        struct usb_port *port_dev = hub->ports[port1 - 1];
        struct usb_device *udev = port_dev->child;
        static int unreliable_port = -1;
+#ifdef CONFIG_USB_DEBUG_DETAILED_LOG
+       dev_info (&port_dev->dev,
+               "port %d, status %04x, change %04x, %s\n",
+               port1, portstatus, portchange, portspeed(hub, portstatus));
+#endif
 
        /* Disconnect any existing devices under this port */
        if (udev) {
@@ -4686,6 +4748,12 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
                        goto done;
                return;
        }
+
+       if (deny_new_usb) {
+               dev_err(&port_dev->dev, "denied insert of USB device on port %d\n", port1);
+               goto done;
+       }
+
        if (hub_is_superspeed(hub->hdev))
                unit_load = 150;
        else
@@ -4729,7 +4797,7 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
                        goto loop;
 
                if (udev->quirks & USB_QUIRK_DELAY_INIT)
-                       msleep(1000);
+                       msleep(2000);
 
                /* consecutive bus-powered hubs aren't reliable; they can
                 * violate the voltage drop budget.  if the new child has
@@ -4823,6 +4891,15 @@ loop:
                usb_put_dev(udev);
                if ((status == -ENOTCONN) || (status == -ENOTSUPP))
                        break;
+
+               /* When halfway through our retry count, power-cycle the port */
+               if (i == (SET_CONFIG_TRIES / 2) - 1) {
+                       dev_info(&port_dev->dev, "attempt power cycle\n");
+                       usb_hub_set_port_power(hdev, hub, port1, false);
+                       msleep(2 * hub_power_on_good_delay(hub));
+                       usb_hub_set_port_power(hdev, hub, port1, true);
+                       msleep(hub_power_on_good_delay(hub));
+               }
        }
        if (hub->hdev->parent ||
                        !hcd->driver->port_handed_over ||
@@ -4834,9 +4911,10 @@ loop:
 
 done:
        hub_port_disable(hub, port1, 1);
-       if (hcd->driver->relinquish_port && !hub->hdev->parent)
-               hcd->driver->relinquish_port(hcd, port1);
-
+       if (hcd->driver->relinquish_port && !hub->hdev->parent) {
+               if (status != -ENOTCONN && status != -ENODEV)
+                       hcd->driver->relinquish_port(hcd, port1);
+       }
 }
 
 /* Handle physical or logical connection change events.
@@ -5011,6 +5089,7 @@ static void hub_event(struct work_struct *work)
        struct usb_interface *intf;
        struct usb_hub *hub;
        struct device *hub_dev;
+       struct usb_hcd *hcd;
        u16 hubstatus;
        u16 hubchange;
        int i, ret;
@@ -5018,6 +5097,7 @@ static void hub_event(struct work_struct *work)
        hub = container_of(work, struct usb_hub, events);
        hdev = hub->hdev;
        hub_dev = hub->intfdev;
+       hcd = bus_to_hcd(hdev->bus);
        intf = to_usb_interface(hub_dev);
 
        dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
@@ -5029,6 +5109,7 @@ static void hub_event(struct work_struct *work)
        /* Lock the device, then check to see if we were
         * disconnected while waiting for the lock to succeed. */
        usb_lock_device(hdev);
+       hcd->is_in_hub_event = true;
        if (unlikely(hub->disconnected))
                goto out_hdev_lock;
 
@@ -5121,6 +5202,7 @@ out_autopm:
        /* Balance the usb_autopm_get_interface() above */
        usb_autopm_put_interface_no_suspend(intf);
 out_hdev_lock:
+       hcd->is_in_hub_event = false;
        usb_unlock_device(hdev);
 
        /* Balance the stuff in kick_hub_wq() and allow autosuspend */