mirror of
https://github.com/torvalds/linux.git
synced 2024-12-17 00:21:32 +00:00
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:
parent
1055b39fac
commit
d87342c10d
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user