From: Inki Dae <inki.dae@xxxxxxxxxxx> Chagelog v2: remove 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@xxxxxxxxxxx> Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx> --- drivers/gpu/drm/exynos/exynos_drm_g2d.c | 171 ++++++++++++++++++++------------ drivers/gpu/drm/exynos/exynos_drm_gem.c | 10 +- drivers/gpu/drm/exynos/exynos_drm_gem.h | 6 +- 3 files changed, 118 insertions(+), 69 deletions(-) diff --git a/drivers/gpu/drm/exynos/exynos_drm_g2d.c b/drivers/gpu/drm/exynos/exynos_drm_g2d.c index f7aab24..bac2399 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_g2d.c +++ b/drivers/gpu/drm/exynos/exynos_drm_g2d.c @@ -17,11 +17,14 @@ #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: diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.c b/drivers/gpu/drm/exynos/exynos_drm_gem.c index 08d0218..8cb6824 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gem.c +++ b/drivers/gpu/drm/exynos/exynos_drm_gem.c @@ -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; diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.h b/drivers/gpu/drm/exynos/exynos_drm_gem.h index 0236321..83d21ef 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gem.h +++ b/drivers/gpu/drm/exynos/exynos_drm_gem.h @@ -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, -- 1.8.0.rc3.16.g8ead1bf _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/dri-devel