2122 lines
54 KiB
C
2122 lines
54 KiB
C
/*
|
|
* drivers/media/m2m1shot2.c
|
|
*
|
|
* Copyright (C) 2015 Samsung Electronics Co., Ltd.
|
|
*
|
|
* Contact: Cho KyongHo <pullip.cho@samsung.com>
|
|
*
|
|
* 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/device.h>
|
|
#include <linux/dma-buf.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/compat.h>
|
|
|
|
#include <linux/ion.h>
|
|
#include <linux/exynos_ion.h>
|
|
#include <linux/exynos_iovmm.h>
|
|
|
|
#include <media/m2m1shot2.h>
|
|
|
|
#include <asm/cacheflush.h>
|
|
|
|
#define M2M1SHOT2_NEED_CACHEFLUSH_ALL (10 * SZ_1M)
|
|
#define M2M1SHOT2_FENCE_MASK (M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE | \
|
|
M2M1SHOT2_IMGFLAG_RELEASE_FENCE)
|
|
|
|
static void m2m1shot2_fence_callback(struct sync_fence *fence,
|
|
struct sync_fence_waiter *waiter);
|
|
|
|
static void m2m1shot2_timeout_handler(unsigned long arg);
|
|
|
|
/*
|
|
* STATE TRANSITION of m2m1shot2_context:
|
|
* - PROCESSING:
|
|
* # set in m2m1shot2_start_processing() after the context is moved to
|
|
* active_contexts.
|
|
* # clear in __m2m1shot2_finish_context() inside m2m1shot2_finish_context()
|
|
* that is called by the client driver when IRQ occurs.
|
|
* - PENDING:
|
|
* # set in m2m1shot2_start_context() along with PROCESSING.
|
|
* # clear in m2m1shot2_schedule() before calling .device_run().
|
|
* - WAITING:
|
|
* # set at the entry of m2m1shot2_wait_process()
|
|
* # clear in m2m1shot2_ioctl() before returning to user
|
|
* - PROCESSED:
|
|
* # set in __m2m1shot2_finish_context() along with clearing PROCESSING.
|
|
* # clear in m2m1shot2_ioctl() before returning to user
|
|
* - ERROR:
|
|
* # set in __m2m1shot2_finish_context() on an error
|
|
* # clear in m2m1shot2_ioctl() before returning to user
|
|
*/
|
|
static int m2m1shot2_open(struct inode *inode, struct file *filp)
|
|
{
|
|
struct m2m1shot2_device *m21dev = container_of(filp->private_data,
|
|
struct m2m1shot2_device, misc);
|
|
struct m2m1shot2_context *ctx;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
|
|
if (!ctx)
|
|
return -ENOMEM;
|
|
|
|
ctx->timeline = sw_sync_timeline_create(dev_name(m21dev->dev));
|
|
if (!ctx->timeline) {
|
|
dev_err(m21dev->dev, "Failed to create timeline\n");
|
|
ret = -ENOMEM;
|
|
goto err_timeline;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&ctx->node);
|
|
|
|
spin_lock_irqsave(&m21dev->lock_ctx, flags);
|
|
list_add_tail(&ctx->node, &m21dev->contexts);
|
|
spin_unlock_irqrestore(&m21dev->lock_ctx, flags);
|
|
|
|
ctx->m21dev = m21dev;
|
|
mutex_init(&ctx->mutex);
|
|
init_completion(&ctx->complete);
|
|
complete_all(&ctx->complete); /* prevent to wait for completion */
|
|
|
|
filp->private_data = ctx;
|
|
|
|
for (ret = 0; ret < M2M1SHOT2_MAX_IMAGES; ret++) {
|
|
ctx->source[ret].img.index = ret;
|
|
sync_fence_waiter_init(&ctx->source[ret].img.waiter,
|
|
m2m1shot2_fence_callback);
|
|
}
|
|
ctx->target.index = M2M1SHOT2_MAX_IMAGES;
|
|
sync_fence_waiter_init(&ctx->target.waiter, m2m1shot2_fence_callback);
|
|
|
|
ret = m21dev->ops->init_context(ctx);
|
|
if (ret)
|
|
goto err_init;
|
|
|
|
spin_lock(&m21dev->lock_priority);
|
|
ctx->priority = M2M1SHOT2_DEFAULT_PRIORITY;
|
|
m21dev->prior_stats[ctx->priority] += 1;
|
|
spin_unlock(&m21dev->lock_priority);
|
|
|
|
spin_lock_init(&ctx->fence_timeout_lock);
|
|
|
|
setup_timer(&ctx->timer, m2m1shot2_timeout_handler,
|
|
(unsigned long)ctx);
|
|
|
|
return 0;
|
|
err_init:
|
|
sync_timeline_destroy(&ctx->timeline->obj);
|
|
err_timeline:
|
|
kfree(ctx);
|
|
return ret;
|
|
|
|
}
|
|
|
|
/*
|
|
* m2m1shot2_current_context - get the current m2m1shot2_context
|
|
*
|
|
* return the current context pointer.
|
|
* This function should not be called other places than the function that
|
|
* finishes the current context.
|
|
*/
|
|
struct m2m1shot2_context *m2m1shot2_current_context(
|
|
const struct m2m1shot2_device *m21dev)
|
|
{
|
|
return m21dev->current_ctx;
|
|
}
|
|
|
|
static void m2m1shot2_put_userptr(struct device *dev,
|
|
struct m2m1shot2_dma_buffer *plane)
|
|
{
|
|
struct vm_area_struct *vma = plane->userptr.vma;
|
|
struct mm_struct *mm;
|
|
|
|
BUG_ON((vma == NULL) || (plane->userptr.addr == 0));
|
|
|
|
mm = vma->vm_mm;
|
|
|
|
exynos_iovmm_unmap_userptr(dev, plane->dma_addr);
|
|
|
|
down_read(&mm->mmap_sem);
|
|
|
|
while (vma) {
|
|
struct vm_area_struct *tvma;
|
|
|
|
if (vma->vm_ops && vma->vm_ops->close)
|
|
vma->vm_ops->close(vma);
|
|
|
|
if (vma->vm_file)
|
|
fput(vma->vm_file);
|
|
|
|
tvma = vma;
|
|
vma = vma->vm_next;
|
|
|
|
kfree(tvma);
|
|
}
|
|
|
|
up_read(&mm->mmap_sem);
|
|
|
|
mmput(mm);
|
|
|
|
plane->userptr.vma = NULL;
|
|
plane->userptr.addr = 0;
|
|
}
|
|
|
|
static void m2m1shot2_put_dmabuf(struct m2m1shot2_dma_buffer *plane)
|
|
{
|
|
ion_iovmm_unmap(plane->dmabuf.attachment,
|
|
plane->dma_addr - plane->dmabuf.offset);
|
|
dma_buf_detach(plane->dmabuf.dmabuf, plane->dmabuf.attachment);
|
|
dma_buf_put(plane->dmabuf.dmabuf);
|
|
plane->dmabuf.dmabuf = NULL;
|
|
plane->dmabuf.attachment = NULL;
|
|
}
|
|
|
|
static void m2m1shot2_put_buffer(struct m2m1shot2_context *ctx, u32 memory,
|
|
struct m2m1shot2_dma_buffer plane[],
|
|
unsigned int num_planes)
|
|
{
|
|
unsigned int i;
|
|
|
|
if (memory == M2M1SHOT2_BUFTYPE_DMABUF) {
|
|
for (i = 0; i < num_planes; i++)
|
|
m2m1shot2_put_dmabuf(&plane[i]);
|
|
} else if (memory == M2M1SHOT2_BUFTYPE_USERPTR) {
|
|
for (i = 0; i < num_planes; i++)
|
|
m2m1shot2_put_userptr(ctx->m21dev->dev, &plane[i]);
|
|
}
|
|
}
|
|
|
|
static void m2m1shot2_put_image(struct m2m1shot2_context *ctx,
|
|
struct m2m1shot2_context_image *img)
|
|
{
|
|
if (!!(img->flags & M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE)) {
|
|
/*
|
|
* confirm the fence callback is not called after img->fence is
|
|
* cleared by the deferred behavior of sync_fence_put().
|
|
*/
|
|
sync_fence_cancel_async(img->fence, &img->waiter);
|
|
sync_fence_put(img->fence);
|
|
img->flags &= ~M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE;
|
|
}
|
|
|
|
m2m1shot2_put_buffer(ctx, img->memory, img->plane, img->num_planes);
|
|
|
|
img->memory = M2M1SHOT2_BUFTYPE_NONE;
|
|
}
|
|
|
|
static void m2m1shot2_put_source_images(struct m2m1shot2_context *ctx)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ctx->num_sources; i++)
|
|
m2m1shot2_put_image(ctx, &ctx->source[i].img);
|
|
|
|
ctx->num_sources = 0;
|
|
}
|
|
|
|
static void m2m1shot2_put_images(struct m2m1shot2_context *ctx)
|
|
{
|
|
m2m1shot2_put_source_images(ctx);
|
|
m2m1shot2_put_image(ctx, &ctx->target);
|
|
}
|
|
|
|
static void __m2m1shot2_finish_context(struct m2m1shot2_context *ctx,
|
|
bool success)
|
|
{
|
|
struct m2m1shot2_device *m21dev = ctx->m21dev;
|
|
unsigned long flags;
|
|
|
|
sw_sync_timeline_inc(ctx->timeline, 1);
|
|
|
|
spin_lock_irqsave(&m21dev->lock_ctx, flags);
|
|
|
|
/* effective only to the current processing context */
|
|
if (WARN_ON(ctx != m21dev->current_ctx)) {
|
|
spin_unlock_irqrestore(&m21dev->lock_ctx, flags);
|
|
return;
|
|
}
|
|
|
|
m21dev->current_ctx = NULL;
|
|
list_add_tail(&ctx->node, &m21dev->contexts);
|
|
|
|
spin_unlock_irqrestore(&m21dev->lock_ctx, flags);
|
|
|
|
if (!success)
|
|
set_bit(M2M1S2_CTXSTATE_ERROR, &ctx->state);
|
|
|
|
set_bit(M2M1S2_CTXSTATE_PROCESSED, &ctx->state);
|
|
clear_bit(M2M1S2_CTXSTATE_PROCESSING, &ctx->state);
|
|
|
|
complete_all(&ctx->complete);
|
|
}
|
|
|
|
/**
|
|
* __m2m1shot2_schedule() - pick a context to process
|
|
*
|
|
* pick a context that is at the front of m2m1shot2_device.active_contexts
|
|
* and provide it to the client to process that context.
|
|
* If the client returns error then pick the next context.
|
|
*/
|
|
static void __m2m1shot2_schedule(struct m2m1shot2_device *m21dev)
|
|
{
|
|
struct m2m1shot2_context *ctx;
|
|
unsigned long flags;
|
|
|
|
retry:
|
|
spin_lock_irqsave(&m21dev->lock_ctx, flags);
|
|
|
|
if (m21dev->current_ctx != NULL) {
|
|
/* the client is currently processing a context */
|
|
spin_unlock_irqrestore(&m21dev->lock_ctx, flags);
|
|
return;
|
|
}
|
|
|
|
if (list_empty(&m21dev->active_contexts)) {
|
|
/* no context to process */
|
|
spin_unlock_irqrestore(&m21dev->lock_ctx, flags);
|
|
return;
|
|
}
|
|
|
|
ctx = list_first_entry(&m21dev->active_contexts,
|
|
struct m2m1shot2_context, node);
|
|
m21dev->current_ctx = ctx;
|
|
list_del_init(&ctx->node);
|
|
|
|
spin_unlock_irqrestore(&m21dev->lock_ctx, flags);
|
|
|
|
clear_bit(M2M1S2_CTXSTATE_PENDING, &ctx->state);
|
|
|
|
if (m21dev->ops->device_run(ctx) < 0) {
|
|
__m2m1shot2_finish_context(ctx, false);
|
|
goto retry;
|
|
}
|
|
}
|
|
|
|
#define is_vma_cached(vma) \
|
|
((pgprot_noncached((vma)->vm_page_prot) != (vma)->vm_page_prot) && \
|
|
(pgprot_writecombine((vma)->vm_page_prot) != (vma)->vm_page_prot))
|
|
|
|
static void m2m1shot2_unmap_image(struct m2m1shot2_device *m21dev,
|
|
struct m2m1shot2_context_image *img,
|
|
enum dma_data_direction dir,
|
|
bool skip_inv)
|
|
{
|
|
bool inv = !skip_inv && !(img->flags & M2M1SHOT2_IMGFLAG_NO_CACHEINV) &&
|
|
!(m21dev->attr & M2M1SHOT2_DEVATTR_COHERENT);
|
|
unsigned int i;
|
|
|
|
/* dma_buf_map_attachment() is not called */
|
|
if (img->plane[0].sgt == NULL)
|
|
return;
|
|
|
|
if (img->memory == M2M1SHOT2_BUFTYPE_USERPTR) {
|
|
for (i = 0; i < img->num_planes; i++)
|
|
if (is_vma_cached(img->plane[i].userptr.vma) && inv)
|
|
exynos_iommu_sync_for_cpu(m21dev->dev,
|
|
img->plane[i].dma_addr,
|
|
img->plane[i].payload, dir);
|
|
} else if (img->memory == M2M1SHOT2_BUFTYPE_DMABUF) {
|
|
for (i = 0; i < img->num_planes; i++) {
|
|
if (inv)
|
|
exynos_ion_sync_dmabuf_for_cpu(m21dev->dev,
|
|
img->plane[i].dmabuf.dmabuf,
|
|
img->plane[i].payload, dir);
|
|
dma_buf_unmap_attachment(
|
|
img->plane[i].dmabuf.attachment,
|
|
img->plane[i].sgt, dir);
|
|
/* not to call unmap again */
|
|
img->plane[i].sgt = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void m2m1shot2_unmap_images(struct m2m1shot2_context *ctx)
|
|
{
|
|
bool flush_all = test_bit(M2M1S2_CTXSTATE_CACHEINVALALL, &ctx->state);
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ctx->num_sources; i++)
|
|
m2m1shot2_unmap_image(ctx->m21dev,
|
|
&ctx->source[i].img, DMA_TO_DEVICE, flush_all);
|
|
|
|
m2m1shot2_unmap_image(ctx->m21dev,
|
|
&ctx->target, DMA_FROM_DEVICE, flush_all);
|
|
|
|
if (flush_all && !(ctx->m21dev->attr & M2M1SHOT2_DEVATTR_COHERENT))
|
|
flush_all_cpu_caches();
|
|
}
|
|
|
|
static int m2m1shot2_map_image(struct m2m1shot2_device *m21dev,
|
|
struct m2m1shot2_context_image *img,
|
|
enum dma_data_direction dir)
|
|
{
|
|
unsigned int i;
|
|
|
|
/* nothing to do for userptr and empty buffer types */
|
|
if (img->memory != M2M1SHOT2_BUFTYPE_DMABUF)
|
|
return 0;
|
|
|
|
for (i = 0; i < img->num_planes; i++) {
|
|
img->plane[i].sgt = dma_buf_map_attachment(
|
|
img->plane[i].dmabuf.attachment, dir);
|
|
if (IS_ERR(img->plane[i].sgt)) {
|
|
int ret = PTR_ERR(img->plane[i].sgt);
|
|
|
|
dev_err(m21dev->dev, "failed to map dmabuf-attachment");
|
|
img->plane[i].sgt = NULL;
|
|
while (i-- > 0) {
|
|
dma_buf_unmap_attachment(
|
|
img->plane[i].dmabuf.attachment,
|
|
img->plane[i].sgt, dir);
|
|
img->plane[i].sgt = NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool m2m1shot2_need_shareable_flush(struct dma_buf *dmabuf, bool clear)
|
|
{
|
|
return dma_buf_get_privflag(dmabuf, clear) &&
|
|
ion_may_hwrender_dmabuf(dmabuf);
|
|
|
|
}
|
|
|
|
static void m2m1shot2_cachesync_image(struct m2m1shot2_context *ctx,
|
|
struct m2m1shot2_context_image *img,
|
|
enum dma_data_direction dir)
|
|
{
|
|
int i;
|
|
|
|
if (!!(img->flags & M2M1SHOT2_IMGFLAG_NO_CACHECLEAN))
|
|
return;
|
|
|
|
if (img->memory == M2M1SHOT2_BUFTYPE_USERPTR) {
|
|
for (i = 0; i < img->num_planes; i++) {
|
|
if (is_vma_cached(img->plane[i].userptr.vma))
|
|
exynos_iommu_sync_for_device(ctx->m21dev->dev,
|
|
img->plane[i].dma_addr,
|
|
img->plane[i].payload, dir);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (img->memory != M2M1SHOT2_BUFTYPE_DMABUF)
|
|
return;
|
|
|
|
for (i = 0; i < img->num_planes; i++) {
|
|
if (m2m1shot2_need_shareable_flush(
|
|
img->plane[i].dmabuf.dmabuf, true))
|
|
exynos_ion_flush_dmabuf_for_device(ctx->m21dev->dev,
|
|
img->plane[i].dmabuf.dmabuf,
|
|
img->plane[i].payload);
|
|
else
|
|
exynos_ion_sync_dmabuf_for_device(ctx->m21dev->dev,
|
|
img->plane[i].dmabuf.dmabuf,
|
|
img->plane[i].payload, dir);
|
|
}
|
|
}
|
|
|
|
static void m2m1shot2_cachesync_images(struct m2m1shot2_context *ctx)
|
|
{
|
|
int i;
|
|
|
|
if (!!(ctx->m21dev->attr & M2M1SHOT2_DEVATTR_COHERENT) &&
|
|
!test_bit(M2M1S2_CTXSTATE_CACHEFLUSH, &ctx->state))
|
|
return;
|
|
|
|
if (test_bit(M2M1S2_CTXSTATE_CACHECLEANALL, &ctx->state) ||
|
|
test_bit(M2M1S2_CTXSTATE_CACHEFLUSHALL, &ctx->state)) {
|
|
flush_all_cpu_caches();
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < ctx->num_sources; i++)
|
|
m2m1shot2_cachesync_image(ctx,
|
|
&ctx->source[i].img, DMA_TO_DEVICE);
|
|
|
|
m2m1shot2_cachesync_image(ctx, &ctx->target, DMA_FROM_DEVICE);
|
|
}
|
|
|
|
static int m2m1shot2_map_images(struct m2m1shot2_context *ctx)
|
|
{
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
for (i = 0; i < ctx->num_sources; i++) {
|
|
ret = m2m1shot2_map_image(ctx->m21dev,
|
|
&ctx->source[i].img, DMA_TO_DEVICE);
|
|
if (ret) {
|
|
while (i-- > 0)
|
|
m2m1shot2_unmap_image(ctx->m21dev,
|
|
&ctx->source[i].img, DMA_TO_DEVICE,
|
|
true);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = m2m1shot2_map_image(ctx->m21dev, &ctx->target, DMA_FROM_DEVICE);
|
|
if (ret) {
|
|
for (i = 0; i < ctx->num_sources; i++)
|
|
m2m1shot2_unmap_image(ctx->m21dev,
|
|
&ctx->source[i].img, DMA_TO_DEVICE, true);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* m2m1shot2_wait_process() - wait until processing the context is finished
|
|
*
|
|
* This function should be called under ctx->mutex held.
|
|
*/
|
|
static bool m2m1shot2_wait_process(struct m2m1shot2_device *m21dev,
|
|
struct m2m1shot2_context *ctx)
|
|
{
|
|
bool success = true;
|
|
|
|
set_bit(M2M1S2_CTXSTATE_WAITING, &ctx->state);
|
|
|
|
wait_for_completion(&ctx->complete);
|
|
|
|
clear_bit(M2M1S2_CTXSTATE_WAITING, &ctx->state);
|
|
|
|
if ((ctx->state & ((1 << M2M1S2_CTXSTATE_PENDING) |
|
|
(1 << M2M1S2_CTXSTATE_PROCESSING)))) {
|
|
pr_err("m2m1shot finish state : %lx\n", ctx->state);
|
|
BUG();
|
|
}
|
|
|
|
clear_bit(M2M1S2_CTXSTATE_PROCESSED, &ctx->state);
|
|
|
|
if (test_bit(M2M1S2_CTXSTATE_ERROR, &ctx->state)) {
|
|
success = false;
|
|
clear_bit(M2M1S2_CTXSTATE_ERROR, &ctx->state);
|
|
}
|
|
|
|
m2m1shot2_unmap_images(ctx);
|
|
|
|
return success;
|
|
}
|
|
|
|
static void m2m1shot2_schedule_context(struct m2m1shot2_context *ctx)
|
|
{
|
|
struct m2m1shot2_device *m21dev = ctx->m21dev;
|
|
unsigned long flags;
|
|
|
|
del_timer(&ctx->timer);
|
|
|
|
m2m1shot2_cachesync_images(ctx);
|
|
|
|
spin_lock_irqsave(&ctx->m21dev->lock_ctx, flags);
|
|
|
|
BUG_ON(list_empty(&ctx->node));
|
|
|
|
/* move ctx from m21dev->contexts to m21dev->active_contexts */
|
|
list_move_tail(&ctx->node, &ctx->m21dev->active_contexts);
|
|
|
|
set_bit(M2M1S2_CTXSTATE_PROCESSING, &ctx->state);
|
|
|
|
spin_unlock_irqrestore(&ctx->m21dev->lock_ctx, flags);
|
|
|
|
__m2m1shot2_schedule(m21dev);
|
|
}
|
|
|
|
static void m2m1shot2_context_schedule_work(struct work_struct *work)
|
|
{
|
|
m2m1shot2_schedule_context(
|
|
container_of(work, struct m2m1shot2_context, work));
|
|
}
|
|
|
|
static void m2m1shot2_context_schedule_start(struct kref *kref)
|
|
{
|
|
m2m1shot2_schedule_context(
|
|
container_of(kref, struct m2m1shot2_context, starter));
|
|
}
|
|
|
|
static void m2m1shot2_schedule_queuework(struct kref *kref)
|
|
{
|
|
struct m2m1shot2_context *ctx =
|
|
container_of(kref, struct m2m1shot2_context, starter);
|
|
bool failed;
|
|
|
|
failed = !queue_work(ctx->m21dev->schedule_workqueue, &ctx->work);
|
|
|
|
BUG_ON(failed);
|
|
}
|
|
|
|
/* NOTE that this function is called under irq disabled context */
|
|
static void m2m1shot2_fence_callback(struct sync_fence *fence,
|
|
struct sync_fence_waiter *waiter)
|
|
{
|
|
struct m2m1shot2_context_image *img =
|
|
container_of(waiter, struct m2m1shot2_context_image, waiter);
|
|
unsigned long ptr = (unsigned long)img;
|
|
struct m2m1shot2_context *ctx;
|
|
unsigned long flags;
|
|
|
|
BUG_ON(img->index > M2M1SHOT2_MAX_IMAGES);
|
|
|
|
ptr -= sizeof(struct m2m1shot2_source_image) * img->index;
|
|
ptr -= offsetof(struct m2m1shot2_context, source);
|
|
ctx = (struct m2m1shot2_context *)ptr;
|
|
|
|
spin_lock_irqsave(&ctx->fence_timeout_lock, flags);
|
|
|
|
/*
|
|
* m2m1shot2_timeout_handler() cancels waiters of all acquire fences and
|
|
* releases the fences. It should be avoided to release fences that are
|
|
* alreayd released in the timeout handler.
|
|
*/
|
|
if (!(img->flags & M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE)) {
|
|
spin_unlock_irqrestore(&ctx->fence_timeout_lock, flags);
|
|
return;
|
|
}
|
|
|
|
img->flags &= ~M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE;
|
|
sync_fence_put(img->fence);
|
|
|
|
kref_put(&ctx->starter, m2m1shot2_schedule_queuework);
|
|
|
|
spin_unlock_irqrestore(&ctx->fence_timeout_lock, flags);
|
|
}
|
|
|
|
static void m2m1shot2_timeout_handler(unsigned long arg)
|
|
{
|
|
struct m2m1shot2_context *ctx = (struct m2m1shot2_context *)arg;
|
|
struct m2m1shot2_source_image *image = ctx->source;
|
|
struct m2m1shot2_context_image *timeout_image[M2M1SHOT2_MAX_IMAGES + 1];
|
|
unsigned long flags;
|
|
char name[32];
|
|
int i, j = 0;
|
|
|
|
pr_err("%s: %d Fence(s) timed out after %d msec.\n", __func__,
|
|
atomic_read(&ctx->starter.refcount), M2M1S2_TIMEOUT_INTERVAL);
|
|
|
|
for (i = 0; i < ctx->num_sources; i++) {
|
|
if (image[i].img.fence) {
|
|
memcpy(name, image[i].img.fence->name, sizeof(name));
|
|
name[sizeof(name) - 1] = '\0';
|
|
pr_err("%s: SOURCE[%d]: [%p] %s\n",
|
|
__func__, i, image[i].img.fence, name);
|
|
}
|
|
}
|
|
|
|
if (ctx->target.fence) {
|
|
memcpy(name, ctx->target.fence->name, sizeof(name));
|
|
name[sizeof(name) - 1] = '\0';
|
|
pr_err("%s: TARGET: [%p] %s\n",
|
|
__func__, ctx->target.fence, name);
|
|
}
|
|
|
|
if (ctx->release_fence)
|
|
pr_err("%s: Pending release fence: %p\n",
|
|
__func__, ctx->release_fence);
|
|
|
|
sync_dump();
|
|
|
|
/*
|
|
* 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 spinlock is required to prevent racing about releasing the
|
|
* acqure fences between this time handler and the fence callback.
|
|
*/
|
|
spin_lock_irqsave(&ctx->fence_timeout_lock, flags);
|
|
|
|
/*
|
|
* Make sure if there is really a unsigned fences. ctx->starter is
|
|
* decremented under fence_timeout_lock held if it is done by fence
|
|
* signal.
|
|
*/
|
|
if (atomic_read(&ctx->starter.refcount) == 0) {
|
|
spin_unlock_irqrestore(&ctx->fence_timeout_lock, flags);
|
|
pr_err("All fences have been signaled. (work_busy? %d)\n",
|
|
work_busy(&ctx->work));
|
|
/* If this happens, there is racing between
|
|
* m2m1shot2_timeout_handler() and m2m1shot2_schedule_queuework.
|
|
* Once m2m1shot2_schedule_queuework is invoked,
|
|
* it is guaranteed that the context is to be schduled to H/W.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < ctx->num_sources; i++) {
|
|
if (image[i].img.flags & M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE) {
|
|
image[i].img.flags &= ~M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE;
|
|
timeout_image[j++] = &image[i].img;
|
|
}
|
|
}
|
|
|
|
if (!!(ctx->target.flags & M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE)) {
|
|
ctx->target.flags &= ~M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE;
|
|
timeout_image[j++] = &ctx->target;
|
|
}
|
|
|
|
/* Increase reference to prevent running the workqueue in callback */
|
|
kref_get(&ctx->starter);
|
|
|
|
spin_unlock_irqrestore(&ctx->fence_timeout_lock, flags);
|
|
|
|
while (j-- > 0) {
|
|
sync_fence_cancel_async(timeout_image[j]->fence,
|
|
&timeout_image[j]->waiter);
|
|
sync_fence_put(timeout_image[j]->fence);
|
|
}
|
|
|
|
m2m1shot2_schedule_queuework(&ctx->starter);
|
|
};
|
|
|
|
/**
|
|
* m2m1shot2_start_context() - add a context to the queue
|
|
*
|
|
* Given a valid context ready for processing, add it to
|
|
* m2m1shot2_device.active_list that is the queue of ready contexts.
|
|
* The state of the given context becomes PROCESSING|PENDING.
|
|
* Then pick the next context to process from the queue. If there was no context
|
|
* in the queue before this function call, the given context will be pick for
|
|
* the context to process.
|
|
*/
|
|
static void m2m1shot2_start_context(struct m2m1shot2_device *m21dev,
|
|
struct m2m1shot2_context *ctx)
|
|
{
|
|
int refcount;
|
|
|
|
m2m1shot2_map_images(ctx);
|
|
|
|
INIT_WORK(&ctx->work, m2m1shot2_context_schedule_work);
|
|
|
|
reinit_completion(&ctx->complete);
|
|
|
|
clear_bit(M2M1S2_CTXSTATE_PROCESSED, &ctx->state);
|
|
set_bit(M2M1S2_CTXSTATE_PENDING, &ctx->state);
|
|
/*
|
|
* if there is no fence to wait for, workqueue is not used to push a
|
|
* task
|
|
*/
|
|
refcount = atomic_read(&ctx->starter.refcount);
|
|
if (refcount > 1)
|
|
mod_timer(&ctx->timer,
|
|
jiffies + msecs_to_jiffies(M2M1S2_TIMEOUT_INTERVAL));
|
|
|
|
kref_put(&ctx->starter, m2m1shot2_schedule_queuework);
|
|
}
|
|
|
|
void m2m1shot2_finish_context(struct m2m1shot2_context *ctx, bool success)
|
|
{
|
|
__m2m1shot2_finish_context(ctx, success);
|
|
}
|
|
|
|
void m2m1shot2_schedule(struct m2m1shot2_device *m21dev)
|
|
{
|
|
__m2m1shot2_schedule(m21dev);
|
|
}
|
|
|
|
static void m2m1shot2_cancel_context(struct m2m1shot2_context *ctx)
|
|
{
|
|
unsigned long flags;
|
|
|
|
/*
|
|
* wait until the H/W finishes the processing about the context if
|
|
* the context is being serviced by H/W. This should be the only waiter
|
|
* for the context. If nothing is to be waited for,
|
|
* m2m1shot2_wait_process() returns immediately.
|
|
*/
|
|
m2m1shot2_wait_process(ctx->m21dev, ctx);
|
|
|
|
/*
|
|
* this context is added to m2m1shot2_device.contexts by
|
|
* m2m1shot2_finish_context() called by the client device driver
|
|
*/
|
|
spin_lock_irqsave(&ctx->m21dev->lock_ctx, flags);
|
|
BUG_ON(list_empty(&ctx->node));
|
|
list_del_init(&ctx->node);
|
|
spin_unlock_irqrestore(&ctx->m21dev->lock_ctx, flags);
|
|
|
|
/* signal all possible release fences */
|
|
sw_sync_timeline_inc(ctx->timeline, 1);
|
|
|
|
/* to confirm if no reference to a resource exists */
|
|
if (!!(ctx->flags & M2M1SHOT2_FLAG_NONBLOCK))
|
|
m2m1shot2_put_images(ctx);
|
|
|
|
WARN(!M2M1S2_CTXSTATE_IDLE(ctx),
|
|
"state should be IDLE but %#lx\n", ctx->state);
|
|
}
|
|
|
|
static void m2m1shot2_destroy_context(struct work_struct *work)
|
|
{
|
|
struct m2m1shot2_context *ctx =
|
|
container_of(work, struct m2m1shot2_context, dwork);
|
|
|
|
mutex_lock(&ctx->mutex);
|
|
|
|
m2m1shot2_cancel_context(ctx);
|
|
|
|
sync_timeline_destroy(&ctx->timeline->obj);
|
|
|
|
spin_lock(&ctx->m21dev->lock_priority);
|
|
ctx->m21dev->prior_stats[ctx->priority] -= 1;
|
|
spin_unlock(&ctx->m21dev->lock_priority);
|
|
|
|
if (ctx->m21dev->ops->prepare_perf)
|
|
ctx->m21dev->ops->prepare_perf(ctx, NULL);
|
|
|
|
ctx->m21dev->ops->free_context(ctx);
|
|
|
|
mutex_unlock(&ctx->mutex);
|
|
|
|
kfree(ctx);
|
|
}
|
|
|
|
/*
|
|
* m2m1shot2_release() may be called during the context to be destroyed is ready
|
|
* for processing or currently waiting for completion of processing due to the
|
|
* non-blocking interface. Therefore it should remove the context from
|
|
* m2m1shot2_device.active_contexts if the context is in the list. If it is
|
|
* currently being processed by H/W, this function should wait until the H/W
|
|
* finishes.
|
|
*/
|
|
static int m2m1shot2_release(struct inode *inode, struct file *filp)
|
|
{
|
|
struct m2m1shot2_context *ctx = filp->private_data;
|
|
|
|
INIT_WORK(&ctx->dwork, m2m1shot2_destroy_context);
|
|
/* always success */
|
|
queue_work(ctx->m21dev->destroy_workqueue, &ctx->dwork);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int m2m1shot2_get_userptr(struct m2m1shot2_device *m21dev,
|
|
struct m2m1shot2_dma_buffer *plane,
|
|
unsigned long addr, u32 length,
|
|
enum dma_data_direction dir)
|
|
{
|
|
unsigned long end = addr + length;
|
|
struct vm_area_struct *vma;
|
|
struct vm_area_struct *tvma;
|
|
struct mm_struct *mm;
|
|
int ret = -EINVAL;
|
|
int prot = IOMMU_READ;
|
|
|
|
/*
|
|
* release the previous buffer whatever the userptr is previously used.
|
|
* the pages mapped to the given user addr can be different even though
|
|
* the user addr is the same if the addr is mmapped again.
|
|
*/
|
|
if (plane->userptr.vma != NULL)
|
|
m2m1shot2_put_userptr(m21dev->dev, plane);
|
|
|
|
mm = get_task_mm(current);
|
|
|
|
down_read(&mm->mmap_sem);
|
|
|
|
vma = find_vma(mm, addr);
|
|
if (!vma || (addr < vma->vm_start)) {
|
|
dev_err(m21dev->dev,
|
|
"%s: invalid address %#lx\n", __func__, addr);
|
|
goto err_novma;
|
|
}
|
|
|
|
tvma = kmemdup(vma, sizeof(*vma), GFP_KERNEL);
|
|
if (!tvma) {
|
|
dev_err(m21dev->dev, "%s: failed to allocate vma\n", __func__);
|
|
ret = -ENOMEM;
|
|
goto err_novma;
|
|
}
|
|
|
|
if (dir != DMA_TO_DEVICE)
|
|
prot |= IOMMU_WRITE;
|
|
if (is_vma_cached(vma) && !!(m21dev->attr & M2M1SHOT2_DEVATTR_COHERENT))
|
|
prot |= IOMMU_CACHE;
|
|
|
|
plane->userptr.vma = tvma;
|
|
|
|
tvma->vm_next = NULL;
|
|
tvma->vm_prev = NULL;
|
|
tvma->vm_mm = mm;
|
|
|
|
while (end > vma->vm_end) {
|
|
if (!!(vma->vm_flags & VM_PFNMAP)) {
|
|
dev_err(m21dev->dev,
|
|
"%s: non-linear pfnmap is not supported\n",
|
|
__func__);
|
|
goto err_vma;
|
|
}
|
|
|
|
if ((vma->vm_next == NULL) ||
|
|
(vma->vm_end != vma->vm_next->vm_start)) {
|
|
dev_err(m21dev->dev, "%s: invalid size %u\n",
|
|
__func__, length);
|
|
goto err_vma;
|
|
}
|
|
|
|
vma = vma->vm_next;
|
|
tvma->vm_next = kmemdup(vma, sizeof(*vma), GFP_KERNEL);
|
|
if (tvma->vm_next == NULL) {
|
|
dev_err(m21dev->dev, "%s: failed to allocate vma\n",
|
|
__func__);
|
|
ret = -ENOMEM;
|
|
goto err_vma;
|
|
}
|
|
tvma->vm_next->vm_prev = tvma;
|
|
tvma->vm_next->vm_next = NULL;
|
|
tvma = tvma->vm_next;
|
|
}
|
|
|
|
for (vma = plane->userptr.vma; vma != NULL; vma = vma->vm_next) {
|
|
if (vma->vm_file)
|
|
get_file(vma->vm_file);
|
|
if (vma->vm_ops && vma->vm_ops->open)
|
|
vma->vm_ops->open(vma);
|
|
}
|
|
|
|
plane->dma_addr = exynos_iovmm_map_userptr(
|
|
m21dev->dev, addr, length, prot);
|
|
if (IS_ERR_VALUE(plane->dma_addr))
|
|
goto err_map;
|
|
|
|
up_read(&mm->mmap_sem);
|
|
|
|
plane->userptr.addr = addr;
|
|
plane->userptr.length = length;
|
|
|
|
return 0;
|
|
err_map:
|
|
plane->dma_addr = 0;
|
|
|
|
for (vma = plane->userptr.vma; vma != NULL; vma = vma->vm_next) {
|
|
if (vma->vm_file)
|
|
fput(vma->vm_file);
|
|
if (vma->vm_ops && vma->vm_ops->close)
|
|
vma->vm_ops->close(vma);
|
|
}
|
|
err_vma:
|
|
while (tvma) {
|
|
vma = tvma;
|
|
tvma = tvma->vm_prev;
|
|
kfree(vma);
|
|
}
|
|
|
|
plane->userptr.vma = NULL;
|
|
err_novma:
|
|
up_read(&mm->mmap_sem);
|
|
|
|
mmput(mm);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int m2m1shot2_get_dmabuf(struct m2m1shot2_device *m21dev,
|
|
struct m2m1shot2_dma_buffer *plane,
|
|
int fd, u32 off, size_t payload,
|
|
enum dma_data_direction dir)
|
|
{
|
|
struct dma_buf *dmabuf = dma_buf_get(fd);
|
|
int ret = -EINVAL;
|
|
int prot = IOMMU_READ;
|
|
|
|
if (IS_ERR(dmabuf)) {
|
|
dev_err(m21dev->dev, "%s: failed to get dmabuf from fd %d\n",
|
|
__func__, fd);
|
|
return PTR_ERR(dmabuf);
|
|
}
|
|
|
|
if (dmabuf->size < off) {
|
|
dev_err(m21dev->dev,
|
|
"%s: too large offset %u for dmabuf of %zu\n",
|
|
__func__, off, dmabuf->size);
|
|
goto err;
|
|
}
|
|
|
|
if ((dmabuf->size - off) < payload) {
|
|
dev_err(m21dev->dev,
|
|
"%s: too small dmabuf %zu/%u but reqiured %zu\n",
|
|
__func__, dmabuf->size, off, payload);
|
|
goto err;
|
|
}
|
|
|
|
if ((dmabuf == plane->dmabuf.dmabuf) &&
|
|
(dmabuf->size == plane->dmabuf.dmabuf->size) &&
|
|
(dmabuf->file == plane->dmabuf.dmabuf->file)) {
|
|
/* do not attach dmabuf again for the same buffer */
|
|
dma_buf_put(dmabuf);
|
|
return 0;
|
|
}
|
|
|
|
if (dir != DMA_TO_DEVICE)
|
|
prot |= IOMMU_WRITE;
|
|
if (!!(m21dev->attr & M2M1SHOT2_DEVATTR_COHERENT))
|
|
prot |= IOMMU_CACHE;
|
|
|
|
/* release the previous buffer */
|
|
if (plane->dmabuf.dmabuf != NULL)
|
|
m2m1shot2_put_dmabuf(plane);
|
|
|
|
plane->dmabuf.attachment = dma_buf_attach(dmabuf, m21dev->dev);
|
|
if (IS_ERR(plane->dmabuf.attachment)) {
|
|
dev_err(m21dev->dev,
|
|
"%s: failed to attach to dmabuf\n", __func__);
|
|
ret = PTR_ERR(plane->dmabuf.attachment);
|
|
goto err;
|
|
}
|
|
|
|
/* NOTE: ion_iovmm_map() ignores offset in the second argument */
|
|
plane->dma_addr = ion_iovmm_map(plane->dmabuf.attachment,
|
|
0, payload, dir, prot);
|
|
if (IS_ERR_VALUE(plane->dma_addr))
|
|
goto err_map;
|
|
|
|
plane->dmabuf.dmabuf = dmabuf;
|
|
plane->dma_addr += off;
|
|
|
|
return 0;
|
|
err_map:
|
|
dma_buf_detach(dmabuf, plane->dmabuf.attachment);
|
|
plane->dma_addr = 0;
|
|
plane->dmabuf.attachment = NULL;
|
|
err:
|
|
dma_buf_put(dmabuf);
|
|
return ret;
|
|
}
|
|
|
|
static int m2m1shot2_get_buffer(struct m2m1shot2_context *ctx,
|
|
struct m2m1shot2_context_image *img,
|
|
struct m2m1shot2_image *src,
|
|
size_t payload[],
|
|
enum dma_data_direction dir)
|
|
{
|
|
unsigned int num_planes = src->num_planes;
|
|
int ret = 0;
|
|
unsigned int i;
|
|
|
|
if (src->memory != img->memory)
|
|
m2m1shot2_put_buffer(ctx, img->memory,
|
|
img->plane, img->num_planes);
|
|
|
|
img->memory = M2M1SHOT2_BUFTYPE_NONE;
|
|
|
|
if (src->memory == M2M1SHOT2_BUFTYPE_DMABUF) {
|
|
for (i = 0; i < src->num_planes; i++) {
|
|
ret = m2m1shot2_get_dmabuf(ctx->m21dev,
|
|
&img->plane[i], src->plane[i].fd,
|
|
src->plane[i].offset, payload[i], dir);
|
|
if (ret) {
|
|
while (i-- > 0)
|
|
m2m1shot2_put_dmabuf(&img->plane[i]);
|
|
return ret;
|
|
}
|
|
|
|
img->plane[i].payload = payload[i];
|
|
|
|
if (!(img->flags & M2M1SHOT2_IMGFLAG_NO_CACHECLEAN) &&
|
|
ion_cached_needsync_dmabuf(
|
|
img->plane[i].dmabuf.dmabuf) > 0)
|
|
ctx->ctx_private += img->plane[i].payload;
|
|
if (m2m1shot2_need_shareable_flush(
|
|
img->plane[i].dmabuf.dmabuf, false))
|
|
ctx->ctx_private2 += img->plane[i].payload;
|
|
}
|
|
} else if (src->memory == M2M1SHOT2_BUFTYPE_USERPTR) {
|
|
for (i = 0; i < src->num_planes; i++) {
|
|
if (src->plane[i].offset != 0) {
|
|
dev_err(ctx->m21dev->dev,
|
|
"%s: offset should be 0 with userptr\n",
|
|
__func__);
|
|
ret = -EINVAL;
|
|
} else {
|
|
ret = m2m1shot2_get_userptr(ctx->m21dev,
|
|
&img->plane[i], src->plane[i].userptr,
|
|
src->plane[i].length, dir);
|
|
}
|
|
|
|
if (ret) {
|
|
while (i-- > 0)
|
|
m2m1shot2_put_userptr(ctx->m21dev->dev,
|
|
&img->plane[i]);
|
|
return ret;
|
|
}
|
|
|
|
img->plane[i].payload = payload[i];
|
|
|
|
if (!(img->flags & M2M1SHOT2_IMGFLAG_NO_CACHECLEAN) &&
|
|
is_vma_cached(img->plane[i].userptr.vma))
|
|
ctx->ctx_private += img->plane[i].payload;
|
|
}
|
|
} else {
|
|
dev_err(ctx->m21dev->dev,
|
|
"%s: invalid memory type %d\n", __func__, src->memory);
|
|
return -EINVAL;
|
|
}
|
|
|
|
img->num_planes = num_planes;
|
|
img->memory = src->memory;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* check fence_count fences configured in the previous images in the same
|
|
* context. If it does not find the same fence with the given, register the
|
|
* fence waiter.
|
|
* If registering the fence waiter for an unique fence fails, it releases it
|
|
* immediately. The caller should handle the case.
|
|
*
|
|
*/
|
|
static bool m2m1shot2_check_fence_wait(struct m2m1shot2_context *ctx,
|
|
unsigned int fence_count,
|
|
struct sync_fence *fence,
|
|
struct sync_fence_waiter *waiter)
|
|
{
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
for (i = 0; i < fence_count; i++) {
|
|
if (ctx->source[i].img.fence == fence)
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Always increase ctx->starter before sync_fence_wait_async().
|
|
* If waiter registration is failed (ret < 0)
|
|
* or already signaled (ret == 1). it will be just decreased.
|
|
*/
|
|
kref_get(&ctx->starter);
|
|
ret = sync_fence_wait_async(fence, waiter);
|
|
if (ret < 0 || ret == 1) {
|
|
if (ret < 0)
|
|
dev_err(ctx->m21dev->dev,
|
|
"Error occurred in an acquire fence\n");
|
|
kref_put(&ctx->starter, m2m1shot2_context_schedule_start);
|
|
sync_fence_put(fence);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int m2m1shot2_get_source(struct m2m1shot2_context *ctx,
|
|
unsigned int index,
|
|
struct m2m1shot2_source_image *img,
|
|
struct m2m1shot2_image *src)
|
|
{
|
|
struct device *dev = ctx->m21dev->dev;
|
|
size_t payload[M2M1SHOT2_MAX_PLANES];
|
|
unsigned int i, num_planes;
|
|
int ret;
|
|
|
|
if (!M2M1SHOT2_BUFTYPE_VALID(src->memory)) {
|
|
dev_err(dev,
|
|
"%s: invalid memory type %u specified for image %u\n",
|
|
__func__, src->memory, index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
img->img.fmt.fmt = src->fmt;
|
|
img->img.flags = src->flags;
|
|
img->ext = src->ext;
|
|
img->img.fmt.colorspace = src->colorspace;
|
|
|
|
ret = ctx->m21dev->ops->prepare_format(&img->img.fmt, index,
|
|
DMA_TO_DEVICE, payload, &num_planes);
|
|
if (ret) {
|
|
dev_err(dev, "%s: invalid format specified for image %u\n",
|
|
__func__, index);
|
|
return ret;
|
|
}
|
|
|
|
if (src->memory == M2M1SHOT2_BUFTYPE_EMPTY) {
|
|
m2m1shot2_put_image(ctx, &img->img);
|
|
img->img.memory = src->memory;
|
|
img->img.num_planes = 0;
|
|
/* no buffer, no fence */
|
|
img->img.flags &= ~(M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE |
|
|
M2M1SHOT2_IMGFLAG_RELEASE_FENCE);
|
|
return 0;
|
|
}
|
|
|
|
BUG_ON((num_planes < 1) || (num_planes > M2M1SHOT2_MAX_PLANES));
|
|
|
|
if (num_planes != src->num_planes) {
|
|
dev_err(dev, "%s: wrong number of planes %u of image %u.\n",
|
|
__func__, src->num_planes, index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < num_planes; i++) {
|
|
if (src->plane[i].length < payload[i]) {
|
|
dev_err(dev,
|
|
"%s: too small size %u (plane %u / image %u)\n",
|
|
__func__, src->plane[i].length, i, index);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return m2m1shot2_get_buffer(ctx, &img->img,
|
|
src, payload, DMA_TO_DEVICE);
|
|
}
|
|
|
|
static int m2m1shot2_get_sources(struct m2m1shot2_context *ctx,
|
|
struct m2m1shot2_image __user *usersrc,
|
|
bool blocking)
|
|
{
|
|
struct device *dev = ctx->m21dev->dev;
|
|
struct m2m1shot2_source_image *image = ctx->source;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
for (i = 0; i < ctx->num_sources; i++, usersrc++) {
|
|
struct m2m1shot2_image source;
|
|
|
|
if (copy_from_user(&source, usersrc, sizeof(source))) {
|
|
dev_err(dev,
|
|
"%s: Failed to read source[%u] image data\n",
|
|
__func__, i);
|
|
ret = -EFAULT;
|
|
goto err;
|
|
}
|
|
|
|
/* blocking mode does not allow fences */
|
|
if (blocking && !!(source.flags & M2M1SHOT2_FENCE_MASK)) {
|
|
dev_err(dev, "%s: fence set for blocking mode\n",
|
|
__func__);
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
ret = m2m1shot2_get_source(ctx, i, &image[i], &source);
|
|
if (ret)
|
|
goto err;
|
|
|
|
ret = ctx->m21dev->ops->prepare_source(ctx, i, &image[i]);
|
|
if (ret) {
|
|
m2m1shot2_put_image(ctx, &image[i].img);
|
|
goto err;
|
|
}
|
|
|
|
image[i].img.fence = NULL;
|
|
|
|
if (!!(image[i].img.flags & M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE)) {
|
|
image[i].img.fence = sync_fence_fdget(source.fence);
|
|
if (image[i].img.fence == NULL) {
|
|
dev_err(dev, "%s: invalid acquire fence %d\n",
|
|
__func__, source.fence);
|
|
ret = -EINVAL;
|
|
image[i].img.flags &=
|
|
~M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE;
|
|
m2m1shot2_put_image(ctx, &image[i].img);
|
|
goto err;
|
|
}
|
|
|
|
if (!m2m1shot2_check_fence_wait(ctx, i,
|
|
image[i].img.fence,
|
|
&image[i].img.waiter))
|
|
image[i].img.flags &=
|
|
~M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
err:
|
|
while (i-- > 0)
|
|
m2m1shot2_put_image(ctx, &ctx->source[i].img);
|
|
|
|
ctx->num_sources = 0;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
static struct sync_fence *m2m1shot2_create_fence(struct m2m1shot2_context *ctx)
|
|
{
|
|
struct device *dev = ctx->m21dev->dev;
|
|
struct sync_fence *fence;
|
|
struct sync_pt *pt;
|
|
|
|
pt = sw_sync_pt_create(ctx->timeline, ctx->timeline_max + 1);
|
|
if (!pt) {
|
|
dev_err(dev,
|
|
"%s: failed to create sync_pt\n", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
fence = sync_fence_create("m2m1shot2", pt);
|
|
if (!fence) {
|
|
dev_err(dev, "%s: failed to create fence\n", __func__);
|
|
sync_pt_free(pt);
|
|
}
|
|
|
|
return fence;
|
|
}
|
|
|
|
static int m2m1shot2_get_target(struct m2m1shot2_context *ctx,
|
|
struct m2m1shot2_image *dst)
|
|
{
|
|
struct device *dev = ctx->m21dev->dev;
|
|
struct m2m1shot2_context_image *img = &ctx->target;
|
|
size_t payload[M2M1SHOT2_MAX_PLANES];
|
|
unsigned int i, num_planes;
|
|
int ret;
|
|
|
|
if (!M2M1SHOT2_BUFTYPE_VALID(dst->memory)) {
|
|
dev_err(dev,
|
|
"%s: invalid memory type %u specified for target\n",
|
|
__func__, dst->memory);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dst->memory == M2M1SHOT2_BUFTYPE_EMPTY) {
|
|
dev_err(dev,
|
|
"%s: M2M1SHOT2_BUFTYPE_EMPTY is not valid for target\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
img->fmt.fmt = dst->fmt;
|
|
img->fmt.colorspace = dst->colorspace;
|
|
|
|
/*
|
|
* The client driver may configure 0 to payload if it is not able to
|
|
* determine the payload before image processing especially when the
|
|
* result is a compressed data
|
|
*/
|
|
ret = ctx->m21dev->ops->prepare_format(&img->fmt, 0,
|
|
DMA_FROM_DEVICE, payload, &num_planes);
|
|
if (ret) {
|
|
dev_err(dev, "%s: invalid format specified for target\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
|
|
BUG_ON((num_planes < 1) || (num_planes > M2M1SHOT2_MAX_PLANES));
|
|
|
|
if (num_planes != dst->num_planes) {
|
|
dev_err(dev, "%s: wrong number of planes %u for target\n",
|
|
__func__, dst->num_planes);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < num_planes; i++) {
|
|
if ((payload[i] != 0) && (dst->plane[i].length < payload[i])) {
|
|
dev_err(dev,
|
|
"%s: too small size %u (plane %u)\n",
|
|
__func__, dst->plane[i].length, i);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
ret = m2m1shot2_get_buffer(ctx, img, dst, payload, DMA_FROM_DEVICE);
|
|
if (ret)
|
|
return ret;
|
|
|
|
img->flags = dst->flags;
|
|
img->fence = NULL;
|
|
|
|
if (!!(img->flags & M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE)) {
|
|
img->fence = sync_fence_fdget(dst->fence);
|
|
if (img->fence == NULL) {
|
|
dev_err(dev, "%s: invalid acquire fence %d\n",
|
|
__func__, dst->fence);
|
|
img->flags &= ~M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE;
|
|
m2m1shot2_put_buffer(ctx, dst->memory,
|
|
img->plane, img->num_planes);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!m2m1shot2_check_fence_wait(ctx, ctx->num_sources,
|
|
img->fence, &img->waiter))
|
|
img->flags &= ~M2M1SHOT2_IMGFLAG_ACQUIRE_FENCE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int m2m1shot2_get_userdata(struct m2m1shot2_context *ctx,
|
|
struct m2m1shot2 *data)
|
|
{
|
|
struct device *dev = ctx->m21dev->dev;
|
|
struct m2m1shot2_image __user *usersrc = data->sources;
|
|
unsigned int len_src, len_dst;
|
|
bool blocking;
|
|
int ret;
|
|
|
|
if ((data->num_sources < 1) ||
|
|
(data->num_sources > M2M1SHOT2_MAX_IMAGES)) {
|
|
dev_err(dev, "%s: Invalid number of source images %u\n",
|
|
__func__, data->num_sources);
|
|
return -EINVAL;
|
|
}
|
|
|
|
blocking = !(data->flags & M2M1SHOT2_FLAG_NONBLOCK);
|
|
|
|
while (ctx->num_sources > data->num_sources) {
|
|
ctx->num_sources--;
|
|
m2m1shot2_put_image(ctx, &ctx->source[ctx->num_sources].img);
|
|
}
|
|
|
|
ctx->ctx_private = 0;
|
|
ctx->ctx_private2 = 0;
|
|
ctx->num_sources = data->num_sources;
|
|
ctx->flags = data->flags;
|
|
|
|
ret = m2m1shot2_get_sources(ctx, usersrc, blocking);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (blocking && !!(data->target.flags & M2M1SHOT2_FENCE_MASK)) {
|
|
dev_err(dev, "%s: fence set for blocking mode\n", __func__);
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
len_src = ctx->ctx_private;
|
|
|
|
ret = m2m1shot2_get_target(ctx, &data->target);
|
|
if (ret)
|
|
goto err;
|
|
|
|
ret = ctx->m21dev->ops->prepare_target(ctx, &ctx->target);
|
|
if (ret)
|
|
goto err;
|
|
|
|
len_dst = ctx->ctx_private - len_src;
|
|
|
|
if (ctx->ctx_private < M2M1SHOT2_NEED_CACHEFLUSH_ALL)
|
|
clear_bit(M2M1S2_CTXSTATE_CACHECLEANALL, &ctx->state);
|
|
else
|
|
set_bit(M2M1S2_CTXSTATE_CACHECLEANALL, &ctx->state);
|
|
|
|
if (ctx->ctx_private2 > 0)
|
|
set_bit(M2M1S2_CTXSTATE_CACHEFLUSH, &ctx->state);
|
|
else
|
|
clear_bit(M2M1S2_CTXSTATE_CACHEFLUSH, &ctx->state);
|
|
|
|
if (ctx->ctx_private2 < M2M1SHOT2_NEED_CACHEFLUSH_ALL)
|
|
clear_bit(M2M1S2_CTXSTATE_CACHEFLUSHALL, &ctx->state);
|
|
else
|
|
set_bit(M2M1S2_CTXSTATE_CACHEFLUSHALL, &ctx->state);
|
|
|
|
if (!(ctx->target.flags & M2M1SHOT2_IMGFLAG_NO_CACHEINV) &&
|
|
(len_dst < M2M1SHOT2_NEED_CACHEFLUSH_ALL))
|
|
clear_bit(M2M1S2_CTXSTATE_CACHEINVALALL, &ctx->state);
|
|
else
|
|
set_bit(M2M1S2_CTXSTATE_CACHEINVALALL, &ctx->state);
|
|
|
|
return 0;
|
|
err:
|
|
m2m1shot2_put_source_images(ctx);
|
|
return ret;
|
|
}
|
|
|
|
static int m2m1shot2_install_fence(struct m2m1shot2_context *ctx,
|
|
struct sync_fence *fence,
|
|
s32 __user *pfd)
|
|
{
|
|
int fd = get_unused_fd_flags(O_CLOEXEC);
|
|
|
|
if (fd < 0) {
|
|
dev_err(ctx->m21dev->dev, "%s: failed to allocated unused fd\n",
|
|
__func__);
|
|
return fd;
|
|
}
|
|
|
|
if (put_user(fd, pfd)) {
|
|
dev_err(ctx->m21dev->dev,
|
|
"%s: failed to put release fence to user\n", __func__);
|
|
put_unused_fd(fd);
|
|
return -EFAULT;
|
|
}
|
|
|
|
sync_fence_install(fence, fd);
|
|
|
|
return fd;
|
|
}
|
|
|
|
static int m2m1shot2_create_release_fence(struct m2m1shot2_context *ctx,
|
|
struct m2m1shot2_image __user *usertgt,
|
|
struct m2m1shot2_image __user usersrc[],
|
|
unsigned int num_sources)
|
|
{
|
|
struct sync_fence *fence = NULL;
|
|
struct m2m1shot2_context_image *img;
|
|
unsigned int i, ifd = 0;
|
|
int fds[M2M1SHOT2_MAX_IMAGES + 1];
|
|
unsigned int num_fences = 0;
|
|
int ret = 0;
|
|
|
|
if (!!(ctx->target.flags & M2M1SHOT2_IMGFLAG_RELEASE_FENCE))
|
|
num_fences++;
|
|
|
|
for (i = 0; i < ctx->num_sources; i++) {
|
|
if (!!(ctx->source[i].img.flags &
|
|
M2M1SHOT2_IMGFLAG_RELEASE_FENCE))
|
|
num_fences++;
|
|
}
|
|
|
|
if (num_fences == 0)
|
|
return 0;
|
|
|
|
fence = m2m1shot2_create_fence(ctx);
|
|
if (!fence)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < ctx->num_sources; i++) {
|
|
img = &ctx->source[i].img;
|
|
if (!!(img->flags & M2M1SHOT2_IMGFLAG_RELEASE_FENCE)) {
|
|
ret = m2m1shot2_install_fence(ctx,
|
|
fence, &usersrc[i].fence);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
get_file(fence->file);
|
|
fds[ifd++] = ret;
|
|
}
|
|
}
|
|
|
|
img = &ctx->target;
|
|
if (!!(ctx->target.flags & M2M1SHOT2_IMGFLAG_RELEASE_FENCE)) {
|
|
ret = m2m1shot2_install_fence(ctx, fence,
|
|
&usertgt->fence);
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
get_file(fence->file);
|
|
fds[ifd++] = ret;
|
|
}
|
|
|
|
/* release a reference of the fence that is increased on creation */
|
|
sync_fence_put(fence);
|
|
|
|
ctx->timeline_max++;
|
|
ctx->release_fence = fence;
|
|
|
|
return 0;
|
|
err:
|
|
while (ifd-- > 0) {
|
|
put_unused_fd(fds[ifd]);
|
|
sync_fence_put(fence);
|
|
}
|
|
|
|
sync_fence_put(fence);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int m2m1shot2_wait_put_user(struct m2m1shot2_context *ctx,
|
|
struct m2m1shot2 __user *uptr, u32 userflag)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!m2m1shot2_wait_process(ctx->m21dev, ctx)) {
|
|
userflag |= M2M1SHOT2_FLAG_ERROR;
|
|
ret = put_user(userflag, &uptr->flags);
|
|
} else {
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ctx->target.num_planes; i++)
|
|
ret = put_user(ctx->target.plane[i].payload,
|
|
&uptr->target.plane[i].payload);
|
|
put_user(ctx->work_delay_in_nsec, &uptr->work_delay_in_nsec);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* find the previous requested context which has lower priority.
|
|
* if it find that context, we have to return error to avoid
|
|
* for the higher context to wait finishing the lower context job
|
|
*/
|
|
static int m2m1shot2_prepare_priority_context(struct m2m1shot2_context *ctx)
|
|
{
|
|
struct m2m1shot2_device *m21dev = ctx->m21dev;
|
|
struct m2m1shot2_context *pctx;
|
|
unsigned long flags;
|
|
int cond = ctx->priority;
|
|
|
|
spin_lock_irqsave(&m21dev->lock_ctx, flags);
|
|
list_for_each_entry(pctx, &m21dev->active_contexts, node) {
|
|
if (pctx->priority < cond)
|
|
goto err_busy;
|
|
}
|
|
|
|
list_for_each_entry(pctx, &m21dev->contexts, node) {
|
|
if ((pctx->priority < cond) && M2M1S2_CTXSTATE_BUSY(pctx))
|
|
goto err_busy;
|
|
}
|
|
|
|
pctx = m2m1shot2_current_context(m21dev);
|
|
if (pctx && (pctx->priority < cond))
|
|
goto err_busy;
|
|
|
|
spin_unlock_irqrestore(&m21dev->lock_ctx, flags);
|
|
|
|
return 0;
|
|
|
|
err_busy:
|
|
spin_unlock_irqrestore(&m21dev->lock_ctx, flags);
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
static long m2m1shot2_ioctl(struct file *filp,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct m2m1shot2_context *ctx = filp->private_data;
|
|
struct m2m1shot2_device *m21dev = ctx->m21dev;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&ctx->mutex);
|
|
|
|
switch (cmd) {
|
|
case M2M1SHOT2_IOC_REQUEST_PERF:
|
|
{
|
|
struct m2m1shot2_performance_data data;
|
|
int i;
|
|
|
|
if (!ctx->m21dev->ops->prepare_perf) {
|
|
dev_err(ctx->m21dev->dev,
|
|
"%s: it doesn't exist the prepare performance\n", __func__);
|
|
ret = -ENOTTY;
|
|
break;
|
|
}
|
|
|
|
if (copy_from_user(&data, (void __user *)arg, sizeof(data))) {
|
|
dev_err(ctx->m21dev->dev,
|
|
"%s: Failed to performance data\n", __func__);
|
|
ret = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
if (data.num_frames >= M2M1SHOT2_PERF_MAX_FRAMES) {
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < data.num_frames; i++) {
|
|
if (data.frame[i].num_layers >= M2M1SHOT2_MAX_IMAGES) {
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
if (!ret)
|
|
ret = ctx->m21dev->ops->prepare_perf(ctx, &data);
|
|
|
|
break;
|
|
}
|
|
case M2M1SHOT2_IOC_SET_PRIORITY:
|
|
{
|
|
enum m2m1shot2_priority data;
|
|
|
|
get_user(data, (enum m2m1shot2_priority __user *)arg);
|
|
|
|
if ((data < M2M1SHOT2_LOW_PRIORITY) ||
|
|
(data >= M2M1SHOT2_PRIORITY_END)) {
|
|
dev_err(m21dev->dev,
|
|
"%s: m2m1shot2 does not allow %d priority\n",
|
|
__func__, data);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
/* update current priority */
|
|
if (data != ctx->priority) {
|
|
spin_lock(&m21dev->lock_priority);
|
|
m21dev->prior_stats[ctx->priority] -= 1;
|
|
m21dev->prior_stats[data] += 1;
|
|
ctx->priority = data;
|
|
spin_unlock(&m21dev->lock_priority);
|
|
}
|
|
|
|
ret = m2m1shot2_prepare_priority_context(ctx);
|
|
if (ret < 0)
|
|
break;
|
|
|
|
break;
|
|
}
|
|
case M2M1SHOT2_IOC_PROCESS:
|
|
{
|
|
struct m2m1shot2 __user *uptr = (struct m2m1shot2 __user *)arg;
|
|
struct m2m1shot2 data;
|
|
int i;
|
|
|
|
if (copy_from_user(&data, uptr, sizeof(data))) {
|
|
dev_err(ctx->m21dev->dev,
|
|
"%s: Failed to read userdata\n", __func__);
|
|
ret = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
/* wait for completion of the previous task */
|
|
if (!M2M1S2_CTXSTATE_IDLE(ctx))
|
|
m2m1shot2_wait_process(m21dev, ctx);
|
|
|
|
/*
|
|
* A new process request with the lower priority than the
|
|
* heighest priority currently configured is not allowed
|
|
* to be executed and m2m1shot2 simply returns -EBUSY
|
|
*/
|
|
spin_lock(&m21dev->lock_priority);
|
|
for (i = ctx->priority + 1; i < M2M1SHOT2_PRIORITY_END; i++) {
|
|
if (m21dev->prior_stats[i] > 0)
|
|
break;
|
|
}
|
|
spin_unlock(&m21dev->lock_priority);
|
|
|
|
if (i < M2M1SHOT2_PRIORITY_END) {
|
|
ret = -EBUSY;
|
|
break;
|
|
}
|
|
|
|
kref_init(&ctx->starter);
|
|
|
|
ret = m2m1shot2_get_userdata(ctx, &data);
|
|
if (ret < 0)
|
|
break;
|
|
|
|
ctx->release_fence = NULL;
|
|
if (!!(data.flags & M2M1SHOT2_FLAG_NONBLOCK)) {
|
|
ret = m2m1shot2_create_release_fence(ctx, &uptr->target,
|
|
data.sources, data.num_sources);
|
|
if (ret < 0) {
|
|
m2m1shot2_put_images(ctx);
|
|
break;
|
|
}
|
|
}
|
|
|
|
m2m1shot2_start_context(ctx->m21dev, ctx);
|
|
|
|
if (!(data.flags & M2M1SHOT2_FLAG_NONBLOCK)) {
|
|
ret = m2m1shot2_wait_put_user(ctx, uptr, data.flags);
|
|
m2m1shot2_put_images(ctx);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case M2M1SHOT2_IOC_WAIT_PROCESS:
|
|
{
|
|
struct m2m1shot2 __user *uptr = (void __user *)arg;
|
|
struct m2m1shot2 data;
|
|
|
|
if (M2M1S2_CTXSTATE_IDLE(ctx)) {
|
|
ret = -EAGAIN;
|
|
break;
|
|
}
|
|
|
|
if (copy_from_user(&data, uptr, sizeof(data))) {
|
|
dev_err(ctx->m21dev->dev,
|
|
"%s: Failed to read userdata\n", __func__);
|
|
ret = -EFAULT;
|
|
break;
|
|
}
|
|
|
|
ret = m2m1shot2_wait_put_user(ctx, uptr, data.flags);
|
|
|
|
break;
|
|
}
|
|
case M2M1SHOT2_IOC_CUSTOM:
|
|
{
|
|
struct m2m1shot2_custom_data data;
|
|
|
|
if (!ctx->m21dev->ops->custom_ioctl) {
|
|
dev_err(ctx->m21dev->dev,
|
|
"%s: it doesn't exist the custom ioctl\n", __func__);
|
|
ret = -ENOTTY;
|
|
break;
|
|
}
|
|
if (copy_from_user(&data, (void __user *)arg, sizeof(data))) {
|
|
dev_err(ctx->m21dev->dev,
|
|
"%s: Failed to read userdata\n", __func__);
|
|
ret = -EFAULT;
|
|
break;
|
|
}
|
|
ret = ctx->m21dev->ops->custom_ioctl(ctx, data.cmd, data.arg);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
dev_err(ctx->m21dev->dev,
|
|
"%s: unknown ioctl command %#x\n", __func__, cmd);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
} /* switch */
|
|
|
|
mutex_unlock(&ctx->mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
struct compat_m2m1shot2_buffer {
|
|
union {
|
|
compat_ulong_t userptr;
|
|
__s32 fd;
|
|
};
|
|
__u32 offset;
|
|
__u32 length;
|
|
__u32 payload;
|
|
compat_ulong_t reserved;
|
|
};
|
|
|
|
struct compat_m2m1shot2_image {
|
|
__u32 flags;
|
|
__s32 fence;
|
|
__u8 memory;
|
|
__u8 num_planes;
|
|
struct compat_m2m1shot2_buffer plane[M2M1SHOT2_MAX_PLANES];
|
|
struct m2m1shot2_format fmt;
|
|
struct m2m1shot2_extra ext;
|
|
__u32 reserved[4];
|
|
};
|
|
|
|
struct compat_m2m1shot2 {
|
|
compat_uptr_t sources;
|
|
struct compat_m2m1shot2_image target;
|
|
__u8 num_sources;
|
|
__u32 flags;
|
|
__u32 reserved1;
|
|
__u32 reserved2;
|
|
compat_ulong_t work_delay_in_nsec;
|
|
compat_ulong_t reserved4;
|
|
};
|
|
|
|
#define COMPAT_M2M1SHOT2_IOC_PROCESS _IOWR('M', 4, struct compat_m2m1shot2)
|
|
#define COMPAT_M2M1SHOT2_IOC_WAIT_PROCESS _IOR('M', 5, struct compat_m2m1shot2)
|
|
|
|
static int m2m1shot2_compat_get_imagedata(struct device *dev,
|
|
struct m2m1shot2_image __user *img,
|
|
struct compat_m2m1shot2_image __user *cimg)
|
|
{
|
|
__u32 uw;
|
|
__s32 sw;
|
|
__u8 b;
|
|
compat_ulong_t l;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
ret = get_user(uw, &cimg->flags);
|
|
ret |= put_user(uw, &img->flags);
|
|
ret |= get_user(sw, &cimg->fence);
|
|
ret |= put_user(sw, &img->fence);
|
|
ret |= get_user(b, &cimg->memory);
|
|
ret |= put_user(b, &img->memory);
|
|
ret |= get_user(b, &cimg->num_planes);
|
|
ret |= put_user(b, &img->num_planes);
|
|
if (b > M2M1SHOT2_MAX_PLANES) {
|
|
dev_err(dev, "%s: too many plane count %u\n", __func__, b);
|
|
return -EINVAL;
|
|
}
|
|
for (i = 0; i < b; i++) { /* b contains num_planes */
|
|
/* userptr is not smaller than fd */
|
|
ret |= get_user(l, &cimg->plane[i].userptr);
|
|
ret |= put_user(l, &img->plane[i].userptr);
|
|
ret |= get_user(uw, &cimg->plane[i].offset);
|
|
ret |= put_user(uw, &img->plane[i].offset);
|
|
ret |= get_user(uw, &cimg->plane[i].length);
|
|
ret |= put_user(uw, &img->plane[i].length);
|
|
ret |= get_user(uw, &cimg->plane[i].payload);
|
|
ret |= put_user(uw, &img->plane[i].payload);
|
|
ret |= get_user(l, &cimg->plane[i].reserved);
|
|
ret |= put_user(l, &img->plane[i].reserved);
|
|
}
|
|
|
|
ret |= copy_in_user(&img->fmt, &cimg->fmt,
|
|
sizeof(struct m2m1shot2_format) +
|
|
sizeof(struct m2m1shot2_extra) + sizeof(__u32) * 4);
|
|
|
|
return ret ? -EFAULT : 0;
|
|
}
|
|
|
|
static int m2m1shot2_compat_put_imagedata(struct m2m1shot2_image __user *img,
|
|
struct compat_m2m1shot2_image __user *cimg)
|
|
{
|
|
__u32 uw;
|
|
__s32 sw;
|
|
__u8 b;
|
|
compat_ulong_t l;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
ret = get_user(uw, &img->flags);
|
|
ret |= put_user(uw, &cimg->flags);
|
|
if (!!(uw & M2M1SHOT2_IMGFLAG_RELEASE_FENCE)) {
|
|
ret |= get_user(sw, &img->fence);
|
|
ret |= put_user(sw, &cimg->fence);
|
|
}
|
|
ret |= get_user(b, &img->num_planes);
|
|
for (i = 0; i < b; i++) { /* b contains num_planes */
|
|
ret |= get_user(uw, &img->plane[i].payload);
|
|
ret |= put_user(uw, &cimg->plane[i].payload);
|
|
ret |= get_user(l, &img->plane[i].reserved);
|
|
ret |= put_user(l, &cimg->plane[i].reserved);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int m2m1shot2_compat_put_fence(struct m2m1shot2_image __user *img,
|
|
struct compat_m2m1shot2_image __user *cimg)
|
|
{
|
|
__u32 uw = 0;
|
|
__s32 sw;
|
|
int ret;
|
|
|
|
ret = get_user(uw, &img->flags);
|
|
if (!!(uw & M2M1SHOT2_IMGFLAG_RELEASE_FENCE)) {
|
|
ret |= get_user(sw, &img->fence);
|
|
ret |= put_user(sw, &cimg->fence);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static long m2m1shot2_compat_ioctl32(struct file *filp,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct m2m1shot2_context *ctx = filp->private_data;
|
|
struct device *dev = ctx->m21dev->dev;
|
|
struct compat_m2m1shot2 __user *cdata = compat_ptr(arg);
|
|
struct m2m1shot2 __user *data;
|
|
struct m2m1shot2_image __user *src;
|
|
int ret;
|
|
compat_ulong_t l;
|
|
compat_uptr_t sources;
|
|
__u32 w;
|
|
__u8 num_sources;
|
|
|
|
switch (cmd) {
|
|
case COMPAT_M2M1SHOT2_IOC_PROCESS:
|
|
cmd = M2M1SHOT2_IOC_PROCESS;
|
|
break;
|
|
case COMPAT_M2M1SHOT2_IOC_WAIT_PROCESS:
|
|
cmd = M2M1SHOT2_IOC_WAIT_PROCESS;
|
|
break;
|
|
case M2M1SHOT2_IOC_CUSTOM:
|
|
case M2M1SHOT2_IOC_SET_PRIORITY:
|
|
case M2M1SHOT2_IOC_REQUEST_PERF:
|
|
if (!filp->f_op->unlocked_ioctl)
|
|
return -ENOTTY;
|
|
|
|
return filp->f_op->unlocked_ioctl(filp, cmd,
|
|
(unsigned long)compat_ptr(arg));
|
|
default:
|
|
dev_err(dev, "%s: unknown ioctl command %#x\n", __func__, cmd);
|
|
return -EINVAL;
|
|
}
|
|
|
|
data = compat_alloc_user_space(sizeof(*data));
|
|
|
|
ret = get_user(num_sources, &cdata->num_sources);
|
|
ret |= put_user(num_sources, &data->num_sources);
|
|
ret |= get_user(w, &cdata->flags);
|
|
ret |= put_user(w, &data->flags);
|
|
ret |= get_user(w, &cdata->reserved1);
|
|
ret |= put_user(w, &data->reserved1);
|
|
ret |= get_user(w, &cdata->reserved2);
|
|
ret |= put_user(w, &data->reserved2);
|
|
ret |= get_user(l, &cdata->work_delay_in_nsec);
|
|
ret |= put_user(l, &data->work_delay_in_nsec);
|
|
ret |= get_user(l, &cdata->reserved4);
|
|
ret |= put_user(l, &data->reserved4);
|
|
ret |= get_user(sources, &cdata->sources);
|
|
if (ret) {
|
|
dev_err(dev, "%s: failed to read m2m1shot2 data\n", __func__);
|
|
return -EFAULT;
|
|
}
|
|
|
|
ret = m2m1shot2_compat_get_imagedata(
|
|
dev, &data->target, &cdata->target);
|
|
if (ret) {
|
|
dev_err(dev, "%s: failed to read the target image data\n",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
|
|
if (cmd == M2M1SHOT2_IOC_PROCESS) {
|
|
struct compat_m2m1shot2_image __user *csc = compat_ptr(sources);
|
|
|
|
src = compat_alloc_user_space(
|
|
sizeof(*data) + sizeof(*src) * num_sources);
|
|
if (w > M2M1SHOT2_MAX_IMAGES) {
|
|
dev_err(dev, "%s: too many source images %u\n",
|
|
__func__, w);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (w = 0; w < num_sources; w++) {
|
|
ret = m2m1shot2_compat_get_imagedata(
|
|
dev, &src[w], &csc[w]);
|
|
if (ret) {
|
|
dev_err(dev,
|
|
"%s: failed to read %dth source image data\n",
|
|
__func__, w);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
if (put_user(src, &data->sources)) {
|
|
dev_err(dev, "%s: failed to handle source image data\n",
|
|
__func__);
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
|
|
ret = m2m1shot2_ioctl(filp, cmd, (unsigned long)data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = get_user(w, &data->flags);
|
|
ret |= put_user(w, &cdata->flags);
|
|
ret |= get_user(w, &data->reserved1);
|
|
ret |= put_user(w, &cdata->reserved1);
|
|
ret |= get_user(w, &data->reserved2);
|
|
ret |= put_user(w, &cdata->reserved2);
|
|
ret |= get_user(l, &data->work_delay_in_nsec);
|
|
ret |= put_user(l, &cdata->work_delay_in_nsec);
|
|
ret |= get_user(l, &data->reserved4);
|
|
ret |= put_user(l, &cdata->reserved4);
|
|
ret |= m2m1shot2_compat_put_imagedata(&data->target, &cdata->target);
|
|
|
|
if (cmd == M2M1SHOT2_IOC_PROCESS) {
|
|
struct compat_m2m1shot2_image __user *csc = compat_ptr(sources);
|
|
|
|
for (w = 0; w < num_sources; w++)
|
|
ret |= m2m1shot2_compat_put_fence(&src[w], &csc[w]);
|
|
}
|
|
|
|
if (ret)
|
|
dev_err(dev, "%s: failed to write userdata\n", __func__);
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static const struct file_operations m2m1shot2_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = m2m1shot2_open,
|
|
.release = m2m1shot2_release,
|
|
.unlocked_ioctl = m2m1shot2_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = m2m1shot2_compat_ioctl32,
|
|
#endif
|
|
};
|
|
|
|
struct m2m1shot2_device *m2m1shot2_create_device(struct device *dev,
|
|
const struct m2m1shot2_devops *ops,
|
|
const char *nodename, int id,
|
|
unsigned long attr)
|
|
{
|
|
struct m2m1shot2_device *m21dev;
|
|
char *name;
|
|
size_t name_size;
|
|
int ret = -ENOMEM;
|
|
|
|
if (!ops || !ops->init_context || !ops->free_context ||
|
|
!ops->prepare_format || !ops->device_run) {
|
|
dev_err(dev,
|
|
"%s: m2m1shot2_devops is insufficient\n", __func__);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
if (!nodename) {
|
|
dev_err(dev, "%s: node name is not specified\n", __func__);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
name_size = strlen(nodename) + 1;
|
|
|
|
if (id >= 0)
|
|
name_size += 3; /* instance number: maximum 3 digits */
|
|
|
|
name = kmalloc(name_size, GFP_KERNEL);
|
|
if (!name)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
if (id < 0)
|
|
strncpy(name, nodename, name_size);
|
|
else
|
|
scnprintf(name, name_size, "%s%d", nodename, id);
|
|
|
|
m21dev = kzalloc(sizeof(*m21dev), GFP_KERNEL);
|
|
if (!m21dev)
|
|
goto err_m21dev;
|
|
|
|
m21dev->misc.minor = MISC_DYNAMIC_MINOR;
|
|
m21dev->misc.name = name;
|
|
m21dev->misc.fops = &m2m1shot2_fops;
|
|
ret = misc_register(&m21dev->misc);
|
|
if (ret)
|
|
goto err_misc;
|
|
|
|
m21dev->schedule_workqueue =
|
|
create_singlethread_workqueue("m2m1shot2-scheduler");
|
|
if (!m21dev->schedule_workqueue) {
|
|
dev_err(dev, "failed to create workqueue for scheduling\n");
|
|
ret = -ENOMEM;
|
|
goto err_workqueue;
|
|
}
|
|
|
|
m21dev->destroy_workqueue =
|
|
create_singlethread_workqueue("m2m1shot2-destructor");
|
|
if (!m21dev->schedule_workqueue) {
|
|
dev_err(dev, "failed to create workqueue for context destruction\n");
|
|
ret = -ENOMEM;
|
|
goto err_workqueue2;
|
|
}
|
|
|
|
spin_lock_init(&m21dev->lock_ctx);
|
|
spin_lock_init(&m21dev->lock_priority);
|
|
INIT_LIST_HEAD(&m21dev->contexts);
|
|
INIT_LIST_HEAD(&m21dev->active_contexts);
|
|
|
|
m21dev->dev = dev;
|
|
m21dev->ops = ops;
|
|
m21dev->attr = attr;
|
|
|
|
dev_info(dev, "registered as m2m1shot2 device");
|
|
|
|
return m21dev;
|
|
err_workqueue2:
|
|
destroy_workqueue(m21dev->schedule_workqueue);
|
|
err_workqueue:
|
|
misc_deregister(&m21dev->misc);
|
|
err_misc:
|
|
kfree(m21dev);
|
|
err_m21dev:
|
|
kfree(name);
|
|
|
|
return ERR_PTR(ret);
|
|
}
|
|
EXPORT_SYMBOL(m2m1shot2_create_device);
|
|
|
|
void m2m1shot2_destroy_device(struct m2m1shot2_device *m21dev)
|
|
{
|
|
destroy_workqueue(m21dev->destroy_workqueue);
|
|
destroy_workqueue(m21dev->schedule_workqueue);
|
|
misc_deregister(&m21dev->misc);
|
|
kfree(m21dev->misc.name);
|
|
kfree(m21dev);
|
|
/* TODO: something forgot to release? */
|
|
}
|
|
EXPORT_SYMBOL(m2m1shot2_destroy_device);
|