Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

PCI: pciehp: Ignore Presence Detect Changed caused by DPC

Commit a97396c6eb13 ("PCI: pciehp: Ignore Link Down/Up caused by DPC")
amended PCIe hotplug to not bring down the slot upon Data Link Layer State
Changed events caused by Downstream Port Containment.

However Keith reports off-list that if the slot uses in-band presence
detect (i.e. Presence Detect State is derived from Data Link Layer Link
Active), DPC also causes a spurious Presence Detect Changed event.

This needs to be ignored as well.

Unfortunately there's no register indicating that in-band presence detect
is used. PCIe r5.0 sec 7.5.3.10 introduced the In-Band PD Disable bit in
the Slot Control Register. The PCIe hotplug driver sets this bit on
ports supporting it. But older ports may still use in-band presence
detect.

If in-band presence detect can be disabled, Presence Detect Changed events
occurring during DPC must not be ignored because they signal device
replacement. On all other ports, device replacement cannot be detected
reliably because the Presence Detect Changed event could be a side effect
of DPC. On those (older) ports, perform a best-effort device replacement
check by comparing the Vendor ID, Device ID and other data in Config Space
with the values cached in struct pci_dev. Use the existing helper
pciehp_device_replaced() to accomplish this. It is currently #ifdef'ed to
CONFIG_PM_SLEEP in pciehp_core.c, so move it to pciehp_hpc.c where most
other functions accessing config space reside.

Reported-by: Keith Busch <kbusch@kernel.org>
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/fa264ff71952915c4e35a53c89eb0cde8455a5c5.1744298239.git.lukas@wunner.de

authored by

Lukas Wunner and committed by
Bjorn Helgaas
c3be50f7 0af2f6be

+43 -36
+1
drivers/pci/hotplug/pciehp.h
··· 187 187 int pciehp_card_present_or_link_active(struct controller *ctrl); 188 188 int pciehp_check_link_status(struct controller *ctrl); 189 189 int pciehp_check_link_active(struct controller *ctrl); 190 + bool pciehp_device_replaced(struct controller *ctrl); 190 191 void pciehp_release_ctrl(struct controller *ctrl); 191 192 192 193 int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot);
-29
drivers/pci/hotplug/pciehp_core.c
··· 284 284 return 0; 285 285 } 286 286 287 - static bool pciehp_device_replaced(struct controller *ctrl) 288 - { 289 - struct pci_dev *pdev __free(pci_dev_put) = NULL; 290 - u32 reg; 291 - 292 - if (pci_dev_is_disconnected(ctrl->pcie->port)) 293 - return false; 294 - 295 - pdev = pci_get_slot(ctrl->pcie->port->subordinate, PCI_DEVFN(0, 0)); 296 - if (!pdev) 297 - return true; 298 - 299 - if (pci_read_config_dword(pdev, PCI_VENDOR_ID, &reg) || 300 - reg != (pdev->vendor | (pdev->device << 16)) || 301 - pci_read_config_dword(pdev, PCI_CLASS_REVISION, &reg) || 302 - reg != (pdev->revision | (pdev->class << 8))) 303 - return true; 304 - 305 - if (pdev->hdr_type == PCI_HEADER_TYPE_NORMAL && 306 - (pci_read_config_dword(pdev, PCI_SUBSYSTEM_VENDOR_ID, &reg) || 307 - reg != (pdev->subsystem_vendor | (pdev->subsystem_device << 16)))) 308 - return true; 309 - 310 - if (pci_get_dsn(pdev) != ctrl->dsn) 311 - return true; 312 - 313 - return false; 314 - } 315 - 316 287 static int pciehp_resume_noirq(struct pcie_device *dev) 317 288 { 318 289 struct controller *ctrl = get_service_data(dev);
+42 -7
drivers/pci/hotplug/pciehp_hpc.c
··· 563 563 PCI_EXP_SLTCTL_PWR_OFF); 564 564 } 565 565 566 + bool pciehp_device_replaced(struct controller *ctrl) 567 + { 568 + struct pci_dev *pdev __free(pci_dev_put) = NULL; 569 + u32 reg; 570 + 571 + if (pci_dev_is_disconnected(ctrl->pcie->port)) 572 + return false; 573 + 574 + pdev = pci_get_slot(ctrl->pcie->port->subordinate, PCI_DEVFN(0, 0)); 575 + if (!pdev) 576 + return true; 577 + 578 + if (pci_read_config_dword(pdev, PCI_VENDOR_ID, &reg) || 579 + reg != (pdev->vendor | (pdev->device << 16)) || 580 + pci_read_config_dword(pdev, PCI_CLASS_REVISION, &reg) || 581 + reg != (pdev->revision | (pdev->class << 8))) 582 + return true; 583 + 584 + if (pdev->hdr_type == PCI_HEADER_TYPE_NORMAL && 585 + (pci_read_config_dword(pdev, PCI_SUBSYSTEM_VENDOR_ID, &reg) || 586 + reg != (pdev->subsystem_vendor | (pdev->subsystem_device << 16)))) 587 + return true; 588 + 589 + if (pci_get_dsn(pdev) != ctrl->dsn) 590 + return true; 591 + 592 + return false; 593 + } 594 + 566 595 static void pciehp_ignore_dpc_link_change(struct controller *ctrl, 567 - struct pci_dev *pdev, int irq) 596 + struct pci_dev *pdev, int irq, 597 + u16 ignored_events) 568 598 { 569 599 /* 570 600 * Ignore link changes which occurred while waiting for DPC recovery. 571 601 * Could be several if DPC triggered multiple times consecutively. 572 602 */ 573 603 synchronize_hardirq(irq); 574 - atomic_and(~PCI_EXP_SLTSTA_DLLSC, &ctrl->pending_events); 604 + atomic_and(~ignored_events, &ctrl->pending_events); 575 605 if (pciehp_poll_mode) 576 606 pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, 577 - PCI_EXP_SLTSTA_DLLSC); 607 + ignored_events); 578 608 ctrl_info(ctrl, "Slot(%s): Link Down/Up ignored (recovered by DPC)\n", 579 609 slot_name(ctrl)); 580 610 ··· 614 584 * Synthesize it to ensure that it is acted on. 615 585 */ 616 586 down_read_nested(&ctrl->reset_lock, ctrl->depth); 617 - if (!pciehp_check_link_active(ctrl)) 618 - pciehp_request(ctrl, PCI_EXP_SLTSTA_DLLSC); 587 + if (!pciehp_check_link_active(ctrl) || pciehp_device_replaced(ctrl)) 588 + pciehp_request(ctrl, ignored_events); 619 589 up_read(&ctrl->reset_lock); 620 590 } 621 591 ··· 766 736 */ 767 737 if ((events & PCI_EXP_SLTSTA_DLLSC) && pci_dpc_recovered(pdev) && 768 738 ctrl->state == ON_STATE) { 769 - events &= ~PCI_EXP_SLTSTA_DLLSC; 770 - pciehp_ignore_dpc_link_change(ctrl, pdev, irq); 739 + u16 ignored_events = PCI_EXP_SLTSTA_DLLSC; 740 + 741 + if (!ctrl->inband_presence_disabled) 742 + ignored_events |= events & PCI_EXP_SLTSTA_PDC; 743 + 744 + events &= ~ignored_events; 745 + pciehp_ignore_dpc_link_change(ctrl, pdev, irq, ignored_events); 771 746 } 772 747 773 748 /*