Merge branch 'iommu/next' of git://linuxtv.org/pinchartl/fbdev into arm/renesas
authorJoerg Roedel <jroedel@suse.de>
Mon, 19 Jan 2015 13:39:45 +0000 (14:39 +0100)
committerJoerg Roedel <jroedel@suse.de>
Mon, 19 Jan 2015 13:39:45 +0000 (14:39 +0100)
Documentation/devicetree/bindings/iommu/renesas,ipmmu-vmsa.txt [new file with mode: 0644]
drivers/iommu/ipmmu-vmsa.c
include/linux/platform_data/ipmmu-vmsa.h [deleted file]

diff --git a/Documentation/devicetree/bindings/iommu/renesas,ipmmu-vmsa.txt b/Documentation/devicetree/bindings/iommu/renesas,ipmmu-vmsa.txt
new file mode 100644 (file)
index 0000000..cd29083
--- /dev/null
@@ -0,0 +1,41 @@
+* Renesas VMSA-Compatible IOMMU
+
+The IPMMU is an IOMMU implementation compatible with the ARM VMSA page tables.
+It provides address translation for bus masters outside of the CPU, each
+connected to the IPMMU through a port called micro-TLB.
+
+
+Required Properties:
+
+  - compatible: Must contain "renesas,ipmmu-vmsa".
+  - reg: Base address and size of the IPMMU registers.
+  - interrupts: Specifiers for the MMU fault interrupts. For instances that
+    support secure mode two interrupts must be specified, for non-secure and
+    secure mode, in that order. For instances that don't support secure mode a
+    single interrupt must be specified.
+
+  - #iommu-cells: Must be 1.
+
+Each bus master connected to an IPMMU must reference the IPMMU in its device
+node with the following property:
+
+  - iommus: A reference to the IPMMU in two cells. The first cell is a phandle
+    to the IPMMU and the second cell the number of the micro-TLB that the
+    device is connected to.
+
+
+Example: R8A7791 IPMMU-MX and VSP1-D0 bus master
+
+       ipmmu_mx: mmu@fe951000 {
+               compatible = "renasas,ipmmu-vmsa";
+               reg = <0 0xfe951000 0 0x1000>;
+               interrupts = <0 222 IRQ_TYPE_LEVEL_HIGH>,
+                            <0 221 IRQ_TYPE_LEVEL_HIGH>;
+               #iommu-cells = <1>;
+       };
+
+       vsp1@fe928000 {
+               ...
+               iommus = <&ipmmu_mx 13>;
+               ...
+       };
index 748693192c20a0dd862f799ace8d20450bc436fe..791c3daec7c0c848d86bb930ace21bb546fef4bd 100644 (file)
@@ -16,7 +16,7 @@
 #include <linux/io.h>
 #include <linux/iommu.h>
 #include <linux/module.h>
-#include <linux/platform_data/ipmmu-vmsa.h>
+#include <linux/of.h>
 #include <linux/platform_device.h>
 #include <linux/sizes.h>
 #include <linux/slab.h>
