Message ID | 1715740955-10688-1-git-send-email-hongxing.zhu@nxp.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | [v3] PCI: dwc: Fix suspend hang when e1000e network card is connected | expand |
On Wed, May 15, 2024 at 10:42:35AM +0800, Richard Zhu wrote: > In dw_pcie_suspend_noirq() and after the PME_TURN_OFF messgage is sent out, > one PMC version2.0 endpoint device E1000E network card hangs in LTSSM stat > polling and L2 stat check. > > To avoid this hang issue after the PME_TURN_OFF message is sent out. > Only poll and check LTSSM L2 stat if the EP's PMC version is 3 or later, Do you have any spec document show PME_TURN_OFF require PMC version >= 3? PMC register show how PCI function device require PME#(in PCI), in PCIe it is PM_PME message, which is difference with PME_TURN_OFF. PCIe spec require all devices have to reponse PME_TURN_OFF message. Does miss PME_TURN_OFF_ACK cause dw pcie controller hang? Frank > > Fixes: 4774faf854f5 ("PCI: dwc: Implement generic suspend/resume functionality") > Signed-off-by: Richard Zhu <hongxing.zhu@nxp.com> > --- > v2: > - Add a new help function dwc_get_devices_pmc_version() to get devices PMC > versions. > v3: > - Fetch the correct PMC versions, and get the minimum of them. > > .../pci/controller/dwc/pcie-designware-host.c | 52 ++++++++++++++++++- > 1 file changed, 51 insertions(+), 1 deletion(-) > > diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c > index cb8c3c2bcc790..a064972b36b7a 100644 > --- a/drivers/pci/controller/dwc/pcie-designware-host.c > +++ b/drivers/pci/controller/dwc/pcie-designware-host.c > @@ -884,6 +884,48 @@ int dw_pcie_setup_rc(struct dw_pcie_rp *pp) > } > EXPORT_SYMBOL_GPL(dw_pcie_setup_rc); > > +static int dwc_get_devices_pmc_version(struct dw_pcie *pci) > +{ > + int ret; > + u16 pmc, ver = 0; ver = PCI_PM_CAP_VER_MASK; > + struct dw_pcie_rp *pp = &pci->pp; > + struct pci_bus *child, *root_bus = NULL; > + struct pci_dev *pdev; > + > + /* Check the remote EP's PMC version */ > + list_for_each_entry(child, &pp->bridge->bus->children, node) { > + if (child->parent == pp->bridge->bus) { > + root_bus = child; > + break; > + } > + } > + > + if (!root_bus) { > + dev_err(pci->dev, "Failed to find downstream devices\n"); > + return -ENODEV; > + } > + > + list_for_each_entry(pdev, &root_bus->devices, bus_list) { > + if (PCI_SLOT(pdev->devfn) == 0) { > + /* find PCI PM capability in list */ > + ret = pci_find_capability(pdev, PCI_CAP_ID_PM); > + if (!ret) > + return ret; > + /* > + * Check device's PM ability version, the fetch the > + * minimum. > + */ > + pci_read_config_word(pdev, ret + PCI_PM_PMC, &pmc); > + pmc &= PCI_PM_CAP_VER_MASK; > + > + if ((ver == 0) || (ver > pmc)) > + ver = pmc; ver = min_t(u16, ver, pmc); > + } > + } > + > + return ver; > +} > + > static int dw_pcie_pme_turn_off(struct dw_pcie *pci) > { > struct dw_pcie_ob_atu_cfg atu = { 0 }; > @@ -924,7 +966,7 @@ int dw_pcie_suspend_noirq(struct dw_pcie *pci) > { > u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); > u32 val; > - int ret = 0; > + int pmc_ver, ret = 0; > > /* > * If L1SS is supported, then do not put the link into L2 as some > @@ -933,6 +975,8 @@ int dw_pcie_suspend_noirq(struct dw_pcie *pci) > if (dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKCTL) & PCI_EXP_LNKCTL_ASPM_L1) > return 0; > > + pmc_ver = dwc_get_devices_pmc_version(pci); > + > if (dw_pcie_get_ltssm(pci) > DW_PCIE_LTSSM_DETECT_ACT) { > /* Only send out PME_TURN_OFF when PCIE link is up */ > if (pci->pp.ops->pme_turn_off) > @@ -942,7 +986,13 @@ int dw_pcie_suspend_noirq(struct dw_pcie *pci) > > if (ret) > return ret; > + } > > + /* > + * PMC Ver2.0 device e1000e NIC hangs in LTSSM polling and L2 check. > + * Only do the L2 check when PMC version is Ver3.0 or later. > + */ > + if (pmc_ver >= 3) { > ret = read_poll_timeout(dw_pcie_get_ltssm, val, val == DW_PCIE_LTSSM_L2_IDLE, > PCIE_PME_TO_L2_TIMEOUT_US/10, > PCIE_PME_TO_L2_TIMEOUT_US, false, pci); > -- > 2.37.1 >
diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index cb8c3c2bcc790..a064972b36b7a 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -884,6 +884,48 @@ int dw_pcie_setup_rc(struct dw_pcie_rp *pp) } EXPORT_SYMBOL_GPL(dw_pcie_setup_rc); +static int dwc_get_devices_pmc_version(struct dw_pcie *pci) +{ + int ret; + u16 pmc, ver = 0; + struct dw_pcie_rp *pp = &pci->pp; + struct pci_bus *child, *root_bus = NULL; + struct pci_dev *pdev; + + /* Check the remote EP's PMC version */ + list_for_each_entry(child, &pp->bridge->bus->children, node) { + if (child->parent == pp->bridge->bus) { + root_bus = child; + break; + } + } + + if (!root_bus) { + dev_err(pci->dev, "Failed to find downstream devices\n"); + return -ENODEV; + } + + list_for_each_entry(pdev, &root_bus->devices, bus_list) { + if (PCI_SLOT(pdev->devfn) == 0) { + /* find PCI PM capability in list */ + ret = pci_find_capability(pdev, PCI_CAP_ID_PM); + if (!ret) + return ret; + /* + * Check device's PM ability version, the fetch the + * minimum. + */ + pci_read_config_word(pdev, ret + PCI_PM_PMC, &pmc); + pmc &= PCI_PM_CAP_VER_MASK; + + if ((ver == 0) || (ver > pmc)) + ver = pmc; + } + } + + return ver; +} + static int dw_pcie_pme_turn_off(struct dw_pcie *pci) { struct dw_pcie_ob_atu_cfg atu = { 0 }; @@ -924,7 +966,7 @@ int dw_pcie_suspend_noirq(struct dw_pcie *pci) { u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); u32 val; - int ret = 0; + int pmc_ver, ret = 0; /* * If L1SS is supported, then do not put the link into L2 as some @@ -933,6 +975,8 @@ int dw_pcie_suspend_noirq(struct dw_pcie *pci) if (dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKCTL) & PCI_EXP_LNKCTL_ASPM_L1) return 0; + pmc_ver = dwc_get_devices_pmc_version(pci); + if (dw_pcie_get_ltssm(pci) > DW_PCIE_LTSSM_DETECT_ACT) { /* Only send out PME_TURN_OFF when PCIE link is up */ if (pci->pp.ops->pme_turn_off) @@ -942,7 +986,13 @@ int dw_pcie_suspend_noirq(struct dw_pcie *pci) if (ret) return ret; + } + /* + * PMC Ver2.0 device e1000e NIC hangs in LTSSM polling and L2 check. + * Only do the L2 check when PMC version is Ver3.0 or later. + */ + if (pmc_ver >= 3) { ret = read_poll_timeout(dw_pcie_get_ltssm, val, val == DW_PCIE_LTSSM_L2_IDLE, PCIE_PME_TO_L2_TIMEOUT_US/10, PCIE_PME_TO_L2_TIMEOUT_US, false, pci);
In dw_pcie_suspend_noirq() and after the PME_TURN_OFF messgage is sent out, one PMC version2.0 endpoint device E1000E network card hangs in LTSSM stat polling and L2 stat check. To avoid this hang issue after the PME_TURN_OFF message is sent out. Only poll and check LTSSM L2 stat if the EP's PMC version is 3 or later, Fixes: 4774faf854f5 ("PCI: dwc: Implement generic suspend/resume functionality") Signed-off-by: Richard Zhu <hongxing.zhu@nxp.com> --- v2: - Add a new help function dwc_get_devices_pmc_version() to get devices PMC versions. v3: - Fetch the correct PMC versions, and get the minimum of them. .../pci/controller/dwc/pcie-designware-host.c | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-)