drm/tegra: Add PRIME support
authorThierry Reding <treding@nvidia.com>
Thu, 12 Dec 2013 09:00:43 +0000 (10:00 +0100)
committerThierry Reding <treding@nvidia.com>
Fri, 20 Dec 2013 14:56:07 +0000 (15:56 +0100)
Implement very basic PRIME support. This currently only works with
buffers that are contiguous in memory and will refuse to import any
physically non-contiguous buffers.

Signed-off-by: Thierry Reding <treding@nvidia.com>
drivers/gpu/drm/tegra/drm.c
drivers/gpu/drm/tegra/gem.c
drivers/gpu/drm/tegra/gem.h

index eec8d2e..88a5290 100644 (file)
@@ -580,7 +580,7 @@ static void tegra_debugfs_cleanup(struct drm_minor *minor)
 #endif
 
 static struct drm_driver tegra_drm_driver = {
-       .driver_features = DRIVER_MODESET | DRIVER_GEM,
+       .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME,
        .load = tegra_drm_load,
        .unload = tegra_drm_unload,
        .open = tegra_drm_open,
@@ -598,6 +598,12 @@ static struct drm_driver tegra_drm_driver = {
 
        .gem_free_object = tegra_bo_free_object,
        .gem_vm_ops = &tegra_bo_vm_ops,
+
+       .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+       .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+       .gem_prime_export = tegra_gem_prime_export,
+       .gem_prime_import = tegra_gem_prime_import,
+
        .dumb_create = tegra_bo_dumb_create,
        .dumb_map_offset = tegra_bo_dumb_map_offset,
        .dumb_destroy = drm_gem_dumb_destroy,
index 7741e33..ef853e5 100644 (file)
@@ -18,6 +18,7 @@
  * GNU General Public License for more details.
  */
 
+#include <linux/dma-buf.h>
 #include <drm/tegra_drm.h>
 
 #include "gem.h"
@@ -173,13 +174,87 @@ err:
        return ERR_PTR(ret);
 }
 
+struct tegra_bo *tegra_bo_import(struct drm_device *drm, struct dma_buf *buf)
+{
+       struct dma_buf_attachment *attach;
+       struct tegra_bo *bo;
+       ssize_t size;
+       int err;
+
+       bo = kzalloc(sizeof(*bo), GFP_KERNEL);
+       if (!bo)
+               return ERR_PTR(-ENOMEM);
+
+       host1x_bo_init(&bo->base, &tegra_bo_ops);
+       size = round_up(buf->size, PAGE_SIZE);
+
+       err = drm_gem_object_init(drm, &bo->gem, size);
+       if (err < 0)
+               goto free;
+
+       err = drm_gem_create_mmap_offset(&bo->gem);
+       if (err < 0)
+               goto release;
+
+       attach = dma_buf_attach(buf, drm->dev);
+       if (IS_ERR(attach)) {
+               err = PTR_ERR(attach);
+               goto free_mmap;
+       }
+
+       get_dma_buf(buf);
+
+       bo->sgt = dma_buf_map_attachment(attach, DMA_TO_DEVICE);
+       if (!bo->sgt) {
+               err = -ENOMEM;
+               goto detach;
+       }
+
+       if (IS_ERR(bo->sgt)) {
+               err = PTR_ERR(bo->sgt);
+               goto detach;
+       }
+
+       if (bo->sgt->nents > 1) {
+               err = -EINVAL;
+               goto detach;
+       }
+
+       bo->paddr = sg_dma_address(bo->sgt->sgl);
+       bo->gem.import_attach = attach;
+
+       return bo;
+
+detach:
+       if (!IS_ERR_OR_NULL(bo->sgt))
+               dma_buf_unmap_attachment(attach, bo->sgt, DMA_TO_DEVICE);
+
+       dma_buf_detach(buf, attach);
+       dma_buf_put(buf);
+free_mmap:
+       drm_gem_free_mmap_offset(&bo->gem);
+release:
+       drm_gem_object_release(&bo->gem);
+free:
+       kfree(bo);
+
+       return ERR_PTR(err);
+}
+
 void tegra_bo_free_object(struct drm_gem_object *gem)
 {
        struct tegra_bo *bo = to_tegra_bo(gem);
 
+       if (gem->import_attach) {
+               dma_buf_unmap_attachment(gem->import_attach, bo->sgt,
+                                        DMA_TO_DEVICE);
+               drm_prime_gem_destroy(gem, NULL);
+       } else {
+               tegra_bo_destroy(gem->dev, bo);
+       }
+
        drm_gem_free_mmap_offset(gem);
        drm_gem_object_release(gem);
-       tegra_bo_destroy(gem->dev, bo);
 
        kfree(bo);
 }
@@ -255,3 +330,106 @@ int tegra_drm_mmap(struct file *file, struct vm_area_struct *vma)
 
        return ret;
 }
