aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLukas Wunner <lukas@wunner.de>2018-07-28 01:18:00 -0400
committerBjorn Helgaas <bhelgaas@google.com>2018-07-31 12:08:56 -0400
commit6b08c3854cfdc5d13165880e2b54642c47edc405 (patch)
tree1f1c268c56019571990a3bb42ac8fc8b10b27270
parent469e764c4a3c7260b353b7bc1bd56c283cb001da (diff)
PCI: pciehp: Support interrupts sent from D3hot
If a hotplug port is able to send an interrupt, one would naively assume that it is accessible at that moment. After all, if it wouldn't be accessible, i.e. if its parent is in D3hot and the link to the hotplug port is thus down, how should an interrupt come through? It turns out that assumption is wrong at least for Thunderbolt: Even though its parents are in D3hot, a Thunderbolt hotplug port is able to signal interrupts. Because the port's config space is inaccessible and resuming the parents may sleep, the hard IRQ handler has to defer runtime resuming the parents and reading the Slot Status register to the IRQ thread. If the hotplug port uses a level-triggered INTx interrupt, it needs to be masked until the IRQ thread has cleared the signaled events. For simplicity, this commit also masks edge-triggered MSI/MSI-X interrupts. Note that if the interrupt is shared (which can only happen for INTx), other devices are starved from receiving interrupts until the IRQ thread is scheduled, has runtime resumed the hotplug port's parents and has read and cleared the Slot Status register. That delay is dominated by the 10 ms D3hot->D0 transition time of each parent port. The worst case is a Thunderbolt downstream port at the end of a daisy chain: There may be up to six Thunderbolt controllers in-between it and the root port, each comprising an upstream and downstream port, plus its own upstream port. That's 13 x 10 = 130 ms. Possible mitigations are polling the interrupt while it's disabled or reducing the d3_delay of Thunderbolt ports if possible. Open code masking of the interrupt instead of requesting it with the IRQF_ONESHOT flag to minimize the period during which it is masked. (IRQF_ONESHOT unmasks the IRQ only after the IRQ thread has finished.) PCIe r4.0 sec 6.7.3.4 states that "If wake generation is required by the associated form factor specification, a hotplug capable Downstream Port must support generation of a wakeup event (using the PME mechanism) on hotplug events that occur when the system is in a sleep state or the Port is in device state D1, D2, or D3Hot." This would seem to imply that PME needs to be enabled on the hotplug port when it is runtime suspended. pci_enable_wake() currently doesn't enable PME on bridges, it may be necessary to add an exemption for hotplug bridges there. On "Light Ridge" Thunderbolt controllers, the PME_Status bit is not set when an interrupt occurs while the hotplug port is in D3hot, even if PME is enabled. (I've tested this on a Mac and we hardcode the OSC_PCI_EXPRESS_PME_CONTROL bit to 0 on Macs in negotiate_os_control(), modifying it to 1 didn't change the behavior.) (Side note: Section 6.7.3.4 also states that "PME and Hot-Plug Event interrupts (when both are implemented) always share the same MSI or MSI-X vector". That would only seem to apply to Root Ports, however the section never mentions Root Ports, only Downstream Ports. This is explained in the definition of "Downstream Port" in the "Terms and Acronyms" section of the PCIe Base Spec: "The Ports on a Switch that are not the Upstream Port are Downstream Ports. All Ports on a Root Complex are Downstream Ports.") Signed-off-by: Lukas Wunner <lukas@wunner.de> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Cc: Mika Westerberg <mika.westerberg@linux.intel.com> Cc: Ashok Raj <ashok.raj@intel.com> Cc: Keith Busch <keith.busch@intel.com> Cc: Yinghai Lu <yinghai@kernel.org>
-rw-r--r--drivers/pci/hotplug/pciehp.h5
-rw-r--r--drivers/pci/hotplug/pciehp_hpc.c45
2 files changed, 48 insertions, 2 deletions
diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h
index 247681963063..811cf83f956d 100644
--- a/drivers/pci/hotplug/pciehp.h
+++ b/drivers/pci/hotplug/pciehp.h
@@ -156,8 +156,13 @@ struct controller {
156 * 156 *
157 * %DISABLE_SLOT: Disable the slot in response to a user request via sysfs or 157 * %DISABLE_SLOT: Disable the slot in response to a user request via sysfs or
158 * an Attention Button press after the 5 second delay 158 * an Attention Button press after the 5 second delay
159 * %RERUN_ISR: Used by the IRQ handler to inform the IRQ thread that the
160 * hotplug port was inaccessible when the interrupt occurred, requiring
161 * that the IRQ handler is rerun by the IRQ thread after it has made the
162 * hotplug port accessible by runtime resuming its parents to D0
159 */ 163 */
160#define DISABLE_SLOT (1 << 16) 164#define DISABLE_SLOT (1 << 16)
165#define RERUN_ISR (1 << 17)
161 166
162#define ATTN_BUTTN(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_ABP) 167#define ATTN_BUTTN(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_ABP)
163#define POWER_CTRL(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_PCP) 168#define POWER_CTRL(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_PCP)
diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c
index 6313ddf38a51..6e9b4330ad82 100644
--- a/drivers/pci/hotplug/pciehp_hpc.c
+++ b/drivers/pci/hotplug/pciehp_hpc.c
@@ -19,6 +19,7 @@
19#include <linux/jiffies.h> 19#include <linux/jiffies.h>
20#include <linux/kthread.h> 20#include <linux/kthread.h>
21#include <linux/pci.h> 21#include <linux/pci.h>
22#include <linux/pm_runtime.h>
22#include <linux/interrupt.h> 23#include <linux/interrupt.h>
23#include <linux/time.h> 24#include <linux/time.h>
24#include <linux/slab.h> 25#include <linux/slab.h>
@@ -521,6 +522,7 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id)
521{ 522{
522 struct controller *ctrl = (struct controller *)dev_id; 523 struct controller *ctrl = (struct controller *)dev_id;
523 struct pci_dev *pdev = ctrl_dev(ctrl); 524 struct pci_dev *pdev = ctrl_dev(ctrl);
525 struct device *parent = pdev->dev.parent;
524 u16 status, events; 526 u16 status, events;
525 527
526 /* 528 /*
@@ -529,9 +531,26 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id)
529 if (pdev->current_state == PCI_D3cold) 531 if (pdev->current_state == PCI_D3cold)
530 return IRQ_NONE; 532 return IRQ_NONE;
531 533
534 /*
535 * Keep the port accessible by holding a runtime PM ref on its parent.
536 * Defer resume of the parent to the IRQ thread if it's suspended.
537 * Mask the interrupt until then.
538 */
539 if (parent) {
540 pm_runtime_get_noresume(parent);
541 if (!pm_runtime_active(parent)) {
542 pm_runtime_put(parent);
543 disable_irq_nosync(irq);
544 atomic_or(RERUN_ISR, &ctrl->pending_events);
545 return IRQ_WAKE_THREAD;
546 }
547 }
548
532 pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status); 549 pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status);
533 if (status == (u16) ~0) { 550 if (status == (u16) ~0) {
534 ctrl_info(ctrl, "%s: no response from device\n", __func__); 551 ctrl_info(ctrl, "%s: no response from device\n", __func__);
552 if (parent)
553 pm_runtime_put(parent);
535 return IRQ_NONE; 554 return IRQ_NONE;
536 } 555 }
537 556
@@ -550,11 +569,16 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id)
550 if (ctrl->power_fault_detected) 569 if (ctrl->power_fault_detected)
551 events &= ~PCI_EXP_SLTSTA_PFD; 570 events &= ~PCI_EXP_SLTSTA_PFD;
552 571
553 if (!events) 572 if (!events) {
573 if (parent)
574 pm_runtime_put(parent);
554 return IRQ_NONE; 575 return IRQ_NONE;
576 }
555 577
556 pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, events); 578 pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, events);
557 ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events); 579 ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events);
580 if (parent)
581 pm_runtime_put(parent);
558 582
559 /* 583 /*
560 * Command Completed notifications are not deferred to the 584 * Command Completed notifications are not deferred to the
@@ -584,13 +608,29 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id)
584static irqreturn_t pciehp_ist(int irq, void *dev_id) 608static irqreturn_t pciehp_ist(int irq, void *dev_id)
585{ 609{
586 struct controller *ctrl = (struct controller *)dev_id; 610 struct controller *ctrl = (struct controller *)dev_id;
611 struct pci_dev *pdev = ctrl_dev(ctrl);
587 struct slot *slot = ctrl->slot; 612 struct slot *slot = ctrl->slot;
613 irqreturn_t ret;
588 u32 events; 614 u32 events;
589 615
616 pci_config_pm_runtime_get(pdev);
617
618 /* rerun pciehp_isr() if the port was inaccessible on interrupt */
619 if (atomic_fetch_and(~RERUN_ISR, &ctrl->pending_events) & RERUN_ISR) {
620 ret = pciehp_isr(irq, dev_id);
621 enable_irq(irq);
622 if (ret != IRQ_WAKE_THREAD) {
623 pci_config_pm_runtime_put(pdev);
624 return ret;
625 }
626 }
627
590 synchronize_hardirq(irq); 628 synchronize_hardirq(irq);
591 events = atomic_xchg(&ctrl->pending_events, 0); 629 events = atomic_xchg(&ctrl->pending_events, 0);
592 if (!events) 630 if (!events) {
631 pci_config_pm_runtime_put(pdev);
593 return IRQ_NONE; 632 return IRQ_NONE;
633 }
594 634
595 /* Check Attention Button Pressed */ 635 /* Check Attention Button Pressed */
596 if (events & PCI_EXP_SLTSTA_ABP) { 636 if (events & PCI_EXP_SLTSTA_ABP) {
@@ -618,6 +658,7 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
618 pciehp_green_led_off(slot); 658 pciehp_green_led_off(slot);
619 } 659 }
620 660
661 pci_config_pm_runtime_put(pdev);
621 wake_up(&ctrl->requester); 662 wake_up(&ctrl->requester);
622 return IRQ_HANDLED; 663 return IRQ_HANDLED;
623} 664}