Commit 75c47c79 authored by Kai-Heng Feng's avatar Kai-Heng Feng Committed by Bjorn Helgaas

PCI/DPC: Disable DPC service on suspend

If the link is powered off during suspend, electrical noise may cause
errors that trigger DPC.  If the DPC interrupt is enabled and shares an IRQ
with PME, that causes a spurious wakeup during suspend.

Disable DPC triggering and the DPC interrupt during suspend to prevent
this.  Clear DPC interrupt status before re-enabling DPC interrupts during
resume so we don't get an interrupt for errors that occurred during the
suspend/resume process.

Link: https://bugzilla.kernel.org/show_bug.cgi?id=209149
Link: https://bugzilla.kernel.org/show_bug.cgi?id=216295
Link: https://bugzilla.kernel.org/show_bug.cgi?id=218090
Link: https://lore.kernel.org/r/20240416043225.1462548-3-kai.heng.feng@canonical.comSigned-off-by: default avatarKai-Heng Feng <kai.heng.feng@canonical.com>
[bhelgaas: clear status on resume, add comments, commit log]
Signed-off-by: default avatarBjorn Helgaas <bhelgaas@google.com>
parent 5afc2f76
...@@ -412,13 +412,44 @@ void pci_dpc_init(struct pci_dev *pdev) ...@@ -412,13 +412,44 @@ void pci_dpc_init(struct pci_dev *pdev)
} }
} }
static void dpc_enable(struct pcie_device *dev)
{
struct pci_dev *pdev = dev->port;
int dpc = pdev->dpc_cap;
u16 ctl;
/*
* Clear DPC Interrupt Status so we don't get an interrupt for an
* old event when setting DPC Interrupt Enable.
*/
pci_write_config_word(pdev, dpc + PCI_EXP_DPC_STATUS,
PCI_EXP_DPC_STATUS_INTERRUPT);
pci_read_config_word(pdev, dpc + PCI_EXP_DPC_CTL, &ctl);
ctl &= ~PCI_EXP_DPC_CTL_EN_MASK;
ctl |= PCI_EXP_DPC_CTL_EN_FATAL | PCI_EXP_DPC_CTL_INT_EN;
pci_write_config_word(pdev, dpc + PCI_EXP_DPC_CTL, ctl);
}
static void dpc_disable(struct pcie_device *dev)
{
struct pci_dev *pdev = dev->port;
int dpc = pdev->dpc_cap;
u16 ctl;
/* Disable DPC triggering and DPC interrupts */
pci_read_config_word(pdev, dpc + PCI_EXP_DPC_CTL, &ctl);
ctl &= ~(PCI_EXP_DPC_CTL_EN_FATAL | PCI_EXP_DPC_CTL_INT_EN);
pci_write_config_word(pdev, dpc + PCI_EXP_DPC_CTL, ctl);
}
#define FLAG(x, y) (((x) & (y)) ? '+' : '-') #define FLAG(x, y) (((x) & (y)) ? '+' : '-')
static int dpc_probe(struct pcie_device *dev) static int dpc_probe(struct pcie_device *dev)
{ {
struct pci_dev *pdev = dev->port; struct pci_dev *pdev = dev->port;
struct device *device = &dev->device; struct device *device = &dev->device;
int status; int status;
u16 ctl, cap; u16 cap;
if (!pcie_aer_is_native(pdev) && !pcie_ports_dpc_native) if (!pcie_aer_is_native(pdev) && !pcie_ports_dpc_native)
return -ENOTSUPP; return -ENOTSUPP;
...@@ -433,11 +464,7 @@ static int dpc_probe(struct pcie_device *dev) ...@@ -433,11 +464,7 @@ static int dpc_probe(struct pcie_device *dev)
} }
pci_read_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_CAP, &cap); pci_read_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_CAP, &cap);
dpc_enable(dev);
pci_read_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_CTL, &ctl);
ctl &= ~PCI_EXP_DPC_CTL_EN_MASK;
ctl |= PCI_EXP_DPC_CTL_EN_FATAL | PCI_EXP_DPC_CTL_INT_EN;
pci_write_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_CTL, ctl);
pci_info(pdev, "enabled with IRQ %d\n", dev->irq); pci_info(pdev, "enabled with IRQ %d\n", dev->irq);
pci_info(pdev, "error containment capabilities: Int Msg #%d, RPExt%c PoisonedTLP%c SwTrigger%c RP PIO Log %d, DL_ActiveErr%c\n", pci_info(pdev, "error containment capabilities: Int Msg #%d, RPExt%c PoisonedTLP%c SwTrigger%c RP PIO Log %d, DL_ActiveErr%c\n",
...@@ -450,14 +477,21 @@ static int dpc_probe(struct pcie_device *dev) ...@@ -450,14 +477,21 @@ static int dpc_probe(struct pcie_device *dev)
return status; return status;
} }
static void dpc_remove(struct pcie_device *dev) static int dpc_suspend(struct pcie_device *dev)
{ {
struct pci_dev *pdev = dev->port; dpc_disable(dev);
u16 ctl; return 0;
}
pci_read_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_CTL, &ctl); static int dpc_resume(struct pcie_device *dev)
ctl &= ~(PCI_EXP_DPC_CTL_EN_FATAL | PCI_EXP_DPC_CTL_INT_EN); {
pci_write_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_CTL, ctl); dpc_enable(dev);
return 0;
}
static void dpc_remove(struct pcie_device *dev)
{
dpc_disable(dev);
} }
static struct pcie_port_service_driver dpcdriver = { static struct pcie_port_service_driver dpcdriver = {
...@@ -465,6 +499,8 @@ static struct pcie_port_service_driver dpcdriver = { ...@@ -465,6 +499,8 @@ static struct pcie_port_service_driver dpcdriver = {
.port_type = PCIE_ANY_PORT, .port_type = PCIE_ANY_PORT,
.service = PCIE_PORT_SERVICE_DPC, .service = PCIE_PORT_SERVICE_DPC,
.probe = dpc_probe, .probe = dpc_probe,
.suspend = dpc_suspend,
.resume = dpc_resume,
.remove = dpc_remove, .remove = dpc_remove,
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment