drm/exynos: add iommu support for g2d
authorInki Dae <daeinki@gmail.com>
Sun, 4 Nov 2012 04:53:24 +0000 (21:53 -0700)
committerInki Dae <inki.dae@samsung.com>
Tue, 4 Dec 2012 05:45:58 +0000 (14:45 +0900)
Chagelog v2:
removed unnecessary structure, struct g2d_gem_node.

Chagelog v1:
This patch adds iommu support for g2d driver. For this, it
adds subdrv_probe/remove callback to enable or disable
g2d iommu. And with this patch, in case of using g2d iommu,
we can get or put device address to a gem handle from user
through exynos_drm_gem_get/put_dma_addr(). Actually, these
functions take a reference to a gem handle so that the gem
object used by g2d dma is released properly.

And runqueue_node has a pointer to drm_file object of current
process to manage gem handles to owner.

This patch is based on the below patch set, "drm/exynos: add
iommu support for -next".
     http://www.spinics.net/lists/dri-devel/msg29041.html

Signed-off-by: Inki Dae <inki.dae@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
drivers/gpu/drm/exynos/exynos_drm_g2d.c
drivers/gpu/drm/exynos/exynos_drm_gem.c
drivers/gpu/drm/exynos/exynos_drm_gem.h

index f7aab24ea46cf716e013c8b8e0dfc2b1ce5e0aa0..bac2399857d1e66e3840277e9ae64ca92a38ebbf 100644 (file)
 #include <linux/pm_runtime.h>
 #include <linux/slab.h>
 #include <linux/workqueue.h>
+#include <linux/dma-mapping.h>
+#include <linux/dma-attrs.h>
 
 #include <drm/drmP.h>
 #include <drm/exynos_drm.h>
 #include "exynos_drm_drv.h"
 #include "exynos_drm_gem.h"
+#include "exynos_drm_iommu.h"
 
 #define G2D_HW_MAJOR_VER               4
 #define G2D_HW_MINOR_VER               1
@@ -92,6 +95,8 @@
 #define G2D_CMDLIST_POOL_SIZE          (G2D_CMDLIST_SIZE * G2D_CMDLIST_NUM)
 #define G2D_CMDLIST_DATA_NUM           (G2D_CMDLIST_SIZE / sizeof(u32) - 2)
 
