add toggle for disabling newly added USB devices
[GitHub/LineageOS/android_kernel_motorola_exynos9610.git] / drivers / usb / core / hub.c
index b5c73361382325127e5412c8b04c748749849954..1f3aa1b96e3745a43d0a293437530c2cb59c267e 100644 (file)
@@ -38,6 +38,8 @@
 #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. */
@@ -650,12 +652,17 @@ void usb_wakeup_notification(struct usb_device *hdev,
                unsigned int portnum)
 {
        struct usb_hub *hub;
+       struct usb_port *port_dev;
 
        if (!hdev)
                return;
 
        hub = usb_hub_to_struct_hub(hdev);
        if (hub) {
+               port_dev = hub->ports[portnum - 1];
+               if (port_dev && port_dev->child)
+                       pm_wakeup_event(&port_dev->child->dev, 0);
+
                set_bit(portnum, hub->wakeup_bits);
                kick_hub_wq(hub);
        }
@@ -1105,6 +1112,16 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
                                                   USB_PORT_FEAT_ENABLE);
                }
 
+               /*
+                * Add debounce if USB3 link is in polling/link training state.
+                * Link will automatically transition to Enabled state after
+                * link training completes.
+                */
+               if (hub_is_superspeed(hdev) &&
+                   ((portstatus & USB_PORT_STAT_LINK_STATE) ==
+                                               USB_SS_PORT_LS_POLLING))
+                       need_debounce_delay = true;
+
                /* Clear status-change flags; we'll debounce later */
                if (portchange & USB_PORT_STAT_C_CONNECTION) {
                        need_debounce_delay = true;
@@ -1136,10 +1153,14 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
 
                if (!udev || udev->state == USB_STATE_NOTATTACHED) {
                        /* Tell hub_wq to disconnect the device or
-                        * check for a new connection
+                        * check for a new connection or over current condition.
+                        * Based on USB2.0 Spec Section 11.12.5,
+                        * C_PORT_OVER_CURRENT could be set while
+                        * PORT_OVER_CURRENT is not. So check for any of them.
                         */
                        if (udev || (portstatus & USB_PORT_STAT_CONNECTION) ||
-                           (portstatus & USB_PORT_STAT_OVERCURRENT))
+                           (portstatus & USB_PORT_STAT_OVERCURRENT) ||
+                           (portchange & USB_PORT_STAT_C_OVERCURRENT))
                                set_bit(port1, hub->change_bits);
 
                } else if (portstatus & USB_PORT_STAT_ENABLE) {
@@ -1685,6 +1706,7 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
        struct usb_host_interface *desc;
        struct usb_device *hdev;
        struct usb_hub *hub;
+       struct usb_hcd *hcd;
 
        desc = intf->cur_altsetting;
        hdev = interface_to_usbdev(intf);
@@ -1726,7 +1748,13 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
         *   usbcore.autosuspend = -1 then keep autosuspend disabled.
         */
 #ifdef CONFIG_PM
-       if (hdev->dev.power.autosuspend_delay >= 0)
+       /*
+        * bus suspend is executed before USB3.0 hub and devices are detected,
+        * which causes USB3.0 hub and devices aren't recognized when l2 sleep is enabled.
+        * Restoring suspend delay will resolve USB3.0 hub detect issue.
+        */
+       hcd = bus_to_hcd(hdev->bus);
+       if ((hdev->dev.power.autosuspend_delay >= 0) && !hcd->driver->usb_l2_check(hcd))
                pm_runtime_set_autosuspend_delay(&hdev->dev, 0);
 #endif
 
@@ -2222,7 +2250,7 @@ static int usb_enumerate_device_otg(struct usb_device *udev)
                /* descriptor may appear anywhere in config */
                err = __usb_get_extra_descriptor(udev->rawdescriptors[0],
                                le16_to_cpu(udev->config[0].desc.wTotalLength),
-                               USB_DT_OTG, (void **) &desc);
+                               USB_DT_OTG, (void **) &desc, sizeof(*desc));
                if (err || !(desc->bmAttributes & USB_OTG_HNP))
                        return 0;
 
@@ -2710,13 +2738,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;
@@ -2803,7 +2834,9 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
                                        USB_PORT_FEAT_C_BH_PORT_RESET);
                        usb_clear_port_feature(hub->hdev, port1,
                                        USB_PORT_FEAT_C_PORT_LINK_STATE);
-                       usb_clear_port_feature(hub->hdev, port1,
+
+                       if (udev)
+                               usb_clear_port_feature(hub->hdev, port1,
                                        USB_PORT_FEAT_C_CONNECTION);
 
                        /*
@@ -3349,6 +3382,10 @@ 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)) {
+                       status = -ENODEV;
+                       break;
+               }
                msleep(20);
                delay_ms += 20;
                status = hub_port_status(hub, *port1, portstatus, portchange);
@@ -3412,8 +3449,11 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
 
        /* Skip the initial Clear-Suspend step for a remote wakeup */
        status = hub_port_status(hub, port1, &portstatus, &portchange);
-       if (status == 0 && !port_is_suspended(hub, portstatus))
+       if (status == 0 && !port_is_suspended(hub, portstatus)) {
+               if (portchange & USB_PORT_STAT_C_SUSPEND)
+                       pm_wakeup_event(&udev->dev, 0);
                goto SuspendCleared;
+       }
 
        /* see 7.1.7.7; affects power usage, but not budgeting */
        if (hub_is_superspeed(hub->hdev))
@@ -4508,7 +4548,9 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
                                 * reset. But only on the first attempt,
                                 * lest we get into a time out/reset loop
                                 */
-                               if (r == 0  || (r == -ETIMEDOUT && retries == 0))
+                               if (r == 0 || (r == -ETIMEDOUT &&
+                                               retries == 0 &&
+                                               udev->speed > USB_SPEED_FULL))
                                        break;
                        }
                        udev->descriptor.bMaxPacketSize0 =
@@ -4795,6 +4837,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
@@ -4932,6 +4980,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 ||
@@ -5121,6 +5178,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;
@@ -5128,6 +5186,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",
@@ -5139,6 +5198,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;
 
@@ -5231,6 +5291,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 */