287 lines
7.3 KiB
C
287 lines
7.3 KiB
C
|
/*
|
||
|
* linux/drivers/gpu/exynos/g2d/g2d_fence.c
|
||
|
*
|
||
|
* Copyright (C) 2017 Samsung Electronics Co., Ltd.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License
|
||
|
* version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful, but
|
||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
* General Public License for more details.
|
||
|
*/
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/dma-fence.h>
|
||
|
#include <linux/sync_file.h>
|
||
|
|
||
|
#include "g2d.h"
|
||
|
#include "g2d_uapi.h"
|
||
|
#include "g2d_task.h"
|
||
|
#include "g2d_fence.h"
|
||
|
#include "g2d_debug.h"
|
||
|
|
||
|
void g2d_fence_timeout_handler(unsigned long arg)
|
||
|
{
|
||
|
struct g2d_task *task = (struct g2d_task *)arg;
|
||
|
struct g2d_device *g2d_dev = task->g2d_dev;
|
||
|
struct dma_fence *fence;
|
||
|
unsigned long flags;
|
||
|
char name[32];
|
||
|
int i;
|
||
|
s32 afbc = 0;
|
||
|
|
||
|
for (i = 0; i < task->num_source; i++) {
|
||
|
fence = task->source[i].fence;
|
||
|
if (fence) {
|
||
|
strlcpy(name, fence->ops->get_driver_name(fence),
|
||
|
sizeof(name));
|
||
|
perrfndev(g2d_dev, " SOURCE[%d]: %s #%d (%s)",
|
||
|
i, name, fence->seqno,
|
||
|
dma_fence_is_signaled(fence)
|
||
|
? "signaled" : "active");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fence = task->target.fence;
|
||
|
if (fence) {
|
||
|
strlcpy(name, fence->ops->get_driver_name(fence), sizeof(name));
|
||
|
perrfn(" TARGET: %s #%d (%s)", name, fence->seqno,
|
||
|
dma_fence_is_signaled(fence) ? "signaled" : "active");
|
||
|
}
|
||
|
|
||
|
if (task->release_fence)
|
||
|
perrfn(" Pending g2d release fence: #%d",
|
||
|
task->release_fence->fence->seqno);
|
||
|
|
||
|
/*
|
||
|
* Give up waiting the acquire fences that are not currently signaled
|
||
|
* and force pushing this pending task to the H/W to avoid indefinite
|
||
|
* wait for the fences to be signaled.
|
||
|
* The reference count is required to prevent racing about the
|
||
|
* acqure fences between this time handler and the fence callback.
|
||
|
*/
|
||
|
spin_lock_irqsave(&task->fence_timeout_lock, flags);
|
||
|
|
||
|
/*
|
||
|
* Make sure if there is really a unsignaled fences. task->starter is
|
||
|
* decremented under fence_timeout_lock held if it is done by fence
|
||
|
* signal.
|
||
|
*/
|
||
|
if (atomic_read(&task->starter.refcount.refs) == 0) {
|
||
|
spin_unlock_irqrestore(&task->fence_timeout_lock, flags);
|
||
|
perr("All fences are signaled. (work_busy? %d, state %#lx)",
|
||
|
work_busy(&task->work), task->state);
|
||
|
/*
|
||
|
* If this happens, there is racing between
|
||
|
* g2d_fence_timeout_handler() and g2d_queuework_task(). Once
|
||
|
* g2d_queuework_task() is invoked, it is guaranteed that the
|
||
|
* task is to be scheduled to H/W.
|
||
|
*/
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
perrfn("%d Fence(s) timed out after %d msec.",
|
||
|
atomic_read(&task->starter.refcount.refs),
|
||
|
G2D_FENCE_TIMEOUT_MSEC);
|
||
|
|
||
|
/* Increase reference to prevent running the workqueue in callback */
|
||
|
kref_get(&task->starter);
|
||
|
|
||
|
spin_unlock_irqrestore(&task->fence_timeout_lock, flags);
|
||
|
|
||
|
for (i = 0; i < task->num_source; i++) {
|
||
|
fence = task->source[i].fence;
|
||
|
if (fence)
|
||
|
dma_fence_remove_callback(fence,
|
||
|
&task->source[i].fence_cb);
|
||
|
}
|
||
|
|
||
|
fence = task->target.fence;
|
||
|
if (fence)
|
||
|
dma_fence_remove_callback(fence, &task->target.fence_cb);
|
||
|
|
||
|
/*
|
||
|
* Now it is OK to init kref for g2d_start_task() below.
|
||
|
* All fences waiters are removed
|
||
|
*/
|
||
|
kref_init(&task->starter);
|
||
|
|
||
|
/* check compressed buffer because crashed buffer makes recovery */
|
||
|
for (i = 0; i < task->num_source; i++) {
|
||
|
if (IS_AFBC(
|
||
|
task->source[i].commands[G2DSFR_IMG_COLORMODE].value))
|
||
|
afbc |= 1 << i;
|
||
|
}
|
||
|
|
||
|
if (IS_AFBC(task->target.commands[G2DSFR_IMG_COLORMODE].value))
|
||
|
afbc |= 1 << G2D_MAX_IMAGES;
|
||
|
|
||
|
g2d_stamp_task(task, G2D_STAMP_STATE_TIMEOUT_FENCE, afbc);
|
||
|
|
||
|
g2d_cancel_task(task);
|
||
|
};
|
||
|
|
||
|
static const char *g2d_fence_get_driver_name(struct dma_fence *fence)
|
||
|
{
|
||
|
return "g2d";
|
||
|
}
|
||
|
|
||
|
static bool g2d_fence_enable_signaling(struct dma_fence *fence)
|
||
|
{
|
||
|
/* nothing to do */
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static void g2d_fence_release(struct dma_fence *fence)
|
||
|
{
|
||
|
kfree(fence);
|
||
|
}
|
||
|
|
||
|
static void g2d_fence_value_str(struct dma_fence *fence, char *str, int size)
|
||
|
{
|
||
|
snprintf(str, size, "%d", fence->seqno);
|
||
|
}
|
||
|
|
||
|
static struct dma_fence_ops g2d_fence_ops = {
|
||
|
.get_driver_name = g2d_fence_get_driver_name,
|
||
|
.get_timeline_name = g2d_fence_get_driver_name,
|
||
|
.enable_signaling = g2d_fence_enable_signaling,
|
||
|
.wait = dma_fence_default_wait,
|
||
|
.release = g2d_fence_release,
|
||
|
.fence_value_str = g2d_fence_value_str,
|
||
|
};
|
||
|
|
||
|
struct sync_file *g2d_create_release_fence(struct g2d_device *g2d_dev,
|
||
|
struct g2d_task *task,
|
||
|
struct g2d_task_data *data)
|
||
|
{
|
||
|
struct dma_fence *fence;
|
||
|
struct sync_file *file;
|
||
|
s32 release_fences[g2d_dev->max_layers + 1];
|
||
|
int i;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!(task->flags & G2D_FLAG_NONBLOCK) || !data->num_release_fences)
|
||
|
return NULL;
|
||
|
|
||
|
if (data->num_release_fences > (task->num_source + 1)) {
|
||
|
perrfndev(g2d_dev,
|
||
|
"Too many release fences %d required (src: %d)",
|
||
|
data->num_release_fences, task->num_source);
|
||
|
return ERR_PTR(-EINVAL);
|
||
|
}
|
||
|
|
||
|
fence = kzalloc(sizeof(*fence), GFP_KERNEL);
|
||
|
if (!fence)
|
||
|
return ERR_PTR(-ENOMEM);
|
||
|
|
||
|
dma_fence_init(fence, &g2d_fence_ops, &g2d_dev->fence_lock,
|
||
|
g2d_dev->fence_context,
|
||
|
atomic_inc_return(&g2d_dev->fence_timeline));
|
||
|
|
||
|
file = sync_file_create(fence);
|
||
|
dma_fence_put(fence);
|
||
|
if (!file)
|
||
|
return ERR_PTR(-ENOMEM);
|
||
|
|
||
|
for (i = 0; i < data->num_release_fences; i++) {
|
||
|
release_fences[i] = get_unused_fd_flags(O_CLOEXEC);
|
||
|
if (release_fences[i] < 0) {
|
||
|
ret = release_fences[i];
|
||
|
while (i-- > 0)
|
||
|
put_unused_fd(release_fences[i]);
|
||
|
goto err_fd;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (copy_to_user(data->release_fences, release_fences,
|
||
|
sizeof(u32) * data->num_release_fences)) {
|
||
|
ret = -EFAULT;
|
||
|
perrfndev(g2d_dev, "Failed to copy release fences to user");
|
||
|
goto err_fd;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < data->num_release_fences; i++)
|
||
|
fd_install(release_fences[i], get_file(file->file));
|
||
|
|
||
|
return file;
|
||
|
err_fd:
|
||
|
while (i-- > 0)
|
||
|
put_unused_fd(release_fences[i]);
|
||
|
fput(file->file);
|
||
|
|
||
|
return ERR_PTR(ret);
|
||
|
}
|
||
|
|
||
|
struct dma_fence *g2d_get_acquire_fence(struct g2d_device *g2d_dev,
|
||
|
struct g2d_layer *layer, s32 fence_fd)
|
||
|
{
|
||
|
struct dma_fence *fence;
|
||
|
int ret;
|
||
|
|
||
|
if (!(layer->flags & G2D_LAYERFLAG_ACQUIRE_FENCE))
|
||
|
return NULL;
|
||
|
|
||
|
fence = sync_file_get_fence(fence_fd);
|
||
|
if (!fence)
|
||
|
return ERR_PTR(-EINVAL);
|
||
|
|
||
|
kref_get(&layer->task->starter);
|
||
|
|
||
|
ret = dma_fence_add_callback(fence, &layer->fence_cb, g2d_fence_callback);
|
||
|
if (ret < 0) {
|
||
|
kref_put(&layer->task->starter, g2d_queuework_task);
|
||
|
dma_fence_put(fence);
|
||
|
return (ret == -ENOENT) ? NULL : ERR_PTR(ret);
|
||
|
}
|
||
|
|
||
|
return fence;
|
||
|
}
|
||
|
|
||
|
static bool g2d_fence_has_error(struct g2d_layer *layer, int layer_idx)
|
||
|
{
|
||
|
struct dma_fence *fence = layer->fence;
|
||
|
unsigned long flags;
|
||
|
bool err = false;
|
||
|
|
||
|
if (fence) {
|
||
|
spin_lock_irqsave(fence->lock, flags);
|
||
|
err = dma_fence_get_status_locked(fence) < 0;
|
||
|
spin_unlock_irqrestore(fence->lock, flags);
|
||
|
}
|
||
|
|
||
|
if (err) {
|
||
|
char name[32];
|
||
|
|
||
|
strlcpy(name, fence->ops->get_driver_name(fence), sizeof(name));
|
||
|
|
||
|
dev_err(layer->task->g2d_dev->dev,
|
||
|
"%s: Error fence of %s%d found: %s#%d\n", __func__,
|
||
|
(layer_idx < 0) ? "target" : "source",
|
||
|
layer_idx, name, fence->seqno);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool g2d_task_has_error_fence(struct g2d_task *task)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
|
||
|
for (i = 0; i < task->num_source; i++)
|
||
|
if (g2d_fence_has_error(&task->source[i], i))
|
||
|
return false;
|
||
|
|
||
|
if (g2d_fence_has_error(&task->target, -1))
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|