OSDN Git Service

msm: pcie: support PCIe MSI QGIC with stage 1 SMMU enabled
authorTony Truong <truong@codeaurora.org>
Fri, 24 Mar 2017 01:00:34 +0000 (18:00 -0700)
committerTony Truong <truong@codeaurora.org>
Wed, 5 Apr 2017 00:26:19 +0000 (17:26 -0700)
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 <truong@codeaurora.org>
drivers/pci/host/pci-msm.c

index 01abb1a..72695d3 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/kernel.h>
 #include <linux/of_pci.h>
 #include <linux/pci.h>
+#include <linux/iommu.h>
 #include <linux/platform_device.h>
 #include <linux/regulator/consumer.h>
 #include <linux/regulator/rpm-smd-regulator.h>
@@ -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);