Merge branch 'pci/hotplug'
authorBjorn Helgaas <bhelgaas@google.com>
Thu, 28 Nov 2019 14:54:31 +0000 (08:54 -0600)
committerBjorn Helgaas <bhelgaas@google.com>
Thu, 28 Nov 2019 14:54:31 +0000 (08:54 -0600)
  - Avoid returning prematurely from sysfs requests to enable or disable a
    PCIe hotplug slot (Lukas Wunner)

  - Don't disable interrupts twice when suspending hotplug ports (Mika
    Westerberg)

  - Fix deadlocks when PCIe ports are hot-removed while suspended (Mika
    Westerberg)

  - Fix boot-time Embedded Controller GPE storm caused by incorrect
    resource assignment after ACPI Bus Check Notification (Mika Westerberg)

* pci/hotplug:
  ACPI / hotplug / PCI: Allocate resources directly under the non-hotplug bridge
  PCI: pciehp: Prevent deadlock on disconnect
  PCI: pciehp: Do not disable interrupt twice on suspend
  PCI: pciehp: Refactor infinite loop in pcie_poll_cmd()
  PCI: pciehp: Avoid returning prematurely from sysfs requests

drivers/pci/hotplug/acpiphp_glue.c
drivers/pci/hotplug/pciehp.h
drivers/pci/hotplug/pciehp_core.c
drivers/pci/hotplug/pciehp_ctrl.c
drivers/pci/hotplug/pciehp_hpc.c

index e4c4663..b386995 100644 (file)
@@ -449,8 +449,15 @@ static void acpiphp_native_scan_bridge(struct pci_dev *bridge)
 
        /* Scan non-hotplug bridges that need to be reconfigured */
        for_each_pci_bridge(dev, bus) {
-               if (!hotplug_is_native(dev))
-                       max = pci_scan_bridge(bus, dev, max, 1);
+               if (hotplug_is_native(dev))
+                       continue;
+
+               max = pci_scan_bridge(bus, dev, max, 1);
+               if (dev->subordinate) {
+                       pcibios_resource_survey_bus(dev->subordinate);
+                       pci_bus_size_bridges(dev->subordinate);
+                       pci_bus_assign_resources(dev->subordinate);
+               }
        }
 }
 
@@ -480,7 +487,6 @@ static void enable_slot(struct acpiphp_slot *slot, bool bridge)
                        if (PCI_SLOT(dev->devfn) == slot->device)
                                acpiphp_native_scan_bridge(dev);
                }
-               pci_assign_unassigned_bridge_resources(bus->self);
        } else {
                LIST_HEAD(add_list);
                int max, pass;
index 654c972..aa61d4c 100644 (file)
@@ -72,6 +72,7 @@ extern int pciehp_poll_time;
  * @reset_lock: prevents access to the Data Link Layer Link Active bit in the
  *     Link Status register and to the Presence Detect State bit in the Slot
  *     Status register during a slot reset which may cause them to flap
+ * @ist_running: flag to keep user request waiting while IRQ thread is running
  * @request_result: result of last user request submitted to the IRQ thread
  * @requester: wait queue to wake up on completion of user request,
  *     used for synchronous slot enable/disable request via sysfs
@@ -101,6 +102,7 @@ struct controller {
 
        struct hotplug_slot hotplug_slot;       /* hotplug core interface */
        struct rw_semaphore reset_lock;
+       unsigned int ist_running;
        int request_result;
        wait_queue_head_t requester;
 };
@@ -172,10 +174,10 @@ void pciehp_set_indicators(struct controller *ctrl, int pwr, int attn);
 
 void pciehp_get_latch_status(struct controller *ctrl, u8 *status);
 int pciehp_query_power_fault(struct controller *ctrl);
-bool pciehp_card_present(struct controller *ctrl);
-bool pciehp_card_present_or_link_active(struct controller *ctrl);
+int pciehp_card_present(struct controller *ctrl);
+int pciehp_card_present_or_link_active(struct controller *ctrl);
 int pciehp_check_link_status(struct controller *ctrl);
-bool pciehp_check_link_active(struct controller *ctrl);
+int pciehp_check_link_active(struct controller *ctrl);
 void pciehp_release_ctrl(struct controller *ctrl);
 
 int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot);
index b3122c1..312cc45 100644 (file)
@@ -139,10 +139,15 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
 {
        struct controller *ctrl = to_ctrl(hotplug_slot);
        struct pci_dev *pdev = ctrl->pcie->port;
+       int ret;
 
        pci_config_pm_runtime_get(pdev);
-       *value = pciehp_card_present_or_link_active(ctrl);
+       ret = pciehp_card_present_or_link_active(ctrl);
        pci_config_pm_runtime_put(pdev);
+       if (ret < 0)
+               return ret;
+
+       *value = ret;
        return 0;
 }
 
