From 8668d80571edfd9f9c2ca9c775bb95e445f7b292 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Mon, 24 Feb 2025 09:08:43 +0800 Subject: [PATCH] PCI: rockchip: dw-ep: Hide broken ATS cap From chip design point of view, ATS support wasn't implemented in EP mode, but leaving ATS cap available for both of EP and RC mode is totally broken if servers active IOMMU and ATS support. Reports state the problem are: (1)When running the rk3588 in endpoint mode, with an Intel host with IOMMU enabled, the host side prints: DMAR: VT-d detected Invalidation Time-out Error: SID 0 (2)When running the rk3588 in endpoint mode, with an AMD host with IOMMU enabled, the host side prints: iommu ivhd0: AMD-Vi: Event logged [IOTLB_INV_TIMEOUT device=63:00.0 address=0x42b5b01a0] Signed-off-by: Shawn Lin Change-Id: I7763f304bb3b71c11a67579803b2531ab7538133 --- .../pci/controller/dwc/pcie-dw-ep-rockchip.c | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-dw-ep-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-ep-rockchip.c index 1c80d917930e..bdf9480d83a9 100644 --- a/drivers/pci/controller/dwc/pcie-dw-ep-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-ep-rockchip.c @@ -700,6 +700,45 @@ static int rockchip_pcie_deinit_host(struct rockchip_pcie *rockchip) return 0; } +/* + * ATS does not work on platform like rk3588 when running in EP mode. + * After a host has enabled ATS on the EP side, it will send an IOTLB + * invalidation request to the EP side. The rk3588 will never send a completion + * back and eventually the host will print an IOTLB_INV_TIMEOUT error, and the + * EP will not be operational. If we hide the ATS cap, things work as expected. + */ +static void rockchip_pcie_hide_broken_ats_cap(struct dw_pcie *pci) +{ + struct device *dev = pci->dev; + unsigned int spcie_cap_offset, next_cap_offset; + u32 spcie_cap_header, next_cap_header; + + /* only hide the ATS cap for rk3588 running in EP mode */ + if (!of_device_is_compatible(dev->of_node, "rockchip,rk3588-pcie-std-ep")) + return; + + spcie_cap_offset = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_SECPCI); + if (!spcie_cap_offset) + return; + + spcie_cap_header = dw_pcie_readl_dbi(pci, spcie_cap_offset); + next_cap_offset = PCI_EXT_CAP_NEXT(spcie_cap_header); + + next_cap_header = dw_pcie_readl_dbi(pci, next_cap_offset); + if (PCI_EXT_CAP_ID(next_cap_header) != PCI_EXT_CAP_ID_ATS) + return; + + /* clear next ptr */ + spcie_cap_header &= ~GENMASK(31, 20); + + /* set next ptr to next ptr of ATS_CAP */ + spcie_cap_header |= next_cap_header & GENMASK(31, 20); + + dw_pcie_dbi_ro_wr_en(pci); + dw_pcie_writel_dbi(pci, spcie_cap_offset, spcie_cap_header); + dw_pcie_dbi_ro_wr_dis(pci); +} + static int rockchip_pcie_config_host(struct rockchip_pcie *rockchip) { struct device *dev = rockchip->pci.dev; @@ -717,6 +756,8 @@ static int rockchip_pcie_config_host(struct rockchip_pcie *rockchip) dw_pcie_setup(&rockchip->pci); + rockchip_pcie_hide_broken_ats_cap(pci); + dw_pcie_dbi_ro_wr_en(&rockchip->pci); /* Enable bus master and memory space */ dw_pcie_writel_dbi(pci, PCIE_TYPE0_STATUS_COMMAND_REG, 0x6);