Skip to content

Commit

Permalink
drm/tegra: Add IOMMU support
Browse files Browse the repository at this point in the history
When an IOMMU device is available on the platform bus, allocate an IOMMU
domain and attach the display controllers to it. The display controllers
can then scan out non-contiguous buffers by mapping them through the
IOMMU.

Signed-off-by: Thierry Reding <treding@nvidia.com>
  • Loading branch information
Thierry Reding committed Nov 13, 2014
1 parent 1d1e6fe commit df06b75
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 34 deletions.
17 changes: 17 additions & 0 deletions drivers/gpu/drm/tegra/dc.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/iommu.h>
#include <linux/reset.h>

#include <soc/tegra/pmc.h>
Expand Down Expand Up @@ -1290,6 +1291,17 @@ static int tegra_dc_init(struct host1x_client *client)
struct tegra_drm *tegra = drm->dev_private;
int err;

if (tegra->domain) {
err = iommu_attach_device(tegra->domain, dc->dev);
if (err < 0) {
dev_err(dc->dev, "failed to attach to domain: %d\n",
err);
return err;
}

dc->domain = tegra->domain;
}

drm_crtc_init(drm, &dc->base, &tegra_crtc_funcs);
drm_mode_crtc_set_gamma_size(&dc->base, 256);
drm_crtc_helper_add(&dc->base, &tegra_crtc_helper_funcs);
Expand Down Expand Up @@ -1347,6 +1359,11 @@ static int tegra_dc_exit(struct host1x_client *client)
return err;
}

if (dc->domain) {
iommu_detach_device(dc->domain, dc->dev);
dc->domain = NULL;
}

return 0;
}

Expand Down
24 changes: 24 additions & 0 deletions drivers/gpu/drm/tegra/drm.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

#include <linux/host1x.h>
#include <linux/iommu.h>

#include "drm.h"
#include "gem.h"
Expand All @@ -33,6 +34,17 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
if (!tegra)
return -ENOMEM;

if (iommu_present(&platform_bus_type)) {
tegra->domain = iommu_domain_alloc(&platform_bus_type);
if (IS_ERR(tegra->domain)) {
err = PTR_ERR(tegra->domain);
goto free;
}

DRM_DEBUG("IOMMU context initialized\n");
drm_mm_init(&tegra->mm, 0, SZ_2G);
}

mutex_init(&tegra->clients_lock);
INIT_LIST_HEAD(&tegra->clients);
drm->dev_private = tegra;
Expand Down Expand Up @@ -76,13 +88,20 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
tegra_drm_fb_free(drm);
config:
drm_mode_config_cleanup(drm);

if (tegra->domain) {
iommu_domain_free(tegra->domain);
drm_mm_takedown(&tegra->mm);
}
free:
kfree(tegra);
return err;
}

static int tegra_drm_unload(struct drm_device *drm)
{
struct host1x_device *device = to_host1x_device(drm->dev);
struct tegra_drm *tegra = drm->dev_private;
int err;

drm_kms_helper_poll_fini(drm);
Expand All @@ -94,6 +113,11 @@ static int tegra_drm_unload(struct drm_device *drm)
if (err < 0)
return err;

if (tegra->domain) {
iommu_domain_free(tegra->domain);
drm_mm_takedown(&tegra->mm);
}

return 0;
}

Expand Down
5 changes: 5 additions & 0 deletions drivers/gpu/drm/tegra/drm.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ struct tegra_fbdev {
struct tegra_drm {
struct drm_device *drm;

struct iommu_domain *domain;
struct drm_mm mm;

struct mutex clients_lock;
struct list_head clients;

Expand Down Expand Up @@ -121,6 +124,8 @@ struct tegra_dc {
struct drm_pending_vblank_event *event;

const struct tegra_dc_soc_info *soc;

struct iommu_domain *domain;
};

static inline struct tegra_dc *
Expand Down
16 changes: 15 additions & 1 deletion drivers/gpu/drm/tegra/fb.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,12 @@ static void tegra_fb_destroy(struct drm_framebuffer *framebuffer)
for (i = 0; i < fb->num_planes; i++) {
struct tegra_bo *bo = fb->planes[i];

if (bo)
if (bo) {
if (bo->pages && bo->vaddr)
vunmap(bo->vaddr);

drm_gem_object_unreference_unlocked(&bo->gem);
}
}

drm_framebuffer_cleanup(framebuffer);
Expand Down Expand Up @@ -254,6 +258,16 @@ static int tegra_fbdev_probe(struct drm_fb_helper *helper,
offset = info->var.xoffset * bytes_per_pixel +
info->var.yoffset * fb->pitches[0];

if (bo->pages) {
bo->vaddr = vmap(bo->pages, bo->num_pages, VM_MAP,
pgprot_writecombine(PAGE_KERNEL));
if (!bo->vaddr) {
dev_err(drm->dev, "failed to vmap() framebuffer\n");
err = -ENOMEM;
goto destroy;
}
}

drm->mode_config.fb_base = (resource_size_t)bo->paddr;
info->screen_base = (void __iomem *)bo->vaddr + offset;
info->screen_size = size;
Expand Down
Loading

0 comments on commit df06b75

Please sign in to comment.