drm/exynos: add iommu support for g2d

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>
This commit is contained in:
Inki Dae 2012-11-03 21:53:24 -07:00 committed by Inki Dae
parent 1055b39fac
commit d87342c10d
3 changed files with 118 additions and 69 deletions

View File

@ -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:

View 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;

View 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,