drm/nouveau/platform: probe IOMMU if present
authorAlexandre Courbot <acourbot@nvidia.com>
Fri, 20 Feb 2015 09:23:03 +0000 (18:23 +0900)
committerBen Skeggs <bskeggs@redhat.com>
Tue, 14 Apr 2015 07:00:44 +0000 (17:00 +1000)
Tegra SoCs have an IOMMU that can be used to present non-contiguous
physical memory as contiguous to the GPU and maximize the use of large
pages in the GPU MMU, leading to performance gains. This patch adds
support for probing such a IOMMU if present and make its properties
available in the nouveau_platform_gpu structure so subsystems can take
advantage of it.

Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
Signed-off-by: Ben Skeggs <bskeggs@redhat.com>
drivers/gpu/drm/nouveau/nouveau_platform.c
drivers/gpu/drm/nouveau/nouveau_platform.h

index dc5900bf54ff2117667ce0aaea5b3ef3b8516e16..3691982452a9e6a2a5567540d3e0698b0e0d212d 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/of.h>
 #include <linux/reset.h>
 #include <linux/regulator/consumer.h>
+#include <linux/iommu.h>
 #include <soc/tegra/fuse.h>
 #include <soc/tegra/pmc.h>
 
@@ -91,6 +92,71 @@ static int nouveau_platform_power_down(struct nouveau_platform_gpu *gpu)
        return 0;
 }
 
+static void nouveau_platform_probe_iommu(struct device *dev,
+                                        struct nouveau_platform_gpu *gpu)
+{
+       int err;
+       unsigned long pgsize_bitmap;
+
+       mutex_init(&gpu->iommu.mutex);
+
+       if (iommu_present(&platform_bus_type)) {
+               gpu->iommu.domain = iommu_domain_alloc(&platform_bus_type);
+               if (IS_ERR(gpu->iommu.domain))
+                       goto error;
+
+               /*
+                * A IOMMU is only usable if it supports page sizes smaller
+                * or equal to the system's PAGE_SIZE, with a preference if
+                * both are equal.
+                */
+               pgsize_bitmap = gpu->iommu.domain->ops->pgsize_bitmap;
+               if (pgsize_bitmap & PAGE_SIZE) {
+                       gpu->iommu.pgshift = PAGE_SHIFT;
+               } else {
+                       gpu->iommu.pgshift = fls(pgsize_bitmap & ~PAGE_MASK);
+                       if (gpu->iommu.pgshift == 0) {
+                               dev_warn(dev, "unsupported IOMMU page size\n");
+                               goto free_domain;
+                       }
+                       gpu->iommu.pgshift -= 1;
+               }
+
+               err = iommu_attach_device(gpu->iommu.domain, dev);
+               if (err)
+                       goto free_domain;
+
+               err = nvkm_mm_init(&gpu->iommu._mm, 0,
+                                  (1ULL << 40) >> gpu->iommu.pgshift, 1);
+               if (err)
+                       goto detach_device;
+
+               gpu->iommu.mm = &gpu->iommu._mm;
+       }
+
+       return;
+
+detach_device:
+       iommu_detach_device(gpu->iommu.domain, dev);
+
+free_domain:
+       iommu_domain_free(gpu->iommu.domain);
+
+error:
+       gpu->iommu.domain = NULL;
+       gpu->iommu.pgshift = 0;
+       dev_err(dev, "cannot initialize IOMMU MM\n");
+}
+
+static void nouveau_platform_remove_iommu(struct device *dev,
+                                         struct nouveau_platform_gpu *gpu)
+{
+       if (gpu->iommu.domain) {
+               iommu_detach_device(gpu->iommu.domain, dev);
+               iommu_domain_free(gpu->iommu.domain);
+       }
+}
+
 static int nouveau_platform_probe(struct platform_device *pdev)
 {
        struct nouveau_platform_gpu *gpu;
@@ -118,6 +184,8 @@ static int nouveau_platform_probe(struct platform_device *pdev)
        if (IS_ERR(gpu->clk_pwr))
                return PTR_ERR(gpu->clk_pwr);
 
+       nouveau_platform_probe_iommu(&pdev->dev, gpu);
+
        err = nouveau_platform_power_up(gpu);
        if (err)
                return err;
@@ -154,10 +222,15 @@ static int nouveau_platform_remove(struct platform_device *pdev)
        struct nouveau_drm *drm = nouveau_drm(drm_dev);
        struct nvkm_device *device = nvxx_device(&drm->device);
        struct nouveau_platform_gpu *gpu = nv_device_to_platform(device)->gpu;
+       int err;
 
        nouveau_drm_device_remove(drm_dev);
 
-       return nouveau_platform_power_down(gpu);
+       err = nouveau_platform_power_down(gpu);
+
+       nouveau_platform_remove_iommu(&pdev->dev, gpu);
+
+       return err;
 }
 
 #if IS_ENABLED(CONFIG_OF)
index 268bb72136815a2cc4bb6c9da6bf5b6a4e5d9e7e..392874cf47257b47f8e265a166851db1d25e6edd 100644 (file)
 #define __NOUVEAU_PLATFORM_H__
 
 #include "core/device.h"
+#include "core/mm.h"
 
 struct reset_control;
 struct clk;
 struct regulator;
+struct iommu_domain;
 struct platform_driver;
 
 struct nouveau_platform_gpu {
@@ -36,6 +38,22 @@ struct nouveau_platform_gpu {
        struct clk *clk_pwr;
 
        struct regulator *vdd;
+
+       struct {
+               /*
+                * Protects accesses to mm from subsystems
+                */
+               struct mutex mutex;
+
+               struct nvkm_mm _mm;
+               /*
+                * Just points to _mm. We need this to avoid embedding
+                * struct nvkm_mm in os.h
+                */
+               struct nvkm_mm *mm;
+               struct iommu_domain *domain;
+               unsigned long pgshift;
+       } iommu;
 };
 
 struct nouveau_platform_device {