| From f8c9ad46b00453a8c075453f3745f8d263f44834 Mon Sep 17 00:00:00 2001 |
| From: Vidya Sagar <vidyas@nvidia.com> |
| Date: Mon, 22 Sep 2025 16:08:26 +0200 |
| Subject: PCI: tegra194: Handle errors in BPMP response |
| |
| From: Vidya Sagar <vidyas@nvidia.com> |
| |
| commit f8c9ad46b00453a8c075453f3745f8d263f44834 upstream. |
| |
| The return value from tegra_bpmp_transfer() indicates the success or |
| failure of the IPC transaction with BPMP. If the transaction succeeded, we |
| also need to check the actual command's result code. |
| |
| If we don't have error handling for tegra_bpmp_transfer(), we will set the |
| pcie->ep_state to EP_STATE_ENABLED even when the tegra_bpmp_transfer() |
| command fails. Thus, the pcie->ep_state will get out of sync with reality, |
| and any further PERST# assert + deassert will be a no-op and will not |
| trigger the hardware initialization sequence. |
| |
| This is because pex_ep_event_pex_rst_deassert() checks the current |
| pcie->ep_state, and does nothing if the current state is already |
| EP_STATE_ENABLED. |
| |
| Thus, it is important to have error handling for tegra_bpmp_transfer(), |
| such that the pcie->ep_state can not get out of sync with reality, so that |
| we will try to initialize the hardware not only during the first PERST# |
| assert + deassert, but also during any succeeding PERST# assert + deassert. |
| |
| One example where this fix is needed is when using a rock5b as host. |
| During the initial PERST# assert + deassert (triggered by the bootloader on |
| the rock5b) pex_ep_event_pex_rst_deassert() will get called, but for some |
| unknown reason, the tegra_bpmp_transfer() call to initialize the PHY fails. |
| Once Linux has been loaded on the rock5b, the PCIe driver will once again |
| assert + deassert PERST#. However, without tegra_bpmp_transfer() error |
| handling, this second PERST# assert + deassert will not trigger the |
| hardware initialization sequence. |
| |
| With tegra_bpmp_transfer() error handling, the second PERST# assert + |
| deassert will once again trigger the hardware to be initialized and this |
| time the tegra_bpmp_transfer() succeeds. |
| |
| Fixes: c57247f940e8 ("PCI: tegra: Add support for PCIe endpoint mode in Tegra194") |
| Signed-off-by: Vidya Sagar <vidyas@nvidia.com> |
| [cassel: improve commit log] |
| Signed-off-by: Niklas Cassel <cassel@kernel.org> |
| Signed-off-by: Manivannan Sadhasivam <mani@kernel.org> |
| Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> |
| Reviewed-by: Jon Hunter <jonathanh@nvidia.com> |
| Acked-by: Thierry Reding <treding@nvidia.com> |
| Cc: stable@vger.kernel.org |
| Link: https://patch.msgid.link/20250922140822.519796-8-cassel@kernel.org |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| drivers/pci/controller/dwc/pcie-tegra194.c | 18 ++++++++++++++++-- |
| 1 file changed, 16 insertions(+), 2 deletions(-) |
| |
| --- a/drivers/pci/controller/dwc/pcie-tegra194.c |
| +++ b/drivers/pci/controller/dwc/pcie-tegra194.c |
| @@ -1204,6 +1204,7 @@ static int tegra_pcie_bpmp_set_ctrl_stat |
| struct mrq_uphy_response resp; |
| struct tegra_bpmp_message msg; |
| struct mrq_uphy_request req; |
| + int err; |
| |
| /* |
| * Controller-5 doesn't need to have its state set by BPMP-FW in |
| @@ -1226,7 +1227,13 @@ static int tegra_pcie_bpmp_set_ctrl_stat |
| msg.rx.data = &resp; |
| msg.rx.size = sizeof(resp); |
| |
| - return tegra_bpmp_transfer(pcie->bpmp, &msg); |
| + err = tegra_bpmp_transfer(pcie->bpmp, &msg); |
| + if (err) |
| + return err; |
| + if (msg.rx.ret) |
| + return -EINVAL; |
| + |
| + return 0; |
| } |
| |
| static int tegra_pcie_bpmp_set_pll_state(struct tegra_pcie_dw *pcie, |
| @@ -1235,6 +1242,7 @@ static int tegra_pcie_bpmp_set_pll_state |
| struct mrq_uphy_response resp; |
| struct tegra_bpmp_message msg; |
| struct mrq_uphy_request req; |
| + int err; |
| |
| memset(&req, 0, sizeof(req)); |
| memset(&resp, 0, sizeof(resp)); |
| @@ -1254,7 +1262,13 @@ static int tegra_pcie_bpmp_set_pll_state |
| msg.rx.data = &resp; |
| msg.rx.size = sizeof(resp); |
| |
| - return tegra_bpmp_transfer(pcie->bpmp, &msg); |
| + err = tegra_bpmp_transfer(pcie->bpmp, &msg); |
| + if (err) |
| + return err; |
| + if (msg.rx.ret) |
| + return -EINVAL; |
| + |
| + return 0; |
| } |
| |
| static void tegra_pcie_downstream_dev_to_D0(struct tegra_pcie_dw *pcie) |