]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - drivers/iommu/arm-smmu-v3.c
Merge branches 'pm-core', 'pm-qos', 'pm-domains' and 'pm-opp'
[linux.git] / drivers / iommu / arm-smmu-v3.c
index e6f9b2d745ca0eefbe02cb5e9c2680dcff60af46..5806a6acc94ecd7543c2435558a0907ec0934ff2 100644 (file)
@@ -20,6 +20,8 @@
  * This driver is powered by bad coffee and bombay mix.
  */
 
+#include <linux/acpi.h>
+#include <linux/acpi_iort.h>
 #include <linux/delay.h>
 #include <linux/dma-iommu.h>
 #include <linux/err.h>
 #define STRTAB_STE_1_SHCFG_INCOMING    1UL
 #define STRTAB_STE_1_SHCFG_SHIFT       44
 
-#define STRTAB_STE_1_PRIVCFG_UNPRIV    2UL
-#define STRTAB_STE_1_PRIVCFG_SHIFT     48
-
 #define STRTAB_STE_2_S2VMID_SHIFT      0
 #define STRTAB_STE_2_S2VMID_MASK       0xffffUL
 #define STRTAB_STE_2_VTCR_SHIFT                32
 /* High-level queue structures */
 #define ARM_SMMU_POLL_TIMEOUT_US       100
 
+#define MSI_IOVA_BASE                  0x8000000
+#define MSI_IOVA_LENGTH                        0x100000
+
 static bool disable_bypass;
 module_param_named(disable_bypass, disable_bypass, bool, S_IRUGO);
 MODULE_PARM_DESC(disable_bypass,
@@ -614,6 +616,9 @@ struct arm_smmu_device {
        unsigned int                    sid_bits;
 
        struct arm_smmu_strtab_cfg      strtab_cfg;
+
+       /* IOMMU core code handle */
+       struct iommu_device             iommu;
 };
 
 /* SMMU private data for each master */
@@ -1040,13 +1045,8 @@ static void arm_smmu_write_strtab_ent(struct arm_smmu_device *smmu, u32 sid,
                }
        }
 
-       /* Nuke the existing Config, as we're going to rewrite it */
-       val &= ~(STRTAB_STE_0_CFG_MASK << STRTAB_STE_0_CFG_SHIFT);
-
-       if (ste->valid)
-               val |= STRTAB_STE_0_V;
-       else
-               val &= ~STRTAB_STE_0_V;
+       /* Nuke the existing STE_0 value, as we're going to rewrite it */
+       val = ste->valid ? STRTAB_STE_0_V : 0;
 
        if (ste->bypass) {
                val |= disable_bypass ? STRTAB_STE_0_CFG_ABORT
@@ -1071,9 +1071,7 @@ static void arm_smmu_write_strtab_ent(struct arm_smmu_device *smmu, u32 sid,
 #ifdef CONFIG_PCI_ATS
                         STRTAB_STE_1_EATS_TRANS << STRTAB_STE_1_EATS_SHIFT |
 #endif
-                        STRTAB_STE_1_STRW_NSEL1 << STRTAB_STE_1_STRW_SHIFT |
-                        STRTAB_STE_1_PRIVCFG_UNPRIV <<
-                        STRTAB_STE_1_PRIVCFG_SHIFT);
+                        STRTAB_STE_1_STRW_NSEL1 << STRTAB_STE_1_STRW_SHIFT);
 
                if (smmu->features & ARM_SMMU_FEAT_STALLS)
                        dst[1] |= cpu_to_le64(STRTAB_STE_1_S1STALLD);
@@ -1081,7 +1079,6 @@ static void arm_smmu_write_strtab_ent(struct arm_smmu_device *smmu, u32 sid,
                val |= (ste->s1_cfg->cdptr_dma & STRTAB_STE_0_S1CTXPTR_MASK
                        << STRTAB_STE_0_S1CTXPTR_SHIFT) |
                        STRTAB_STE_0_CFG_S1_TRANS;
-
        }
 
        if (ste->s2_cfg) {
@@ -1358,7 +1355,7 @@ static void arm_smmu_tlb_inv_range_nosync(unsigned long iova, size_t size,
        } while (size -= granule);
 }
 