+#define MAX_BUF_ADDR_NR                        6
+
 /* cmdlist data structure */
 struct g2d_cmdlist {
        u32     head;
@@ -104,15 +109,11 @@ struct drm_exynos_pending_g2d_event {
        struct drm_exynos_g2d_event     event;
 };
 
-struct g2d_gem_node {
-       struct list_head        list;
-       unsigned int            handle;
-};
-
 struct g2d_cmdlist_node {
        struct list_head        list;
        struct g2d_cmdlist      *cmdlist;
-       unsigned int            gem_nr;
+       unsigned int            map_nr;
+       unsigned int            handles[MAX_BUF_ADDR_NR];
        dma_addr_t              dma_addr;
 
        struct drm_exynos_pending_g2d_event     *event;
@@ -122,6 +123,7 @@ struct g2d_runqueue_node {
        struct list_head        list;
        struct list_head        run_cmdlist;
        struct list_head        event_list;
+       struct drm_file         *filp;
        pid_t                   pid;
        struct completion       complete;
        int                     async;
@@ -143,6 +145,7 @@ struct g2d_data {
        struct mutex                    cmdlist_mutex;
        dma_addr_t                      cmdlist_pool;
        void                            *cmdlist_pool_virt;
+       struct dma_attrs                cmdlist_dma_attrs;
 
        /* runqueue*/
        struct g2d_runqueue_node        *runqueue_node;
@@ -155,11 +158,17 @@ static int g2d_init_cmdlist(struct g2d_data *g2d)
 {
        struct device *dev = g2d->dev;
        struct g2d_cmdlist_node *node = g2d->cmdlist_node;
+       struct exynos_drm_subdrv *subdrv = &g2d->subdrv;
        int nr;
        int ret;
 
-       g2d->cmdlist_pool_virt = dma_alloc_coherent(dev, G2D_CMDLIST_POOL_SIZE,
-                                               &g2d->cmdlist_pool, GFP_KERNEL);
+       init_dma_attrs(&g2d->cmdlist_dma_attrs);
+       dma_set_attr(DMA_ATTR_WRITE_COMBINE, &g2d->cmdlist_dma_attrs);
+
+       g2d->cmdlist_pool_virt = dma_alloc_attrs(subdrv->drm_dev->dev,
+                                               G2D_CMDLIST_POOL_SIZE,
+                                               &g2d->cmdlist_pool, GFP_KERNEL,
+                                               &g2d->cmdlist_dma_attrs);
        if (!g2d->cmdlist_pool_virt) {
                dev_err(dev, "failed to allocate dma memory\n");
                return -ENOMEM;
@@ -184,18 +193,20 @@ static int g2d_init_cmdlist(struct g2d_data *g2d)
        return 0;
 
 err:
-       dma_free_coherent(dev, G2D_CMDLIST_POOL_SIZE, g2d->cmdlist_pool_virt,
-                       g2d->cmdlist_pool);
+       dma_free_attrs(subdrv->drm_dev->dev, G2D_CMDLIST_POOL_SIZE,
+                       g2d->cmdlist_pool_virt,
+                       g2d->cmdlist_pool, &g2d->cmdlist_dma_attrs);
        return ret;
 }
 
 static void g2d_fini_cmdlist(struct g2d_data *g2d)
 {
-       struct device *dev = g2d->dev;
+       struct exynos_drm_subdrv *subdrv = &g2d->subdrv;
 
        kfree(g2d->cmdlist_node);
-       dma_free_coherent(dev, G2D_CMDLIST_POOL_SIZE, g2d->cmdlist_pool_virt,
-                       g2d->cmdlist_pool);
+       dma_free_attrs(subdrv->drm_dev->dev, G2D_CMDLIST_POOL_SIZE,
+                       g2d->cmdlist_pool_virt,
+                       g2d->cmdlist_pool, &g2d->cmdlist_dma_attrs);
 }
 
 static struct g2d_cmdlist_node *g2d_get_cmdlist(struct g2d_data *g2d)
@@ -245,62 +256,51 @@ add_to_list:
                list_add_tail(&node->event->base.link, &g2d_priv->event_list);
 }
 
-static int g2d_get_cmdlist_gem(struct drm_device *drm_dev,
-                              struct drm_file *file,
-                              struct g2d_cmdlist_node *node)
+static int g2d_map_cmdlist_gem(struct g2d_data *g2d,
+                               struct g2d_cmdlist_node *node,
+                               struct drm_device *drm_dev,
+                               struct drm_file *file)
 {
-       struct drm_exynos_file_private *file_priv = file->driver_priv;
-       struct exynos_drm_g2d_private *g2d_priv = file_priv->g2d_priv;
        struct g2d_cmdlist *cmdlist = node->cmdlist;
-       dma_addr_t *addr;
        int offset;
        int i;
 
-       for (i = 0; i < node->gem_nr; i++) {
-               struct g2d_gem_node *gem_node;
-
-               gem_node = kzalloc(sizeof(*gem_node), GFP_KERNEL);
-               if (!gem_node) {
-                       dev_err(g2d_priv->dev, "failed to allocate gem node\n");
-                       return -ENOMEM;
-               }
+       for (i = 0; i < node->map_nr; i++) {
+               unsigned long handle;
+               dma_addr_t *addr;
 
                offset = cmdlist->last - (i * 2 + 1);
-               gem_node->handle = cmdlist->data[offset];
+               handle = cmdlist->data[offset];
 
-               addr = exynos_drm_gem_get_dma_addr(drm_dev, gem_node->handle,
-                                                  file);
+               addr = exynos_drm_gem_get_dma_addr(drm_dev, handle, file);
                if (IS_ERR(addr)) {
-                       node->gem_nr = i;
-                       kfree(gem_node);
-                       return PTR_ERR(addr);
+                       node->map_nr = i;
+                       return -EFAULT;
                }
 
                cmdlist->data[offset] = *addr;
-               list_add_tail(&gem_node->list, &g2d_priv->gem_list);
-               g2d_priv->gem_nr++;
+               node->handles[i] = handle;
        }
 
        return 0;
 }
 
-static void g2d_put_cmdlist_gem(struct drm_device *drm_dev,
-                               struct drm_file *file,
-                               unsigned int nr)
+static void g2d_unmap_cmdlist_gem(struct g2d_data *g2d,
+                                 struct g2d_cmdlist_node *node,
+                                 struct drm_file *filp)
 {
-       struct drm_exynos_file_private *file_priv = file->driver_priv;
-       struct exynos_drm_g2d_private *g2d_priv = file_priv->g2d_priv;
-       struct g2d_gem_node *node, *n;
+       struct exynos_drm_subdrv *subdrv = &g2d->subdrv;
+       int i;
 
-       list_for_each_entry_safe_reverse(node, n, &g2d_priv->gem_list, list) {
-               if (!nr)
-                       break;
+       for (i = 0; i < node->map_nr; i++) {
+               unsigned int handle = node->handles[i];
 
-               exynos_drm_gem_put_dma_addr(drm_dev, node->handle, file);
-               list_del_init(&node->list);
-               kfree(node);
-               nr--;
+               exynos_drm_gem_put_dma_addr(subdrv->drm_dev, handle, filp);
+
+               node->handles[i] = 0;
        }
+
+       node->map_nr = 0;
 }
 
 static void g2d_dma_start(struct g2d_data *g2d,
@@ -337,10 +337,18 @@ static struct g2d_runqueue_node *g2d_get_runqueue_node(struct g2d_data *g2d)
 static void g2d_free_runqueue_node(struct g2d_data *g2d,
                                   struct g2d_runqueue_node *runqueue_node)
 {
+       struct g2d_cmdlist_node *node;
+
        if (!runqueue_node)
                return;
 
        mutex_lock(&g2d->cmdlist_mutex);
+       /*
+        * commands in run_cmdlist have been completed so unmap all gem
+        * objects in each command node so that they are unreferenced.
+        */
+       list_for_each_entry(node, &runqueue_node->run_cmdlist, list)
+               g2d_unmap_cmdlist_gem(g2d, node, runqueue_node->filp);
        list_splice_tail_init(&runqueue_node->run_cmdlist, &g2d->free_cmdlist);
        mutex_unlock(&g2d->cmdlist_mutex);
 
@@ -587,7 +595,7 @@ int exynos_g2d_set_cmdlist_ioctl(struct drm_device *drm_dev, void *data,
        if (ret < 0)
                goto err_free_event;
 
-       node->gem_nr = req->cmd_gem_nr;
+       node->map_nr = req->cmd_gem_nr;
        if (req->cmd_gem_nr) {
                struct drm_exynos_g2d_cmd *cmd_gem;
 
@@ -605,7 +613,7 @@ int exynos_g2d_set_cmdlist_ioctl(struct drm_device *drm_dev, void *data,
                if (ret < 0)
                        goto err_free_event;
 
-               ret = g2d_get_cmdlist_gem(drm_dev, file, node);
+               ret = g2d_map_cmdlist_gem(g2d, node, drm_dev, file);
                if (ret < 0)
                        goto err_unmap;
        }
@@ -624,7 +632,7 @@ int exynos_g2d_set_cmdlist_ioctl(struct drm_device *drm_dev, void *data,
        return 0;
 
 err_unmap:
-       g2d_put_cmdlist_gem(drm_dev, file, node->gem_nr);
+       g2d_unmap_cmdlist_gem(g2d, node, file);
 err_free_event:
        if (node->event) {
                spin_lock_irqsave(&drm_dev->event_lock, flags);
@@ -680,6 +688,7 @@ int exynos_g2d_exec_ioctl(struct drm_device *drm_dev, void *data,
 
        mutex_lock(&g2d->runqueue_mutex);
        runqueue_node->pid = current->pid;
+       runqueue_node->filp = file;
        list_add_tail(&runqueue_node->list, &g2d->runqueue);
        if (!g2d->runqueue_node)
                g2d_exec_runqueue(g2d);
@@ -696,6 +705,43 @@ out:
 }
 EXPORT_SYMBOL_GPL(exynos_g2d_exec_ioctl);
 
+static int g2d_subdrv_probe(struct drm_device *drm_dev, struct device *dev)
+{
+       struct g2d_data *g2d;
+       int ret;
+
+       g2d = dev_get_drvdata(dev);
+       if (!g2d)
+               return -EFAULT;
+
+       /* allocate dma-aware cmdlist buffer. */
+       ret = g2d_init_cmdlist(g2d);
+       if (ret < 0) {
+               dev_err(dev, "cmdlist init failed\n");
+               return ret;
+       }
+
+       if (!is_drm_iommu_supported(drm_dev))
+               return 0;
+
+       ret = drm_iommu_attach_device(drm_dev, dev);
+       if (ret < 0) {
+               dev_err(dev, "failed to enable iommu.\n");
+               g2d_fini_cmdlist(g2d);
+       }
+
+       return ret;
+
+}
+
+static void g2d_subdrv_remove(struct drm_device *drm_dev, struct device *dev)
+{
+       if (!is_drm_iommu_supported(drm_dev))
+               return;
+
+       drm_iommu_detach_device(drm_dev, dev);
+}
+
 static int g2d_open(struct drm_device *drm_dev, struct device *dev,
                        struct drm_file *file)
 {
@@ -734,12 +780,19 @@ static void g2d_close(struct drm_device *drm_dev, struct device *dev,
                return;
 
        mutex_lock(&g2d->cmdlist_mutex);
-       list_for_each_entry_safe(node, n, &g2d_priv->inuse_cmdlist, list)
+       list_for_each_entry_safe(node, n, &g2d_priv->inuse_cmdlist, list) {
+               /*
+                * unmap all gem objects not completed.
+                *
+                * P.S. if current process was terminated forcely then
+                * there may be some commands in inuse_cmdlist so unmap
+                * them.
+                */
+               g2d_unmap_cmdlist_gem(g2d, node, file);
                list_move_tail(&node->list, &g2d->free_cmdlist);
+       }
        mutex_unlock(&g2d->cmdlist_mutex);
 
-       g2d_put_cmdlist_gem(drm_dev, file, g2d_priv->gem_nr);
-
        kfree(file_priv->g2d_priv);
 }
 
@@ -778,15 +831,11 @@ static int __devinit g2d_probe(struct platform_device *pdev)
        mutex_init(&g2d->cmdlist_mutex);
        mutex_init(&g2d->runqueue_mutex);
 
-       ret = g2d_init_cmdlist(g2d);
-       if (ret < 0)
-               goto err_destroy_workqueue;
-
        g2d->gate_clk = clk_get(dev, "fimg2d");
        if (IS_ERR(g2d->gate_clk)) {
                dev_err(dev, "failed to get gate clock\n");
                ret = PTR_ERR(g2d->gate_clk);
-               goto err_fini_cmdlist;
+               goto err_destroy_workqueue;
        }
 
        pm_runtime_enable(dev);
@@ -818,6 +867,8 @@ static int __devinit g2d_probe(struct platform_device *pdev)
 
        subdrv = &g2d->subdrv;
        subdrv->dev = dev;
+       subdrv->probe = g2d_subdrv_probe;
+       subdrv->remove = g2d_subdrv_remove;
        subdrv->open = g2d_open;
        subdrv->close = g2d_close;
 
@@ -835,8 +886,6 @@ static int __devinit g2d_probe(struct platform_device *pdev)
 err_put_clk:
        pm_runtime_disable(dev);
        clk_put(g2d->gate_clk);
-err_fini_cmdlist:
-       g2d_fini_cmdlist(g2d);
 err_destroy_workqueue:
        destroy_workqueue(g2d->g2d_workq);
 err_destroy_slab:
index 08d0218d5ba6014f579f77ef97188ced5a42115d..8cb6824923c8658b0b74d43cfae2fd37df5f44f5 100644 (file)
@@ -266,14 +266,14 @@ int exynos_drm_gem_create_ioctl(struct drm_device *dev, void *data,
        return 0;
 }
 
-void *exynos_drm_gem_get_dma_addr(struct drm_device *dev,
+dma_addr_t *exynos_drm_gem_get_dma_addr(struct drm_device *dev,
                                        unsigned int gem_handle,
-                                       struct drm_file *file_priv)
+                                       struct drm_file *filp)
 {
        struct exynos_drm_gem_obj *exynos_gem_obj;
        struct drm_gem_object *obj;
 
-       obj = drm_gem_object_lookup(dev, file_priv, gem_handle);
+       obj = drm_gem_object_lookup(dev, filp, gem_handle);
        if (!obj) {
                DRM_ERROR("failed to lookup gem object.\n");
                return ERR_PTR(-EINVAL);
@@ -294,12 +294,12 @@ void *exynos_drm_gem_get_dma_addr(struct drm_device *dev,
 
 void exynos_drm_gem_put_dma_addr(struct drm_device *dev,
                                        unsigned int gem_handle,
-                                       struct drm_file *file_priv)
+                                       struct drm_file *filp)
 {
        struct exynos_drm_gem_obj *exynos_gem_obj;
        struct drm_gem_object *obj;
 
-       obj = drm_gem_object_lookup(dev, file_priv, gem_handle);
+       obj = drm_gem_object_lookup(dev, filp, gem_handle);
        if (!obj) {
                DRM_ERROR("failed to lookup gem object.\n");
                return;
index 0236321521a1295881acb6b18b70505065fa7929..83d21ef1d1e9be27aebed3b8141b35a09402c9b2 100644 (file)
@@ -105,9 +105,9 @@ int exynos_drm_gem_create_ioctl(struct drm_device *dev, void *data,
  * other drivers such as 2d/3d acceleration drivers.
  * with this function call, gem object reference count would be increased.
  */
-void *exynos_drm_gem_get_dma_addr(struct drm_device *dev,
+dma_addr_t *exynos_drm_gem_get_dma_addr(struct drm_device *dev,
                                        unsigned int gem_handle,
-                                       struct drm_file *file_priv);
+                                       struct drm_file *filp);
 
 /*
  * put dma address from gem handle and this function could be used for
@@ -116,7 +116,7 @@ void *exynos_drm_gem_get_dma_addr(struct drm_device *dev,
  */
 void exynos_drm_gem_put_dma_addr(struct drm_device *dev,
                                        unsigned int gem_handle,
-                                       struct drm_file *file_priv);
+                                       struct drm_file *filp);
 
 /* get buffer offset to map to user space. */
 int exynos_drm_gem_map_offset_ioctl(struct drm_device *dev, void *data,