root/drivers/gpu/drm/msm/msm_syncobj.c
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2020 Google, Inc */

#include "drm/drm_drv.h"

#include "msm_drv.h"
#include "msm_syncobj.h"

struct drm_syncobj **
msm_syncobj_parse_deps(struct drm_device *dev,
                       struct drm_sched_job *job,
                       struct drm_file *file,
                       uint64_t in_syncobjs_addr,
                       uint32_t nr_in_syncobjs,
                       size_t syncobj_stride)
{
        struct drm_syncobj **syncobjs = NULL;
        struct drm_msm_syncobj syncobj_desc = {0};
        int ret = 0;
        uint32_t i, j;

        syncobjs = kzalloc_objs(*syncobjs, nr_in_syncobjs,
                                GFP_KERNEL | __GFP_NOWARN | __GFP_NORETRY);
        if (!syncobjs)
                return ERR_PTR(-ENOMEM);

        for (i = 0; i < nr_in_syncobjs; ++i) {
                uint64_t address = in_syncobjs_addr + i * syncobj_stride;

                if (copy_from_user(&syncobj_desc,
                                   u64_to_user_ptr(address),
                                   min(syncobj_stride, sizeof(syncobj_desc)))) {
                        ret = -EFAULT;
                        break;
                }

                if (syncobj_desc.point &&
                    !drm_core_check_feature(dev, DRIVER_SYNCOBJ_TIMELINE)) {
                        ret = UERR(EOPNOTSUPP, dev, "syncobj timeline unsupported");
                        break;
                }

                if (syncobj_desc.flags & ~MSM_SYNCOBJ_FLAGS) {
                        ret = UERR(EINVAL, dev, "invalid syncobj flags: %x", syncobj_desc.flags);
                        break;
                }

                ret = drm_sched_job_add_syncobj_dependency(job, file,
                                                   syncobj_desc.handle,
                                                   syncobj_desc.point);
                if (ret)
                        break;

                if (syncobj_desc.flags & MSM_SYNCOBJ_RESET) {
                        syncobjs[i] = drm_syncobj_find(file, syncobj_desc.handle);
                        if (!syncobjs[i]) {
                                ret = UERR(EINVAL, dev, "invalid syncobj handle: %u", i);
                                break;
                        }
                }
        }

        if (ret) {
                for (j = 0; j <= i; ++j) {
                        if (syncobjs[j])
                                drm_syncobj_put(syncobjs[j]);
                }
                kfree(syncobjs);
                return ERR_PTR(ret);
        }
        return syncobjs;
}

void
msm_syncobj_reset(struct drm_syncobj **syncobjs, uint32_t nr_syncobjs)
{
        uint32_t i;

        for (i = 0; syncobjs && i < nr_syncobjs; ++i) {
                if (syncobjs[i])
                        drm_syncobj_replace_fence(syncobjs[i], NULL);
        }
}

struct msm_syncobj_post_dep *
msm_syncobj_parse_post_deps(struct drm_device *dev,
                            struct drm_file *file,
                            uint64_t syncobjs_addr,
                            uint32_t nr_syncobjs,
                            size_t syncobj_stride)
{
        struct msm_syncobj_post_dep *post_deps;
        struct drm_msm_syncobj syncobj_desc = {0};
        int ret = 0;
        uint32_t i, j;

        post_deps = kzalloc_objs(*post_deps, nr_syncobjs,
                                 GFP_KERNEL | __GFP_NOWARN | __GFP_NORETRY);
        if (!post_deps)
                return ERR_PTR(-ENOMEM);

        for (i = 0; i < nr_syncobjs; ++i) {
                uint64_t address = syncobjs_addr + i * syncobj_stride;

                if (copy_from_user(&syncobj_desc,
                                   u64_to_user_ptr(address),
                                   min(syncobj_stride, sizeof(syncobj_desc)))) {
                        ret = -EFAULT;
                        break;
                }

                post_deps[i].point = syncobj_desc.point;

                if (syncobj_desc.flags) {
                        ret = UERR(EINVAL, dev, "invalid syncobj flags");
                        break;
                }

                if (syncobj_desc.point) {
                        if (!drm_core_check_feature(dev,
                                                    DRIVER_SYNCOBJ_TIMELINE)) {
                                ret = UERR(EOPNOTSUPP, dev, "syncobj timeline unsupported");
                                break;
                        }

                        post_deps[i].chain = dma_fence_chain_alloc();
                        if (!post_deps[i].chain) {
                                ret = -ENOMEM;
                                break;
                        }
                }

                post_deps[i].syncobj =
                        drm_syncobj_find(file, syncobj_desc.handle);
                if (!post_deps[i].syncobj) {
                        ret = UERR(EINVAL, dev, "invalid syncobj handle");
                        break;
                }
        }

        if (ret) {
                for (j = 0; j <= i; ++j) {
                        dma_fence_chain_free(post_deps[j].chain);
                        if (post_deps[j].syncobj)
                                drm_syncobj_put(post_deps[j].syncobj);
                }

                kfree(post_deps);
                return ERR_PTR(ret);
        }

        return post_deps;
}

void
msm_syncobj_process_post_deps(struct msm_syncobj_post_dep *post_deps,
                              uint32_t count, struct dma_fence *fence)
{
        uint32_t i;

        for (i = 0; post_deps && i < count; ++i) {
                if (post_deps[i].chain) {
                        drm_syncobj_add_point(post_deps[i].syncobj,
                                              post_deps[i].chain,
                                              fence, post_deps[i].point);
                        post_deps[i].chain = NULL;
                } else {
                        drm_syncobj_replace_fence(post_deps[i].syncobj,
                                                  fence);
                }
        }
}