From 23764aabce7b58460347a99e5edf8cfc4ef0e3d6 Mon Sep 17 00:00:00 2001 From: Tony Truong Date: Thu, 23 Mar 2017 18:00:34 -0700 Subject: [PATCH] msm: pcie: support PCIe MSI QGIC with stage 1 SMMU enabled When Stage 1 SMMU is enabled, the QGIC doorbell address needs to be mapped or else there will be a translation fault when an endpoint tries to trigger an interrupt via MSI. PCIe host driver will map this address on behalf of the client. Change-Id: I7fdbe62daeb5dbecc459e4d9bc7832785f5b9fb7 Signed-off-by: Tony Truong --- drivers/pci/host/pci-msm.c | 94 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/drivers/pci/host/pci-msm.c b/drivers/pci/host/pci-msm.c index 01abb1adec30..72695d3b9224 100644 --- a/drivers/pci/host/pci-msm.c +++ b/drivers/pci/host/pci-msm.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -5573,6 +5574,34 @@ static irqreturn_t handle_global_irq(int irq, void *data) return IRQ_HANDLED; } +static void msm_pcie_unmap_qgic_addr(struct msm_pcie_dev_t *dev, + struct pci_dev *pdev) +{ + struct iommu_domain *domain = iommu_get_domain_for_dev(&pdev->dev); + int bypass_en = 0; + + if (!domain) { + PCIE_DBG(dev, + "PCIe: RC%d: client does not have an iommu domain\n", + dev->rc_idx); + return; + } + + iommu_domain_get_attr(domain, DOMAIN_ATTR_S1_BYPASS, &bypass_en); + if (!bypass_en) { + int ret; + phys_addr_t pcie_base_addr = + dev->res[MSM_PCIE_RES_DM_CORE].resource->start; + dma_addr_t iova = rounddown(pcie_base_addr, PAGE_SIZE); + + ret = iommu_unmap(domain, iova, PAGE_SIZE); + if (ret != PAGE_SIZE) + PCIE_ERR(dev, + "PCIe: RC%d: failed to unmap QGIC address. ret = %d\n", + dev->rc_idx, ret); + } +} + void msm_pcie_destroy_irq(unsigned int irq) { int pos; @@ -5620,6 +5649,8 @@ void msm_pcie_destroy_irq(unsigned int irq) irq, dev->rc_idx); return; } else { + if (irq == firstirq + nvec - 1) + msm_pcie_unmap_qgic_addr(dev, pdev); pos = irq - firstirq; } } else { @@ -5769,10 +5800,64 @@ again: return irq; } +static int msm_pcie_map_qgic_addr(struct msm_pcie_dev_t *dev, + struct pci_dev *pdev, + struct msi_msg *msg) +{ + struct iommu_domain *domain = iommu_get_domain_for_dev(&pdev->dev); + int ret, bypass_en = 0; + dma_addr_t iova; + phys_addr_t pcie_base_addr, gicm_db_offset; + + msg->address_hi = 0; + msg->address_lo = dev->msi_gicm_addr; + + if (!domain) { + PCIE_DBG(dev, + "PCIe: RC%d: client does not have an iommu domain\n", + dev->rc_idx); + return 0; + } + + iommu_domain_get_attr(domain, DOMAIN_ATTR_S1_BYPASS, &bypass_en); + + PCIE_DBG(dev, + "PCIe: RC%d: Stage 1 is %s for endpoint: %04x:%02x\n", + dev->rc_idx, bypass_en ? "bypass" : "enabled", + pdev->bus->number, pdev->devfn); + + if (bypass_en) + return 0; + + gicm_db_offset = dev->msi_gicm_addr - + rounddown(dev->msi_gicm_addr, PAGE_SIZE); + /* + * Use PCIe DBI address as the IOVA since client cannot + * use this address for their IOMMU mapping. This will + * prevent any conflicts between PCIe host and + * client's mapping. + */ + pcie_base_addr = dev->res[MSM_PCIE_RES_DM_CORE].resource->start; + iova = rounddown(pcie_base_addr, PAGE_SIZE); + + ret = iommu_map(domain, iova, rounddown(dev->msi_gicm_addr, PAGE_SIZE), + PAGE_SIZE, IOMMU_READ | IOMMU_WRITE); + if (ret < 0) { + PCIE_ERR(dev, + "PCIe: RC%d: ret: %d: Could not do iommu map for QGIC address\n", + dev->rc_idx, ret); + return -ENOMEM; + } + + msg->address_lo = iova + gicm_db_offset; + + return 0; +} + static int arch_setup_msi_irq_qgic(struct pci_dev *pdev, struct msi_desc *desc, int nvec) { - int irq, index, firstirq = 0; + int irq, index, ret, firstirq = 0; struct msi_msg msg; struct msm_pcie_dev_t *dev = PCIE_BUS_PRIV_DATA(pdev->bus); @@ -5794,8 +5879,11 @@ static int arch_setup_msi_irq_qgic(struct pci_dev *pdev, /* write msi vector and data */ irq_set_msi_desc(firstirq, desc); - msg.address_hi = 0; - msg.address_lo = dev->msi_gicm_addr; + + ret = msm_pcie_map_qgic_addr(dev, pdev, &msg); + if (ret) + return ret; + msg.data = dev->msi_gicm_base + (firstirq - dev->msi[0].num); write_msi_msg(firstirq, &msg); -- 2.11.0