-static struct iommu_gather_ops arm_smmu_gather_ops = {
+static const struct iommu_gather_ops arm_smmu_gather_ops = {
        .tlb_flush_all  = arm_smmu_tlb_inv_context,
        .tlb_add_flush  = arm_smmu_tlb_inv_range_nosync,
        .tlb_sync       = arm_smmu_tlb_sync,
@@ -1370,8 +1367,6 @@ static bool arm_smmu_capable(enum iommu_cap cap)
        switch (cap) {
        case IOMMU_CAP_CACHE_COHERENCY:
                return true;
-       case IOMMU_CAP_INTR_REMAP:
-               return true; /* MSIs are just memory writes */
        case IOMMU_CAP_NOEXEC:
                return true;
        default:
@@ -1723,13 +1718,14 @@ static struct platform_driver arm_smmu_driver;
 
 static int arm_smmu_match_node(struct device *dev, void *data)
 {
-       return dev->of_node == data;
+       return dev->fwnode == data;
 }
 
-static struct arm_smmu_device *arm_smmu_get_by_node(struct device_node *np)
+static
+struct arm_smmu_device *arm_smmu_get_by_fwnode(struct fwnode_handle *fwnode)
 {
        struct device *dev = driver_find_device(&arm_smmu_driver.driver, NULL,
-                                               np, arm_smmu_match_node);
+                                               fwnode, arm_smmu_match_node);
        put_device(dev);
        return dev ? dev_get_drvdata(dev) : NULL;
 }
@@ -1765,7 +1761,7 @@ static int arm_smmu_add_device(struct device *dev)
                master = fwspec->iommu_priv;
                smmu = master->smmu;
        } else {
-               smmu = arm_smmu_get_by_node(to_of_node(fwspec->iommu_fwnode));
+               smmu = arm_smmu_get_by_fwnode(fwspec->iommu_fwnode);
                if (!smmu)
                        return -ENODEV;
                master = kzalloc(sizeof(*master), GFP_KERNEL);
@@ -1792,8 +1788,10 @@ static int arm_smmu_add_device(struct device *dev)
        }
 
        group = iommu_group_get_for_dev(dev);
-       if (!IS_ERR(group))
+       if (!IS_ERR(group)) {
                iommu_group_put(group);
+               iommu_device_link(&smmu->iommu, dev);
+       }
 
        return PTR_ERR_OR_ZERO(group);
 }
@@ -1802,14 +1800,17 @@ static void arm_smmu_remove_device(struct device *dev)
 {
        struct iommu_fwspec *fwspec = dev->iommu_fwspec;
        struct arm_smmu_master_data *master;
+       struct arm_smmu_device *smmu;
 
        if (!fwspec || fwspec->ops != &arm_smmu_ops)
                return;
 
        master = fwspec->iommu_priv;
+       smmu = master->smmu;
        if (master && master->ste.valid)
                arm_smmu_detach_dev(dev);
        iommu_group_remove_device(dev);
+       iommu_device_unlink(&smmu->iommu, dev);
        kfree(master);
        iommu_fwspec_free(dev);
 }
@@ -1880,6 +1881,29 @@ static int arm_smmu_of_xlate(struct device *dev, struct of_phandle_args *args)
        return iommu_fwspec_add_ids(dev, args->args, 1);
 }
 