+
+static struct sg_table *
+tegra_gem_prime_map_dma_buf(struct dma_buf_attachment *attach,
+                           enum dma_data_direction dir)
+{
+       struct drm_gem_object *gem = attach->dmabuf->priv;
+       struct tegra_bo *bo = to_tegra_bo(gem);
+       struct sg_table *sgt;
+
+       sgt = kmalloc(sizeof(*sgt), GFP_KERNEL);
+       if (!sgt)
+               return NULL;
+
+       if (sg_alloc_table(sgt, 1, GFP_KERNEL)) {
+               kfree(sgt);
+               return NULL;
+       }
+
+       sg_dma_address(sgt->sgl) = bo->paddr;
+       sg_dma_len(sgt->sgl) = gem->size;
+
+       return sgt;
+}
+
+static void tegra_gem_prime_unmap_dma_buf(struct dma_buf_attachment *attach,
+                                         struct sg_table *sgt,
+                                         enum dma_data_direction dir)
+{
+       sg_free_table(sgt);
+       kfree(sgt);
+}
+
+static void tegra_gem_prime_release(struct dma_buf *buf)
+{
+       drm_gem_dmabuf_release(buf);
+}
+
+static void *tegra_gem_prime_kmap_atomic(struct dma_buf *buf,
+                                        unsigned long page)
+{
+       return NULL;
+}
+
+static void tegra_gem_prime_kunmap_atomic(struct dma_buf *buf,
+                                         unsigned long page,
+                                         void *addr)
+{
+}
+
+static void *tegra_gem_prime_kmap(struct dma_buf *buf, unsigned long page)
+{
+       return NULL;
+}
+
+static void tegra_gem_prime_kunmap(struct dma_buf *buf, unsigned long page,
+                                  void *addr)
+{
+}
+
+static int tegra_gem_prime_mmap(struct dma_buf *buf, struct vm_area_struct *vma)
+{
+       return -EINVAL;
+}
+
+static const struct dma_buf_ops tegra_gem_prime_dmabuf_ops = {
+       .map_dma_buf = tegra_gem_prime_map_dma_buf,
+       .unmap_dma_buf = tegra_gem_prime_unmap_dma_buf,
+       .release = tegra_gem_prime_release,
+       .kmap_atomic = tegra_gem_prime_kmap_atomic,
+       .kunmap_atomic = tegra_gem_prime_kunmap_atomic,
+       .kmap = tegra_gem_prime_kmap,
+       .kunmap = tegra_gem_prime_kunmap,
+       .mmap = tegra_gem_prime_mmap,
+};
+
+struct dma_buf *tegra_gem_prime_export(struct drm_device *drm,
+                                      struct drm_gem_object *gem,
+                                      int flags)
+{
+       return dma_buf_export(gem, &tegra_gem_prime_dmabuf_ops, gem->size,
+                             flags);
+}
+
+struct drm_gem_object *tegra_gem_prime_import(struct drm_device *drm,
+                                             struct dma_buf *buf)
+{
+       struct tegra_bo *bo;
+
+       if (buf->ops == &tegra_gem_prime_dmabuf_ops) {
+               struct drm_gem_object *gem = buf->priv;
+
+               if (gem->dev == drm) {
+                       drm_gem_object_reference(gem);
+                       return gem;
+               }
+       }
+
+       bo = tegra_bo_import(drm, buf);
+       if (IS_ERR(bo))
+               return ERR_CAST(bo);
+
+       return &bo->gem;
+}
index 007e0be..ffd4f79 100644 (file)
@@ -31,6 +31,7 @@ struct tegra_bo {
        struct drm_gem_object gem;
        struct host1x_bo base;
        unsigned long flags;
+       struct sg_table *sgt;
        dma_addr_t paddr;
        void *vaddr;
 };
@@ -57,4 +58,10 @@ int tegra_drm_mmap(struct file *file, struct vm_area_struct *vma);
 
 extern const struct vm_operations_struct tegra_bo_vm_ops;
 
+struct dma_buf *tegra_gem_prime_export(struct drm_device *drm,
+                                      struct drm_gem_object *gem,
+                                      int flags);
+struct drm_gem_object *tegra_gem_prime_import(struct drm_device *drm,
+                                             struct dma_buf *buf);
+
 #endif