@@ -158,13 +163,13 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
  */
 static void pciehp_check_presence(struct controller *ctrl)
 {
-       bool occupied;
+       int occupied;
 
        down_read(&ctrl->reset_lock);
        mutex_lock(&ctrl->state_lock);
 
        occupied = pciehp_card_present_or_link_active(ctrl);
-       if ((occupied && (ctrl->state == OFF_STATE ||
+       if ((occupied > 0 && (ctrl->state == OFF_STATE ||
                          ctrl->state == BLINKINGON_STATE)) ||
            (!occupied && (ctrl->state == ON_STATE ||
                           ctrl->state == BLINKINGOFF_STATE)))
@@ -253,7 +258,7 @@ static bool pme_is_native(struct pcie_device *dev)
        return pcie_ports_native || host->native_pme;
 }
 
-static int pciehp_suspend(struct pcie_device *dev)
+static void pciehp_disable_interrupt(struct pcie_device *dev)
 {
        /*
         * Disable hotplug interrupt so that it does not trigger
@@ -261,7 +266,19 @@ static int pciehp_suspend(struct pcie_device *dev)
         */
        if (pme_is_native(dev))
                pcie_disable_interrupt(get_service_data(dev));
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int pciehp_suspend(struct pcie_device *dev)
+{
+       /*
+        * If the port is already runtime suspended we can keep it that
+        * way.
+        */
+       if (dev_pm_smart_suspend_and_suspended(&dev->port->dev))
+               return 0;
 
+       pciehp_disable_interrupt(dev);
        return 0;
 }
 
@@ -279,6 +296,7 @@ static int pciehp_resume_noirq(struct pcie_device *dev)
 
        return 0;
 }
+#endif
 
 static int pciehp_resume(struct pcie_device *dev)
 {
@@ -292,6 +310,12 @@ static int pciehp_resume(struct pcie_device *dev)
        return 0;
 }
 
+static int pciehp_runtime_suspend(struct pcie_device *dev)
+{
+       pciehp_disable_interrupt(dev);
+       return 0;
+}
+
 static int pciehp_runtime_resume(struct pcie_device *dev)
 {
        struct controller *ctrl = get_service_data(dev);
@@ -318,10 +342,12 @@ static struct pcie_port_service_driver hpdriver_portdrv = {
        .remove         = pciehp_remove,
 
 #ifdef CONFIG_PM
+#ifdef CONFIG_PM_SLEEP
        .suspend        = pciehp_suspend,
        .resume_noirq   = pciehp_resume_noirq,
        .resume         = pciehp_resume,
-       .runtime_suspend = pciehp_suspend,
+#endif
+       .runtime_suspend = pciehp_runtime_suspend,
        .runtime_resume = pciehp_runtime_resume,
 #endif /* PM */
 };
index 21af7b1..6503d15 100644 (file)
@@ -226,7 +226,7 @@ void pciehp_handle_disable_request(struct controller *ctrl)
 
 void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events)
 {
-       bool present, link_active;
+       int present, link_active;
 
        /*
         * If the slot is on and presence or link has changed, turn it off.
@@ -257,7 +257,7 @@ void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events)
        mutex_lock(&ctrl->state_lock);
        present = pciehp_card_present(ctrl);
        link_active = pciehp_check_link_active(ctrl);
-       if (!present && !link_active) {
+       if (present <= 0 && link_active <= 0) {
                mutex_unlock(&ctrl->state_lock);
                return;
        }
@@ -375,7 +375,8 @@ int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot)
                ctrl->request_result = -ENODEV;
                pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC);
                wait_event(ctrl->requester,
-                          !atomic_read(&ctrl->pending_events));
+                          !atomic_read(&ctrl->pending_events) &&
+                          !ctrl->ist_running);
                return ctrl->request_result;
        case POWERON_STATE:
                ctrl_info(ctrl, "Slot(%s): Already in powering on state\n",
@@ -408,7 +409,8 @@ int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot)
                mutex_unlock(&ctrl->state_lock);
                pciehp_request(ctrl, DISABLE_SLOT);
                wait_event(ctrl->requester,
-                          !atomic_read(&ctrl->pending_events));
+                          !atomic_read(&ctrl->pending_events) &&
+                          !ctrl->ist_running);
                return ctrl->request_result;
        case POWEROFF_STATE:
                ctrl_info(ctrl, "Slot(%s): Already in powering off state\n",
index 1a522c1..8a2cb17 100644 (file)
@@ -68,7 +68,7 @@ static int pcie_poll_cmd(struct controller *ctrl, int timeout)
        struct pci_dev *pdev = ctrl_dev(ctrl);
        u16 slot_status;
 
-       while (true) {
+       do {
                pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status);
                if (slot_status == (u16) ~0) {
                        ctrl_info(ctrl, "%s: no response from device\n",
@@ -81,11 +81,9 @@ static int pcie_poll_cmd(struct controller *ctrl, int timeout)
                                                   PCI_EXP_SLTSTA_CC);
                        return 1;
                }
-               if (timeout < 0)
-                       break;
                msleep(10);
                timeout -= 10;
-       }
+       } while (timeout >= 0);
        return 0;       /* timeout */
 }
 
@@ -201,17 +199,29 @@ static void pcie_write_cmd_nowait(struct controller *ctrl, u16 cmd, u16 mask)
        pcie_do_write_cmd(ctrl, cmd, mask, false);
 }
 
-bool pciehp_check_link_active(struct controller *ctrl)
+/**
+ * pciehp_check_link_active() - Is the link active
+ * @ctrl: PCIe hotplug controller
+ *
+ * Check whether the downstream link is currently active. Note it is
+ * possible that the card is removed immediately after this so the
+ * caller may need to take it into account.
+ *
+ * If the hotplug controller itself is not available anymore returns
+ * %-ENODEV.
+ */
+int pciehp_check_link_active(struct controller *ctrl)
 {
        struct pci_dev *pdev = ctrl_dev(ctrl);
        u16 lnk_status;
-       bool ret;
+       int ret;
 
-       pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status);
-       ret = !!(lnk_status & PCI_EXP_LNKSTA_DLLLA);
+       ret = pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status);
+       if (ret == PCIBIOS_DEVICE_NOT_FOUND || lnk_status == (u16)~0)
+               return -ENODEV;
 
-       if (ret)
-               ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status);
+       ret = !!(lnk_status & PCI_EXP_LNKSTA_DLLLA);
+       ctrl_dbg(ctrl, "%s: lnk_status = %x\n", __func__, lnk_status);
 
        return ret;
 }
