xhci: disable U3 suspended ports in S4 hibernate poweroff_late stage
authorMathias Nyman <mathias.nyman@linux.intel.com>
Wed, 30 Nov 2022 09:19:42 +0000 (11:19 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 30 Nov 2022 11:10:48 +0000 (12:10 +0100)
Disable U3 suspended ports in hibernate S4 poweroff_late for systems
with XHCI_RESET_TO_DEFAULT quirk, if wakeup is not enabled.

This reduces the number of self-powered usb devices from surviving in
U3 suspended state into next reboot.

Bootloader/firmware on these systems can't handle usb ports in U3, and
will timeout, causing extra delay during reboot/restore from S4.

Add pci_poweroff_late() callback to struct usb_hcd to get this done at
the correct stage in hibernate.

Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://lore.kernel.org/r/20221130091944.2171610-5-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/core/hcd-pci.c
drivers/usb/host/xhci-pci.c
include/linux/usb/hcd.h

index 9b77f49..ab2f373 100644 (file)
@@ -558,6 +558,17 @@ static int hcd_pci_suspend_noirq(struct device *dev)
        return retval;
 }
 
        return retval;
 }
 
+static int hcd_pci_poweroff_late(struct device *dev)
+{
+       struct pci_dev          *pci_dev = to_pci_dev(dev);
+       struct usb_hcd          *hcd = pci_get_drvdata(pci_dev);
+
+       if (hcd->driver->pci_poweroff_late && !HCD_DEAD(hcd))
+               return hcd->driver->pci_poweroff_late(hcd, device_may_wakeup(dev));
+
+       return 0;
+}
+
 static int hcd_pci_resume_noirq(struct device *dev)
 {
        powermac_set_asic(to_pci_dev(dev), 1);
 static int hcd_pci_resume_noirq(struct device *dev)
 {
        powermac_set_asic(to_pci_dev(dev), 1);
@@ -578,6 +589,7 @@ static int hcd_pci_restore(struct device *dev)
 
 #define hcd_pci_suspend                NULL
 #define hcd_pci_suspend_noirq  NULL
 
 #define hcd_pci_suspend                NULL
 #define hcd_pci_suspend_noirq  NULL
+#define hcd_pci_poweroff_late  NULL
 #define hcd_pci_resume_noirq   NULL
 #define hcd_pci_resume         NULL
 #define hcd_pci_restore                NULL
 #define hcd_pci_resume_noirq   NULL
 #define hcd_pci_resume         NULL
 #define hcd_pci_restore                NULL
@@ -615,6 +627,7 @@ const struct dev_pm_ops usb_hcd_pci_pm_ops = {
        .thaw_noirq     = NULL,
        .thaw           = hcd_pci_resume,
        .poweroff       = hcd_pci_suspend,
        .thaw_noirq     = NULL,
        .thaw           = hcd_pci_resume,
        .poweroff       = hcd_pci_suspend,
+       .poweroff_late  = hcd_pci_poweroff_late,
        .poweroff_noirq = hcd_pci_suspend_noirq,
        .restore_noirq  = hcd_pci_resume_noirq,
        .restore        = hcd_pci_restore,
        .poweroff_noirq = hcd_pci_suspend_noirq,
        .restore_noirq  = hcd_pci_resume_noirq,
        .restore        = hcd_pci_restore,
index 1fb773e..79d679b 100644 (file)
@@ -622,6 +622,57 @@ static int xhci_pci_resume(struct usb_hcd *hcd, bool hibernated)
        return retval;
 }
 
        return retval;
 }
 
+static int xhci_pci_poweroff_late(struct usb_hcd *hcd, bool do_wakeup)
+{
+       struct xhci_hcd         *xhci = hcd_to_xhci(hcd);
+       struct xhci_port        *port;
+       struct usb_device       *udev;
+       unsigned int            slot_id;
+       u32                     portsc;
+       int                     i;
+
+       /*
+        * Systems with XHCI_RESET_TO_DEFAULT quirk have boot firmware that
+        * cause significant boot delay if usb ports are in suspended U3 state
+        * during boot. Some USB devices survive in U3 state over S4 hibernate
+        *
+        * Disable ports that are in U3 if remote wake is not enabled for either
+        * host controller or connected device
+        */
+
+       if (!(xhci->quirks & XHCI_RESET_TO_DEFAULT))
+               return 0;
+
+       for (i = 0; i < HCS_MAX_PORTS(xhci->hcs_params1); i++) {
+               port = &xhci->hw_ports[i];
+               portsc = readl(port->addr);
+
+               if ((portsc & PORT_PLS_MASK) != XDEV_U3)
+                       continue;
+
+               slot_id = xhci_find_slot_id_by_port(port->rhub->hcd, xhci,
+                                                   port->hcd_portnum + 1);
+               if (!slot_id || !xhci->devs[slot_id]) {
+                       xhci_err(xhci, "No dev for slot_id %d for port %d-%d in U3\n",
+                                slot_id, port->rhub->hcd->self.busnum, port->hcd_portnum + 1);
+                       continue;
+               }
+
+               udev = xhci->devs[slot_id]->udev;
+
+               /* if wakeup is enabled then don't disable the port */
+               if (udev->do_remote_wakeup && do_wakeup)
+                       continue;
+
+               xhci_dbg(xhci, "port %d-%d in U3 without wakeup, disable it\n",
+                        port->rhub->hcd->self.busnum, port->hcd_portnum + 1);
+               portsc = xhci_port_state_to_neutral(portsc);
+               writel(portsc | PORT_PE, port->addr);
+       }
+
+       return 0;
+}
+
 static void xhci_pci_shutdown(struct usb_hcd *hcd)
 {
        struct xhci_hcd         *xhci = hcd_to_xhci(hcd);
 static void xhci_pci_shutdown(struct usb_hcd *hcd)
 {
        struct xhci_hcd         *xhci = hcd_to_xhci(hcd);
@@ -689,6 +740,7 @@ static int __init xhci_pci_init(void)
 #ifdef CONFIG_PM
        xhci_pci_hc_driver.pci_suspend = xhci_pci_suspend;
        xhci_pci_hc_driver.pci_resume = xhci_pci_resume;
 #ifdef CONFIG_PM
        xhci_pci_hc_driver.pci_suspend = xhci_pci_suspend;
        xhci_pci_hc_driver.pci_resume = xhci_pci_resume;
+       xhci_pci_hc_driver.pci_poweroff_late = xhci_pci_poweroff_late;
        xhci_pci_hc_driver.shutdown = xhci_pci_shutdown;
 #endif
        return pci_register_driver(&xhci_pci_driver);
        xhci_pci_hc_driver.shutdown = xhci_pci_shutdown;
 #endif
        return pci_register_driver(&xhci_pci_driver);
index 78cd566..b51c071 100644 (file)
@@ -269,6 +269,9 @@ struct hc_driver {
        /* called after entering D0 (etc), before resuming the hub */
        int     (*pci_resume)(struct usb_hcd *hcd, bool hibernated);
 
        /* called after entering D0 (etc), before resuming the hub */
        int     (*pci_resume)(struct usb_hcd *hcd, bool hibernated);
 
+       /* called just before hibernate final D3 state, allows host to poweroff parts */
+       int     (*pci_poweroff_late)(struct usb_hcd *hcd, bool do_wakeup);
+
        /* cleanly make HCD stop writing memory and doing I/O */
        void    (*stop) (struct usb_hcd *hcd);
 
        /* cleanly make HCD stop writing memory and doing I/O */
        void    (*stop) (struct usb_hcd *hcd);