diff options
Diffstat (limited to 'drivers/gpu/drm/tegra/drm.c')
-rw-r--r-- | drivers/gpu/drm/tegra/drm.c | 283 |
1 files changed, 228 insertions, 55 deletions
diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index dba4e090d3df..9a1e34e48f64 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -1,13 +1,15 @@ /* * Copyright (C) 2012 Avionic Design GmbH - * Copyright (C) 2012-2013 NVIDIA CORPORATION. All rights reserved. + * Copyright (C) 2012-2016 NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ +#include <linux/bitops.h> #include <linux/host1x.h> +#include <linux/idr.h> #include <linux/iommu.h> #include <drm/drm_atomic.h> @@ -23,8 +25,11 @@ #define DRIVER_MINOR 0 #define DRIVER_PATCHLEVEL 0 +#define CARVEOUT_SZ SZ_64M + struct tegra_drm_file { - struct list_head contexts; + struct idr contexts; + struct mutex lock; }; static void tegra_atomic_schedule(struct tegra_drm *tegra, @@ -126,8 +131,9 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) return -ENOMEM; if (iommu_present(&platform_bus_type)) { + u64 carveout_start, carveout_end, gem_start, gem_end; struct iommu_domain_geometry *geometry; - u64 start, end; + unsigned long order; tegra->domain = iommu_domain_alloc(&platform_bus_type); if (!tegra->domain) { @@ -136,12 +142,26 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) } geometry = &tegra->domain->geometry; - start = geometry->aperture_start; - end = geometry->aperture_end; - - DRM_DEBUG_DRIVER("IOMMU aperture initialized (%#llx-%#llx)\n", - start, end); - drm_mm_init(&tegra->mm, start, end - start + 1); + gem_start = geometry->aperture_start; + gem_end = geometry->aperture_end - CARVEOUT_SZ; + carveout_start = gem_end + 1; + carveout_end = geometry->aperture_end; + + order = __ffs(tegra->domain->pgsize_bitmap); + init_iova_domain(&tegra->carveout.domain, 1UL << order, + carveout_start >> order, + carveout_end >> order); + + tegra->carveout.shift = iova_shift(&tegra->carveout.domain); + tegra->carveout.limit = carveout_end >> tegra->carveout.shift; + + drm_mm_init(&tegra->mm, gem_start, gem_end - gem_start + 1); + mutex_init(&tegra->mm_lock); + + DRM_DEBUG("IOMMU apertures:\n"); + DRM_DEBUG(" GEM: %#llx-%#llx\n", gem_start, gem_end); + DRM_DEBUG(" Carveout: %#llx-%#llx\n", carveout_start, + carveout_end); } mutex_init(&tegra->clients_lock); @@ -161,6 +181,8 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) drm->mode_config.max_width = 4096; drm->mode_config.max_height = 4096; + drm->mode_config.allow_fb_modifiers = true; + drm->mode_config.funcs = &tegra_drm_mode_funcs; err = tegra_drm_fb_prepare(drm); @@ -208,6 +230,8 @@ config: if (tegra->domain) { iommu_domain_free(tegra->domain); drm_mm_takedown(&tegra->mm); + mutex_destroy(&tegra->mm_lock); + put_iova_domain(&tegra->carveout.domain); } free: kfree(tegra); @@ -232,6 +256,8 @@ static void tegra_drm_unload(struct drm_device *drm) if (tegra->domain) { iommu_domain_free(tegra->domain); drm_mm_takedown(&tegra->mm); + mutex_destroy(&tegra->mm_lock); + put_iova_domain(&tegra->carveout.domain); } kfree(tegra); @@ -245,7 +271,8 @@ static int tegra_drm_open(struct drm_device *drm, struct drm_file *filp) if (!fpriv) return -ENOMEM; - INIT_LIST_HEAD(&fpriv->contexts); + idr_init(&fpriv->contexts); + mutex_init(&fpriv->lock); filp->driver_priv = fpriv; return 0; @@ -424,21 +451,16 @@ fail: #ifdef CONFIG_DRM_TEGRA_STAGING -static struct tegra_drm_context *tegra_drm_get_context(__u64 context) -{ - return (struct tegra_drm_context *)(uintptr_t)context; -} - -static bool tegra_drm_file_owns_context(struct tegra_drm_file *file, - struct tegra_drm_context *context) +static struct tegra_drm_context * +tegra_drm_file_get_context(struct tegra_drm_file *file, u32 id) { - struct tegra_drm_context *ctx; + struct tegra_drm_context *context; - list_for_each_entry(ctx, &file->contexts, list) - if (ctx == context) - return true; + mutex_lock(&file->lock); + context = idr_find(&file->contexts, id); + mutex_unlock(&file->lock); - return false; + return context; } static int tegra_gem_create(struct drm_device *drm, void *data, @@ -519,6 +541,28 @@ static int tegra_syncpt_wait(struct drm_device *drm, void *data, &args->value); } +static int tegra_client_open(struct tegra_drm_file *fpriv, + struct tegra_drm_client *client, + struct tegra_drm_context *context) +{ + int err; + + err = client->ops->open_channel(client, context); + if (err < 0) + return err; + + err = idr_alloc(&fpriv->contexts, context, 0, 0, GFP_KERNEL); + if (err < 0) { + client->ops->close_channel(context); + return err; + } + + context->client = client; + context->id = err; + + return 0; +} + static int tegra_open_channel(struct drm_device *drm, void *data, struct drm_file *file) { @@ -533,19 +577,22 @@ static int tegra_open_channel(struct drm_device *drm, void *data, if (!context) return -ENOMEM; + mutex_lock(&fpriv->lock); + list_for_each_entry(client, &tegra->clients, list) if (client->base.class == args->client) { - err = client->ops->open_channel(client, context); - if (err) + err = tegra_client_open(fpriv, client, context); + if (err < 0) break; - list_add(&context->list, &fpriv->contexts); - args->context = (uintptr_t)context; - context->client = client; - return 0; + args->context = context->id; + break; } - kfree(context); + if (err < 0) + kfree(context); + + mutex_unlock(&fpriv->lock); return err; } @@ -555,16 +602,22 @@ static int tegra_close_channel(struct drm_device *drm, void *data, struct tegra_drm_file *fpriv = file->driver_priv; struct drm_tegra_close_channel *args = data; struct tegra_drm_context *context; + int err = 0; - context = tegra_drm_get_context(args->context); + mutex_lock(&fpriv->lock); - if (!tegra_drm_file_owns_context(fpriv, context)) - return -EINVAL; + context = tegra_drm_file_get_context(fpriv, args->context); + if (!context) { + err = -EINVAL; + goto unlock; + } - list_del(&context->list); + idr_remove(&fpriv->contexts, context->id); tegra_drm_context_free(context); - return 0; +unlock: + mutex_unlock(&fpriv->lock); + return err; } static int tegra_get_syncpt(struct drm_device *drm, void *data, @@ -574,19 +627,27 @@ static int tegra_get_syncpt(struct drm_device *drm, void *data, struct drm_tegra_get_syncpt *args = data; struct tegra_drm_context *context; struct host1x_syncpt *syncpt; + int err = 0; - context = tegra_drm_get_context(args->context); + mutex_lock(&fpriv->lock); - if (!tegra_drm_file_owns_context(fpriv, context)) - return -ENODEV; + context = tegra_drm_file_get_context(fpriv, args->context); + if (!context) { + err = -ENODEV; + goto unlock; + } - if (args->index >= context->client->base.num_syncpts) - return -EINVAL; + if (args->index >= context->client->base.num_syncpts) { + err = -EINVAL; + goto unlock; + } syncpt = context->client->base.syncpts[args->index]; args->id = host1x_syncpt_id(syncpt); - return 0; +unlock: + mutex_unlock(&fpriv->lock); + return err; } static int tegra_submit(struct drm_device *drm, void *data, @@ -595,13 +656,21 @@ static int tegra_submit(struct drm_device *drm, void *data, struct tegra_drm_file *fpriv = file->driver_priv; struct drm_tegra_submit *args = data; struct tegra_drm_context *context; + int err; - context = tegra_drm_get_context(args->context); + mutex_lock(&fpriv->lock); + + context = tegra_drm_file_get_context(fpriv, args->context); + if (!context) { + err = -ENODEV; + goto unlock; + } - if (!tegra_drm_file_owns_context(fpriv, context)) - return -ENODEV; + err = context->client->ops->submit(context, args, drm, file); - return context->client->ops->submit(context, args, drm, file); +unlock: + mutex_unlock(&fpriv->lock); + return err; } static int tegra_get_syncpt_base(struct drm_device *drm, void *data, @@ -612,24 +681,34 @@ static int tegra_get_syncpt_base(struct drm_device *drm, void *data, struct tegra_drm_context *context; struct host1x_syncpt_base *base; struct host1x_syncpt *syncpt; + int err = 0; - context = tegra_drm_get_context(args->context); + mutex_lock(&fpriv->lock); - if (!tegra_drm_file_owns_context(fpriv, context)) - return -ENODEV; + context = tegra_drm_file_get_context(fpriv, args->context); + if (!context) { + err = -ENODEV; + goto unlock; + } - if (args->syncpt >= context->client->base.num_syncpts) - return -EINVAL; + if (args->syncpt >= context->client->base.num_syncpts) { + err = -EINVAL; + goto unlock; + } syncpt = context->client->base.syncpts[args->syncpt]; base = host1x_syncpt_get_base(syncpt); - if (!base) - return -ENXIO; + if (!base) { + err = -ENXIO; + goto unlock; + } args->id = host1x_syncpt_base_id(base); - return 0; +unlock: + mutex_unlock(&fpriv->lock); + return err; } static int tegra_gem_set_tiling(struct drm_device *drm, void *data, @@ -804,14 +883,25 @@ static const struct file_operations tegra_drm_fops = { .llseek = noop_llseek, }; +static int tegra_drm_context_cleanup(int id, void *p, void *data) +{ + struct tegra_drm_context *context = p; + + tegra_drm_context_free(context); + + return 0; +} + static void tegra_drm_preclose(struct drm_device *drm, struct drm_file *file) { struct tegra_drm_file *fpriv = file->driver_priv; - struct tegra_drm_context *context, *tmp; - list_for_each_entry_safe(context, tmp, &fpriv->contexts, list) - tegra_drm_context_free(context); + mutex_lock(&fpriv->lock); + idr_for_each(&fpriv->contexts, tegra_drm_context_cleanup, NULL); + mutex_unlock(&fpriv->lock); + idr_destroy(&fpriv->contexts); + mutex_destroy(&fpriv->lock); kfree(fpriv); } @@ -844,7 +934,9 @@ static int tegra_debugfs_iova(struct seq_file *s, void *data) struct tegra_drm *tegra = drm->dev_private; struct drm_printer p = drm_seq_file_printer(s); + mutex_lock(&tegra->mm_lock); drm_mm_print(&tegra->mm, &p); + mutex_unlock(&tegra->mm_lock); return 0; } @@ -919,6 +1011,84 @@ int tegra_drm_unregister_client(struct tegra_drm *tegra, return 0; } +void *tegra_drm_alloc(struct tegra_drm *tegra, size_t size, + dma_addr_t *dma) +{ + struct iova *alloc; + void *virt; + gfp_t gfp; + int err; + + if (tegra->domain) + size = iova_align(&tegra->carveout.domain, size); + else + size = PAGE_ALIGN(size); + + gfp = GFP_KERNEL | __GFP_ZERO; + if (!tegra->domain) { + /* + * Many units only support 32-bit addresses, even on 64-bit + * SoCs. If there is no IOMMU to translate into a 32-bit IO + * virtual address space, force allocations to be in the + * lower 32-bit range. + */ + gfp |= GFP_DMA; + } + + virt = (void *)__get_free_pages(gfp, get_order(size)); + if (!virt) + return ERR_PTR(-ENOMEM); + + if (!tegra->domain) { + /* + * If IOMMU is disabled, devices address physical memory + * directly. + */ + *dma = virt_to_phys(virt); + return virt; + } + + alloc = alloc_iova(&tegra->carveout.domain, + size >> tegra->carveout.shift, + tegra->carveout.limit, true); + if (!alloc) { + err = -EBUSY; + goto free_pages; + } + + *dma = iova_dma_addr(&tegra->carveout.domain, alloc); + err = iommu_map(tegra->domain, *dma, virt_to_phys(virt), + size, IOMMU_READ | IOMMU_WRITE); + if (err < 0) + goto free_iova; + + return virt; + +free_iova: + __free_iova(&tegra->carveout.domain, alloc); +free_pages: + free_pages((unsigned long)virt, get_order(size)); + + return ERR_PTR(err); +} + +void tegra_drm_free(struct tegra_drm *tegra, size_t size, void *virt, + dma_addr_t dma) +{ + if (tegra->domain) + size = iova_align(&tegra->carveout.domain, size); + else + size = PAGE_ALIGN(size); + + if (tegra->domain) { + iommu_unmap(tegra->domain, dma, size); + free_iova(&tegra->carveout.domain, + iova_pfn(&tegra->carveout.domain, dma)); + } + + free_pages((unsigned long)virt, get_order(size)); +} + static int host1x_drm_probe(struct host1x_device *dev) { struct drm_driver *driver = &tegra_drm_driver; @@ -1003,11 +1173,13 @@ static const struct of_device_id host1x_drm_subdevs[] = { { .compatible = "nvidia,tegra124-sor", }, { .compatible = "nvidia,tegra124-hdmi", }, { .compatible = "nvidia,tegra124-dsi", }, + { .compatible = "nvidia,tegra124-vic", }, { .compatible = "nvidia,tegra132-dsi", }, { .compatible = "nvidia,tegra210-dc", }, { .compatible = "nvidia,tegra210-dsi", }, { .compatible = "nvidia,tegra210-sor", }, { .compatible = "nvidia,tegra210-sor1", }, + { .compatible = "nvidia,tegra210-vic", }, { /* sentinel */ } }; @@ -1029,6 +1201,7 @@ static struct platform_driver * const drivers[] = { &tegra_sor_driver, &tegra_gr2d_driver, &tegra_gr3d_driver, + &tegra_vic_driver, }; static int __init host1x_drm_init(void) |