@@ -29,7 +29,6 @@ struct ipmmu_vmsa_device {
        void __iomem *base;
        struct list_head list;
 
-       const struct ipmmu_vmsa_platform_data *pdata;
        unsigned int num_utlbs;
 
        struct dma_iommu_mapping *mapping;
@@ -46,7 +45,8 @@ struct ipmmu_vmsa_domain {
 
 struct ipmmu_vmsa_archdata {
        struct ipmmu_vmsa_device *mmu;
-       unsigned int utlb;
+       unsigned int *utlbs;
+       unsigned int num_utlbs;
 };
 
 static DEFINE_SPINLOCK(ipmmu_devices_lock);
@@ -58,6 +58,8 @@ static LIST_HEAD(ipmmu_devices);
  * Registers Definition
  */
 
+#define IM_NS_ALIAS_OFFSET             0x800
+
 #define IM_CTX_SIZE                    0x40
 
 #define IMCTR                          0x0000
@@ -678,30 +680,33 @@ done:
 
 static void ipmmu_clear_pud(struct ipmmu_vmsa_device *mmu, pud_t *pud)
 {
-       /* Free the page table. */
        pgtable_t table = pud_pgtable(*pud);
-       __free_page(table);
 
        /* Clear the PUD. */
        *pud = __pud(0);
        ipmmu_flush_pgtable(mmu, pud, sizeof(*pud));
+
+       /* Free the page table. */
+       __free_page(table);
 }
 
 static void ipmmu_clear_pmd(struct ipmmu_vmsa_device *mmu, pud_t *pud,
                            pmd_t *pmd)
 {
+       pmd_t pmdval = *pmd;
        unsigned int i;
 
-       /* Free the page table. */
-       if (pmd_table(*pmd)) {
-               pgtable_t table = pmd_pgtable(*pmd);
-               __free_page(table);
-       }
-
        /* Clear the PMD. */
        *pmd = __pmd(0);
        ipmmu_flush_pgtable(mmu, pmd, sizeof(*pmd));
 
+       /* Free the page table. */
+       if (pmd_table(pmdval)) {
+               pgtable_t table = pmd_pgtable(pmdval);
+
+               __free_page(table);
+       }
+
        /* Check whether the PUD is still needed. */
        pmd = pmd_offset(pud, 0);
        for (i = 0; i < IPMMU_PTRS_PER_PMD; ++i) {
@@ -782,7 +787,6 @@ static int ipmmu_clear_mapping(struct ipmmu_vmsa_domain *domain,
        pud_t *pud;
        pmd_t *pmd;
        pte_t *pte;
-       int ret = 0;
 
        if (!pgd)
                return -EINVAL;
@@ -844,8 +848,7 @@ static int ipmmu_clear_mapping(struct ipmmu_vmsa_domain *domain,
 done:
        spin_unlock_irqrestore(&domain->lock, flags);
 
-       if (ret)
-               ipmmu_tlb_invalidate(domain);
+       ipmmu_tlb_invalidate(domain);
 
        return 0;
 }
@@ -896,6 +899,7 @@ static int ipmmu_attach_device(struct iommu_domain *io_domain,
        struct ipmmu_vmsa_device *mmu = archdata->mmu;
        struct ipmmu_vmsa_domain *domain = io_domain->priv;
        unsigned long flags;
+       unsigned int i;
        int ret = 0;
 
        if (!mmu) {
@@ -924,7 +928,8 @@ static int ipmmu_attach_device(struct iommu_domain *io_domain,
        if (ret < 0)
                return ret;
 
-       ipmmu_utlb_enable(domain, archdata->utlb);
+       for (i = 0; i < archdata->num_utlbs; ++i)
+               ipmmu_utlb_enable(domain, archdata->utlbs[i]);
 
        return 0;
 }
@@ -934,8 +939,10 @@ static void ipmmu_detach_device(struct iommu_domain *io_domain,
 {
        struct ipmmu_vmsa_archdata *archdata = dev->archdata.iommu;
        struct ipmmu_vmsa_domain *domain = io_domain->priv;
+       unsigned int i;
 
-       ipmmu_utlb_disable(domain, archdata->utlb);
+       for (i = 0; i < archdata->num_utlbs; ++i)
+               ipmmu_utlb_disable(domain, archdata->utlbs[i]);
 
        /*
         * TODO: Optimize by disabling the context when no device is attached.
@@ -999,26 +1006,56 @@ static phys_addr_t ipmmu_iova_to_phys(struct iommu_domain *io_domain,
        return __pfn_to_phys(pte_pfn(pte)) | (iova & ~PAGE_MASK);
 }
 
-static int ipmmu_find_utlb(struct ipmmu_vmsa_device *mmu, struct device *dev)
+static int ipmmu_find_utlbs(struct ipmmu_vmsa_device *mmu, struct device *dev,
+                           unsigned int **_utlbs)
 {
-       const struct ipmmu_vmsa_master *master = mmu->pdata->masters;
-       const char *devname = dev_name(dev);
+       unsigned int *utlbs;
        unsigned int i;
+       int count;
+
+       count = of_count_phandle_with_args(dev->of_node, "iommus",
+                                          "#iommu-cells");
+       if (count < 0)
+               return -EINVAL;
+
+       utlbs = kcalloc(count, sizeof(*utlbs), GFP_KERNEL);
+       if (!utlbs)
+               return -ENOMEM;
+
+       for (i = 0; i < count; ++i) {
+               struct of_phandle_args args;
+               int ret;
+
+               ret = of_parse_phandle_with_args(dev->of_node, "iommus",
+                                                "#iommu-cells", i, &args);
+               if (ret < 0)
+                       goto error;
+
+               of_node_put(args.np);
+
+               if (args.np != mmu->dev->of_node || args.args_count != 1)
+                       goto error;
 
-       for (i = 0; i < mmu->pdata->num_masters; ++i, ++master) {
-               if (strcmp(master->name, devname) == 0)
-                       return master->utlb;
+               utlbs[i] = args.args[0];
        }
 
-       return -1;
+       *_utlbs = utlbs;
+
+       return count;
+
+error:
+       kfree(utlbs);
+       return -EINVAL;
 }
 
 static int ipmmu_add_device(struct device *dev)
 {
        struct ipmmu_vmsa_archdata *archdata;
        struct ipmmu_vmsa_device *mmu;
-       struct iommu_group *group;
-       int utlb = -1;
+       struct iommu_group *group = NULL;
+       unsigned int *utlbs = NULL;
+       unsigned int i;
+       int num_utlbs = 0;
        int ret;
 
        if (dev->archdata.iommu) {
@@ -1031,8 +1068,8 @@ static int ipmmu_add_device(struct device *dev)
        spin_lock(&ipmmu_devices_lock);
 
        list_for_each_entry(mmu, &ipmmu_devices, list) {
-               utlb = ipmmu_find_utlb(mmu, dev);
-               if (utlb >= 0) {
+               num_utlbs = ipmmu_find_utlbs(mmu, dev, &utlbs);
+               if (num_utlbs) {
                        /*
                         * TODO Take a reference to the MMU to protect
                         * against device removal.
@@ -1043,17 +1080,22 @@ static int ipmmu_add_device(struct device *dev)
 
        spin_unlock(&ipmmu_devices_lock);
 
-       if (utlb < 0)
+       if (num_utlbs <= 0)
                return -ENODEV;
 
-       if (utlb >= mmu->num_utlbs)
-               return -EINVAL;
+       for (i = 0; i < num_utlbs; ++i) {
+               if (utlbs[i] >= mmu->num_utlbs) {
+                       ret = -EINVAL;
+                       goto error;
+               }
+       }
 
        /* Create a device group and add the device to it. */
        group = iommu_group_alloc();
        if (IS_ERR(group)) {
                dev_err(dev, "Failed to allocate IOMMU group\n");
-               return PTR_ERR(group);
+               ret = PTR_ERR(group);
+               goto error;
        }
 
        ret = iommu_group_add_device(group, dev);
@@ -1061,7 +1103,8 @@ static int ipmmu_add_device(struct device *dev)
 
        if (ret < 0) {
                dev_err(dev, "Failed to add device to IPMMU group\n");
-               return ret;
+               group = NULL;
+               goto error;
        }
 
        archdata = kzalloc(sizeof(*archdata), GFP_KERNEL);
@@ -1071,7 +1114,8 @@ static int ipmmu_add_device(struct device *dev)
        }
 
        archdata->mmu = mmu;
-       archdata->utlb = utlb;
+       archdata->utlbs = utlbs;
+       archdata->num_utlbs = num_utlbs;
        dev->archdata.iommu = archdata;
 
        /*
@@ -1090,7 +1134,8 @@ static int ipmmu_add_device(struct device *dev)
                                                   SZ_1G, SZ_2G);
                if (IS_ERR(mapping)) {
                        dev_err(mmu->dev, "failed to create ARM IOMMU mapping\n");
-                       return PTR_ERR(mapping);
+                       ret = PTR_ERR(mapping);
+                       goto error;
                }
 
                mmu->mapping = mapping;
@@ -1106,17 +1151,29 @@ static int ipmmu_add_device(struct device *dev)
        return 0;
 
 error:
+       arm_iommu_release_mapping(mmu->mapping);
+
        kfree(dev->archdata.iommu);
+       kfree(utlbs);
+
        dev->archdata.iommu = NULL;
-       iommu_group_remove_device(dev);
+
+       if (!IS_ERR_OR_NULL(group))
+               iommu_group_remove_device(dev);
+
        return ret;
 }
 
 static void ipmmu_remove_device(struct device *dev)
 {
+       struct ipmmu_vmsa_archdata *archdata = dev->archdata.iommu;
+
        arm_iommu_detach_device(dev);
        iommu_group_remove_device(dev);
-       kfree(dev->archdata.iommu);
+
+       kfree(archdata->utlbs);
+       kfree(archdata);
+
        dev->archdata.iommu = NULL;
 }
 
@@ -1154,7 +1211,7 @@ static int ipmmu_probe(struct platform_device *pdev)
        int irq;
        int ret;
 
-       if (!pdev->dev.platform_data) {
+       if (!IS_ENABLED(CONFIG_OF) && !pdev->dev.platform_data) {
                dev_err(&pdev->dev, "missing platform data\n");
                return -EINVAL;
        }
@@ -1166,7 +1223,6 @@ static int ipmmu_probe(struct platform_device *pdev)
        }
 
        mmu->dev = &pdev->dev;
-       mmu->pdata = pdev->dev.platform_data;
        mmu->num_utlbs = 32;
 
        /* Map I/O memory and request IRQ. */
@@ -1175,6 +1231,20 @@ static int ipmmu_probe(struct platform_device *pdev)
        if (IS_ERR(mmu->base))
                return PTR_ERR(mmu->base);
 
+       /*
+        * The IPMMU has two register banks, for secure and non-secure modes.
+        * The bank mapped at the beginning of the IPMMU address space
+        * corresponds to the running mode of the CPU. When running in secure
+        * mode the non-secure register bank is also available at an offset.
+        *
+        * Secure mode operation isn't clearly documented and is thus currently
+        * not implemented in the driver. Furthermore, preliminary tests of
+        * non-secure operation with the main register bank were not successful.
+        * Offset the registers base unconditionally to point to the non-secure
+        * alias space for now.
+        */
+       mmu->base += IM_NS_ALIAS_OFFSET;
+
        irq = platform_get_irq(pdev, 0);
        if (irq < 0) {
                dev_err(&pdev->dev, "no IRQ found\n");
@@ -1220,9 +1290,14 @@ static int ipmmu_remove(struct platform_device *pdev)
        return 0;
 }
 
+static const struct of_device_id ipmmu_of_ids[] = {
+       { .compatible = "renesas,ipmmu-vmsa", },
+};
+
 static struct platform_driver ipmmu_driver = {
        .driver = {
                .name = "ipmmu-vmsa",
+               .of_match_table = of_match_ptr(ipmmu_of_ids),
        },
        .probe = ipmmu_probe,
        .remove = ipmmu_remove,
diff --git a/include/linux/platform_data/ipmmu-vmsa.h b/include/linux/platform_data/ipmmu-vmsa.h
deleted file mode 100644 (file)
index 5275b3a..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * IPMMU VMSA Platform Data
- *
- * Copyright (C) 2014 Renesas Electronics Corporation
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- */
-
-#ifndef __IPMMU_VMSA_H__
-#define __IPMMU_VMSA_H__
-
-struct ipmmu_vmsa_master {
-       const char *name;
-       unsigned int utlb;
-};
-
-struct ipmmu_vmsa_platform_data {
-       const struct ipmmu_vmsa_master *masters;
-       unsigned int num_masters;
-};
-
-#endif /* __IPMMU_VMSA_H__ */
This page took 0.031568 seconds and 5 git commands to generate.