+static void arm_smmu_get_resv_regions(struct device *dev,
+                                     struct list_head *head)
+{
+       struct iommu_resv_region *region;
+       int prot = IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_MMIO;
+
+       region = iommu_alloc_resv_region(MSI_IOVA_BASE, MSI_IOVA_LENGTH,
+                                        prot, IOMMU_RESV_MSI);
+       if (!region)
+               return;
+
+       list_add_tail(&region->list, head);
+}
+
+static void arm_smmu_put_resv_regions(struct device *dev,
+                                     struct list_head *head)
+{
+       struct iommu_resv_region *entry, *next;
+
+       list_for_each_entry_safe(entry, next, head, list)
+               kfree(entry);
+}
+
 static struct iommu_ops arm_smmu_ops = {
        .capable                = arm_smmu_capable,
        .domain_alloc           = arm_smmu_domain_alloc,
@@ -1895,6 +1919,8 @@ static struct iommu_ops arm_smmu_ops = {
        .domain_get_attr        = arm_smmu_domain_get_attr,
        .domain_set_attr        = arm_smmu_domain_set_attr,
        .of_xlate               = arm_smmu_of_xlate,
+       .get_resv_regions       = arm_smmu_get_resv_regions,
+       .put_resv_regions       = arm_smmu_put_resv_regions,
        .pgsize_bitmap          = -1UL, /* Restricted during device attach */
 };
 
@@ -1980,17 +2006,9 @@ static int arm_smmu_init_strtab_2lvl(struct arm_smmu_device *smmu)
        u32 size, l1size;
        struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
 
-       /*
-        * If we can resolve everything with a single L2 table, then we
-        * just need a single L1 descriptor. Otherwise, calculate the L1
-        * size, capped to the SIDSIZE.
-        */
-       if (smmu->sid_bits < STRTAB_SPLIT) {
-               size = 0;
-       } else {
-               size = STRTAB_L1_SZ_SHIFT - (ilog2(STRTAB_L1_DESC_DWORDS) + 3);
-               size = min(size, smmu->sid_bits - STRTAB_SPLIT);
-       }
+       /* Calculate the L1 size, capped to the SIDSIZE. */
+       size = STRTAB_L1_SZ_SHIFT - (ilog2(STRTAB_L1_DESC_DWORDS) + 3);
+       size = min(size, smmu->sid_bits - STRTAB_SPLIT);
        cfg->num_l1_ents = 1 << size;
 
        size += STRTAB_SPLIT;
@@ -2380,10 +2398,10 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu, bool bypass)
        return 0;
 }
 
-static int arm_smmu_device_probe(struct arm_smmu_device *smmu)
+static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
 {
        u32 reg;
-       bool coherent;
+       bool coherent = smmu->features & ARM_SMMU_FEAT_COHERENCY;
 
        /* IDR0 */
        reg = readl_relaxed(smmu->base + ARM_SMMU_IDR0);
@@ -2435,13 +2453,9 @@ static int arm_smmu_device_probe(struct arm_smmu_device *smmu)
                smmu->features |= ARM_SMMU_FEAT_HYP;
 
        /*
-        * The dma-coherent property is used in preference to the ID
+        * The coherency feature as set by FW is used in preference to the ID
         * register, but warn on mismatch.
         */
-       coherent = of_dma_is_coherent(smmu->dev->of_node);
-       if (coherent)
-               smmu->features |= ARM_SMMU_FEAT_COHERENCY;
-
        if (!!(reg & IDR0_COHACC) != coherent)
                dev_warn(smmu->dev, "IDR0.COHACC overridden by dma-coherent property (%s)\n",
                         coherent ? "true" : "false");
@@ -2505,6 +2519,13 @@ static int arm_smmu_device_probe(struct arm_smmu_device *smmu)
        smmu->ssid_bits = reg >> IDR1_SSID_SHIFT & IDR1_SSID_MASK;
        smmu->sid_bits = reg >> IDR1_SID_SHIFT & IDR1_SID_MASK;
 
+       /*
+        * If the SMMU supports fewer bits than would fill a single L2 stream
+        * table, use a linear table instead.
+        */
+       if (smmu->sid_bits <= STRTAB_SPLIT)
+               smmu->features &= ~ARM_SMMU_FEAT_2_LVL_STRTAB;
+
        /* IDR5 */
        reg = readl_relaxed(smmu->base + ARM_SMMU_IDR5);
 
@@ -2562,21 +2583,62 @@ static int arm_smmu_device_probe(struct arm_smmu_device *smmu)
        return 0;
 }
 