@@ -373,13 +383,29 @@ void pciehp_get_latch_status(struct controller *ctrl, u8 *status)
        *status = !!(slot_status & PCI_EXP_SLTSTA_MRLSS);
 }
 
-bool pciehp_card_present(struct controller *ctrl)
+/**
+ * pciehp_card_present() - Is the card present
+ * @ctrl: PCIe hotplug controller
+ *
+ * Function checks whether the card is currently present in the slot and
+ * in that case returns true. Note it is possible that the card is
+ * removed immediately after the check so the caller may need to take
+ * this into account.
+ *
+ * It the hotplug controller itself is not available anymore returns
+ * %-ENODEV.
+ */
+int pciehp_card_present(struct controller *ctrl)
 {
        struct pci_dev *pdev = ctrl_dev(ctrl);
        u16 slot_status;
+       int ret;
 
-       pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status);
-       return slot_status & PCI_EXP_SLTSTA_PDS;
+       ret = pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status);
+       if (ret == PCIBIOS_DEVICE_NOT_FOUND || slot_status == (u16)~0)
+               return -ENODEV;
+
+       return !!(slot_status & PCI_EXP_SLTSTA_PDS);
 }
 
 /**
@@ -390,10 +416,19 @@ bool pciehp_card_present(struct controller *ctrl)
  * Presence Detect State bit, this helper also returns true if the Link Active
  * bit is set.  This is a concession to broken hotplug ports which hardwire
  * Presence Detect State to zero, such as Wilocity's [1ae9:0200].
+ *
+ * Returns: %1 if the slot is occupied and %0 if it is not. If the hotplug
+ *         port is not present anymore returns %-ENODEV.
  */
-bool pciehp_card_present_or_link_active(struct controller *ctrl)
+int pciehp_card_present_or_link_active(struct controller *ctrl)
 {
-       return pciehp_card_present(ctrl) || pciehp_check_link_active(ctrl);
+       int ret;
+
+       ret = pciehp_card_present(ctrl);
+       if (ret)
+               return ret;
+
+       return pciehp_check_link_active(ctrl);
 }
 
 int pciehp_query_power_fault(struct controller *ctrl)
@@ -583,6 +618,7 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
        irqreturn_t ret;
        u32 events;
 
+       ctrl->ist_running = true;
        pci_config_pm_runtime_get(pdev);
 
        /* rerun pciehp_isr() if the port was inaccessible on interrupt */
@@ -629,6 +665,7 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
        up_read(&ctrl->reset_lock);
 
        pci_config_pm_runtime_put(pdev);
+       ctrl->ist_running = false;
        wake_up(&ctrl->requester);
        return IRQ_HANDLED;
 }