diff options
author | Bjorn Helgaas <bhelgaas@google.com> | 2017-02-15 11:56:07 -0600 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2017-02-15 11:56:07 -0600 |
commit | af3a2ab5daa0b8bf188a039c122a670cdc8b9544 (patch) | |
tree | 2f6ddec5a2c66b61cebd5f24b906100eeefcbee1 /drivers/pci | |
parent | 8f417ca9ebfa8701a41db64f5ed9cbb01b8e4219 (diff) | |
parent | abdbf4d635a9a8c956bb9757a9d4f08c2abe1f97 (diff) |
Merge branch 'pci/dpc' into next
* pci/dpc:
PCI/DPC: Wait for Root Port busy to clear
PCI/DPC: Decode extended reasons
Diffstat (limited to 'drivers/pci')
-rw-r--r-- | drivers/pci/pcie/pcie-dpc.c | 34 |
1 files changed, 31 insertions, 3 deletions
diff --git a/drivers/pci/pcie/pcie-dpc.c b/drivers/pci/pcie/pcie-dpc.c index 9811b14d9ad8..d4d70ef4a2d7 100644 --- a/drivers/pci/pcie/pcie-dpc.c +++ b/drivers/pci/pcie/pcie-dpc.c @@ -19,8 +19,28 @@ struct dpc_dev { struct pcie_device *dev; struct work_struct work; int cap_pos; + bool rp; }; +static int dpc_wait_rp_inactive(struct dpc_dev *dpc) +{ + unsigned long timeout = jiffies + HZ; + struct pci_dev *pdev = dpc->dev->port; + u16 status; + + pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_STATUS, &status); + while (status & PCI_EXP_DPC_RP_BUSY && + !time_after(jiffies, timeout)) { + msleep(10); + pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_STATUS, &status); + } + if (status & PCI_EXP_DPC_RP_BUSY) { + dev_warn(&pdev->dev, "DPC root port still busy\n"); + return -EBUSY; + } + return 0; +} + static void dpc_wait_link_inactive(struct pci_dev *pdev) { unsigned long timeout = jiffies + HZ; @@ -33,7 +53,7 @@ static void dpc_wait_link_inactive(struct pci_dev *pdev) pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status); } if (lnk_status & PCI_EXP_LNKSTA_DLLLA) - dev_warn(&pdev->dev, "Link state not disabled for DPC event"); + dev_warn(&pdev->dev, "Link state not disabled for DPC event\n"); } static void interrupt_event_handler(struct work_struct *work) @@ -52,6 +72,8 @@ static void interrupt_event_handler(struct work_struct *work) pci_unlock_rescan_remove(); dpc_wait_link_inactive(pdev); + if (dpc->rp && dpc_wait_rp_inactive(dpc)) + return; pci_write_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_STATUS, PCI_EXP_DPC_STATUS_TRIGGER | PCI_EXP_DPC_STATUS_INTERRUPT); } @@ -73,11 +95,15 @@ static irqreturn_t dpc_irq(int irq, void *context) if (status & PCI_EXP_DPC_STATUS_TRIGGER) { u16 reason = (status >> 1) & 0x3; + u16 ext_reason = (status >> 5) & 0x3; - dev_warn(&dpc->dev->device, "DPC %s triggered, remove downstream devices\n", + dev_warn(&dpc->dev->device, "DPC %s detected, remove downstream devices\n", (reason == 0) ? "unmasked uncorrectable error" : (reason == 1) ? "ERR_NONFATAL" : - (reason == 2) ? "ERR_FATAL" : "extended error"); + (reason == 2) ? "ERR_FATAL" : + (ext_reason == 0) ? "RP PIO error" : + (ext_reason == 1) ? "software trigger" : + "reserved error"); schedule_work(&dpc->work); } return IRQ_HANDLED; @@ -111,6 +137,8 @@ static int dpc_probe(struct pcie_device *dev) pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CAP, &cap); pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CTL, &ctl); + dpc->rp = (cap & PCI_EXP_DPC_CAP_RP_EXT); + ctl |= PCI_EXP_DPC_CTL_EN_NONFATAL | PCI_EXP_DPC_CTL_INT_EN; pci_write_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CTL, ctl); |