-static int arm_smmu_device_dt_probe(struct platform_device *pdev)
+#ifdef CONFIG_ACPI
+static int arm_smmu_device_acpi_probe(struct platform_device *pdev,
+                                     struct arm_smmu_device *smmu)
+{
+       struct acpi_iort_smmu_v3 *iort_smmu;
+       struct device *dev = smmu->dev;
+       struct acpi_iort_node *node;
+
+       node = *(struct acpi_iort_node **)dev_get_platdata(dev);
+
+       /* Retrieve SMMUv3 specific data */
+       iort_smmu = (struct acpi_iort_smmu_v3 *)node->node_data;
+
+       if (iort_smmu->flags & ACPI_IORT_SMMU_V3_COHACC_OVERRIDE)
+               smmu->features |= ARM_SMMU_FEAT_COHERENCY;
+
+       return 0;
+}
+#else
+static inline int arm_smmu_device_acpi_probe(struct platform_device *pdev,
+                                            struct arm_smmu_device *smmu)
+{
+       return -ENODEV;
+}
+#endif
+
+static int arm_smmu_device_dt_probe(struct platform_device *pdev,
+                                   struct arm_smmu_device *smmu)
 {
-       int irq, ret;
-       struct resource *res;
-       struct arm_smmu_device *smmu;
        struct device *dev = &pdev->dev;
-       bool bypass = true;
        u32 cells;
+       int ret = -EINVAL;
 
        if (of_property_read_u32(dev->of_node, "#iommu-cells", &cells))
                dev_err(dev, "missing #iommu-cells property\n");
        else if (cells != 1)
                dev_err(dev, "invalid #iommu-cells value (%d)\n", cells);
        else
-               bypass = false;
+               ret = 0;
+
+       parse_driver_options(smmu);
+
+       if (of_dma_is_coherent(dev->of_node))
+               smmu->features |= ARM_SMMU_FEAT_COHERENCY;
+
+       return ret;
+}
+
+static int arm_smmu_device_probe(struct platform_device *pdev)
+{
+       int irq, ret;
+       struct resource *res;
+       resource_size_t ioaddr;
+       struct arm_smmu_device *smmu;
+       struct device *dev = &pdev->dev;
+       bool bypass;
 
        smmu = devm_kzalloc(dev, sizeof(*smmu), GFP_KERNEL);
        if (!smmu) {
@@ -2591,6 +2653,7 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev)
                dev_err(dev, "MMIO region too small (%pr)\n", res);
                return -EINVAL;
        }
+       ioaddr = res->start;
 
        smmu->base = devm_ioremap_resource(dev, res);
        if (IS_ERR(smmu->base))
@@ -2613,10 +2676,19 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev)
        if (irq > 0)
                smmu->gerr_irq = irq;
 
-       parse_driver_options(smmu);
+       if (dev->of_node) {
+               ret = arm_smmu_device_dt_probe(pdev, smmu);
+       } else {
+               ret = arm_smmu_device_acpi_probe(pdev, smmu);
+               if (ret == -ENODEV)
+                       return ret;
+       }
+
+       /* Set bypass mode according to firmware probing result */
+       bypass = !!ret;
 
        /* Probe the h/w */
-       ret = arm_smmu_device_probe(smmu);
+       ret = arm_smmu_device_hw_probe(smmu);
        if (ret)
                return ret;
 
@@ -2634,7 +2706,16 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev)
                return ret;
 
        /* And we're up. Go go go! */
-       of_iommu_set_ops(dev->of_node, &arm_smmu_ops);
+       ret = iommu_device_sysfs_add(&smmu->iommu, dev, NULL,
+                                    "smmu3.%pa", &ioaddr);
+       if (ret)
+               return ret;
+
+       iommu_device_set_ops(&smmu->iommu, &arm_smmu_ops);
+       iommu_device_set_fwnode(&smmu->iommu, dev->fwnode);
+
+       ret = iommu_device_register(&smmu->iommu);
+
 #ifdef CONFIG_PCI
        if (pci_bus_type.iommu_ops != &arm_smmu_ops) {
                pci_request_acs();
@@ -2677,7 +2758,7 @@ static struct platform_driver arm_smmu_driver = {
                .name           = "arm-smmu-v3",
                .of_match_table = of_match_ptr(arm_smmu_of_match),
        },
-       .probe  = arm_smmu_device_dt_probe,
+       .probe  = arm_smmu_device_probe,
        .remove = arm_smmu_device_remove,
 };
 
@@ -2715,6 +2796,17 @@ static int __init arm_smmu_of_init(struct device_node *np)
 }
 IOMMU_OF_DECLARE(arm_smmuv3, "arm,smmu-v3", arm_smmu_of_init);
 
+#ifdef CONFIG_ACPI
+static int __init acpi_smmu_v3_init(struct acpi_table_header *table)
+{
+       if (iort_node_match(ACPI_IORT_NODE_SMMU_V3))
+               return arm_smmu_init();
+
+       return 0;
+}
+IORT_ACPI_DECLARE(arm_smmu_v3, ACPI_SIG_IORT, acpi_smmu_v3_init);
+#endif
+
 MODULE_DESCRIPTION("IOMMU API for ARM architected SMMUv3 implementations");
 MODULE_AUTHOR("Will Deacon <will.deacon@arm.com>");
 MODULE_LICENSE("GPL v2");