root/drivers/gpu/drm/tegra/uapi.c
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020 NVIDIA Corporation */

#include <linux/host1x.h>
#include <linux/iommu.h>
#include <linux/list.h>

#include <drm/drm_drv.h>
#include <drm/drm_file.h>
#include <drm/drm_utils.h>

#include "drm.h"
#include "uapi.h"

static void tegra_drm_mapping_release(struct kref *ref)
{
        struct tegra_drm_mapping *mapping =
                container_of(ref, struct tegra_drm_mapping, ref);

        host1x_bo_unpin(mapping->map);
        host1x_bo_put(mapping->bo);

        kfree(mapping);
}

void tegra_drm_mapping_put(struct tegra_drm_mapping *mapping)
{
        kref_put(&mapping->ref, tegra_drm_mapping_release);
}

static void tegra_drm_channel_context_close(struct tegra_drm_context *context)
{
        struct tegra_drm_mapping *mapping;
        unsigned long id;

        if (context->memory_context)
                host1x_memory_context_put(context->memory_context);

        xa_for_each(&context->mappings, id, mapping)
                tegra_drm_mapping_put(mapping);

        xa_destroy(&context->mappings);

        host1x_channel_put(context->channel);

        kfree(context);
}

void tegra_drm_uapi_close_file(struct tegra_drm_file *file)
{
        struct tegra_drm_context *context;
        struct host1x_syncpt *sp;
        unsigned long id;

        xa_for_each(&file->contexts, id, context)
                tegra_drm_channel_context_close(context);

        xa_for_each(&file->syncpoints, id, sp)
                host1x_syncpt_put(sp);

        xa_destroy(&file->contexts);
        xa_destroy(&file->syncpoints);
}

static struct tegra_drm_client *tegra_drm_find_client(struct tegra_drm *tegra, u32 class)
{
        struct tegra_drm_client *client;

        list_for_each_entry(client, &tegra->clients, list)
                if (client->base.class == class)
                        return client;

        return NULL;
}

int tegra_drm_ioctl_channel_open(struct drm_device *drm, void *data, struct drm_file *file)
{
        struct host1x *host = tegra_drm_to_host1x(drm->dev_private);
        struct tegra_drm_file *fpriv = file->driver_priv;
        struct tegra_drm *tegra = drm->dev_private;
        struct drm_tegra_channel_open *args = data;
        struct tegra_drm_client *client = NULL;
        struct tegra_drm_context *context;
        int err;

        if (args->flags)
                return -EINVAL;

        context = kzalloc_obj(*context);
        if (!context)
                return -ENOMEM;

        client = tegra_drm_find_client(tegra, args->host1x_class);
        if (!client) {
                err = -ENODEV;
                goto free;
        }

        if (client->shared_channel) {
                context->channel = host1x_channel_get(client->shared_channel);
        } else {
                context->channel = host1x_channel_request(&client->base);
                if (!context->channel) {
                        err = -EBUSY;
                        goto free;
                }
        }

        /* Only allocate context if the engine supports context isolation. */
        if (device_iommu_mapped(client->base.dev) && client->ops->can_use_memory_ctx) {
                bool supported;

                err = client->ops->can_use_memory_ctx(client, &supported);
                if (err)
                        goto put_channel;

                if (supported) {
                        struct pid *pid = get_task_pid(current, PIDTYPE_TGID);
                        context->memory_context = host1x_memory_context_alloc(
                                host, client->base.dev, pid);
                        put_pid(pid);
                }

                if (IS_ERR(context->memory_context)) {
                        if (PTR_ERR(context->memory_context) != -EOPNOTSUPP) {
                                err = PTR_ERR(context->memory_context);
                                goto put_channel;
                        } else {
                                /*
                                 * OK, HW does not support contexts or contexts
                                 * are disabled.
                                 */
                                context->memory_context = NULL;
                        }
                }
        }

        err = xa_alloc(&fpriv->contexts, &args->context, context, XA_LIMIT(1, U32_MAX),
                       GFP_KERNEL);
        if (err < 0)
                goto put_memctx;

        context->client = client;
        xa_init_flags(&context->mappings, XA_FLAGS_ALLOC1);

        args->version = client->version;
        args->capabilities = 0;

        if (device_get_dma_attr(client->base.dev) == DEV_DMA_COHERENT)
                args->capabilities |= DRM_TEGRA_CHANNEL_CAP_CACHE_COHERENT;

        return 0;

put_memctx:
        if (context->memory_context)
                host1x_memory_context_put(context->memory_context);
put_channel:
        host1x_channel_put(context->channel);
free:
        kfree(context);

        return err;
}

int tegra_drm_ioctl_channel_close(struct drm_device *drm, void *data, struct drm_file *file)
{
        struct tegra_drm_file *fpriv = file->driver_priv;
        struct drm_tegra_channel_close *args = data;
        struct tegra_drm_context *context;

        mutex_lock(&fpriv->lock);

        context = xa_load(&fpriv->contexts, args->context);
        if (!context) {
                mutex_unlock(&fpriv->lock);
                return -EINVAL;
        }

        xa_erase(&fpriv->contexts, args->context);

        mutex_unlock(&fpriv->lock);

        tegra_drm_channel_context_close(context);

        return 0;
}

int tegra_drm_ioctl_channel_map(struct drm_device *drm, void *data, struct drm_file *file)
{
        struct tegra_drm_file *fpriv = file->driver_priv;
        struct drm_tegra_channel_map *args = data;
        struct tegra_drm_mapping *mapping;
        struct tegra_drm_context *context;
        enum dma_data_direction direction;
        struct device *mapping_dev;
        int err = 0;

        if (args->flags & ~DRM_TEGRA_CHANNEL_MAP_READ_WRITE)
                return -EINVAL;

        mutex_lock(&fpriv->lock);

        context = xa_load(&fpriv->contexts, args->context);
        if (!context) {
                mutex_unlock(&fpriv->lock);
                return -EINVAL;
        }

        mapping = kzalloc_obj(*mapping);
        if (!mapping) {
                err = -ENOMEM;
                goto unlock;
        }

        kref_init(&mapping->ref);

        if (context->memory_context)
                mapping_dev = &context->memory_context->dev;
        else
                mapping_dev = context->client->base.dev;

        mapping->bo = tegra_gem_lookup(file, args->handle);
        if (!mapping->bo) {
                err = -EINVAL;
                goto free;
        }

        switch (args->flags & DRM_TEGRA_CHANNEL_MAP_READ_WRITE) {
        case DRM_TEGRA_CHANNEL_MAP_READ_WRITE:
                direction = DMA_BIDIRECTIONAL;
                break;

        case DRM_TEGRA_CHANNEL_MAP_WRITE:
                direction = DMA_FROM_DEVICE;
                break;

        case DRM_TEGRA_CHANNEL_MAP_READ:
                direction = DMA_TO_DEVICE;
                break;

        default:
                err = -EINVAL;
                goto put_gem;
        }

        mapping->map = host1x_bo_pin(mapping_dev, mapping->bo, direction, NULL);
        if (IS_ERR(mapping->map)) {
                err = PTR_ERR(mapping->map);
                goto put_gem;
        }

        mapping->iova = mapping->map->phys;
        mapping->iova_end = mapping->iova + host1x_to_tegra_bo(mapping->bo)->gem.size;

        err = xa_alloc(&context->mappings, &args->mapping, mapping, XA_LIMIT(1, U32_MAX),
                       GFP_KERNEL);
        if (err < 0)
                goto unpin;

        mutex_unlock(&fpriv->lock);

        return 0;

unpin:
        host1x_bo_unpin(mapping->map);
put_gem:
        host1x_bo_put(mapping->bo);
free:
        kfree(mapping);
unlock:
        mutex_unlock(&fpriv->lock);
        return err;
}

int tegra_drm_ioctl_channel_unmap(struct drm_device *drm, void *data, struct drm_file *file)
{
        struct tegra_drm_file *fpriv = file->driver_priv;
        struct drm_tegra_channel_unmap *args = data;
        struct tegra_drm_mapping *mapping;
        struct tegra_drm_context *context;

        mutex_lock(&fpriv->lock);

        context = xa_load(&fpriv->contexts, args->context);
        if (!context) {
                mutex_unlock(&fpriv->lock);
                return -EINVAL;
        }

        mapping = xa_erase(&context->mappings, args->mapping);

        mutex_unlock(&fpriv->lock);

        if (!mapping)
                return -EINVAL;

        tegra_drm_mapping_put(mapping);
        return 0;
}

int tegra_drm_ioctl_syncpoint_allocate(struct drm_device *drm, void *data, struct drm_file *file)
{
        struct host1x *host1x = tegra_drm_to_host1x(drm->dev_private);
        struct tegra_drm_file *fpriv = file->driver_priv;
        struct drm_tegra_syncpoint_allocate *args = data;
        struct host1x_syncpt *sp;
        int err;

        if (args->id)
                return -EINVAL;

        sp = host1x_syncpt_alloc(host1x, HOST1X_SYNCPT_CLIENT_MANAGED, current->comm);
        if (!sp)
                return -EBUSY;

        args->id = host1x_syncpt_id(sp);

        err = xa_insert(&fpriv->syncpoints, args->id, sp, GFP_KERNEL);
        if (err) {
                host1x_syncpt_put(sp);
                return err;
        }

        return 0;
}

int tegra_drm_ioctl_syncpoint_free(struct drm_device *drm, void *data, struct drm_file *file)
{
        struct tegra_drm_file *fpriv = file->driver_priv;
        struct drm_tegra_syncpoint_allocate *args = data;
        struct host1x_syncpt *sp;

        mutex_lock(&fpriv->lock);
        sp = xa_erase(&fpriv->syncpoints, args->id);
        mutex_unlock(&fpriv->lock);

        if (!sp)
                return -EINVAL;

        host1x_syncpt_put(sp);

        return 0;
}

int tegra_drm_ioctl_syncpoint_wait(struct drm_device *drm, void *data, struct drm_file *file)
{
        struct host1x *host1x = tegra_drm_to_host1x(drm->dev_private);
        struct drm_tegra_syncpoint_wait *args = data;
        signed long timeout_jiffies;
        struct host1x_syncpt *sp;

        if (args->padding != 0)
                return -EINVAL;

        sp = host1x_syncpt_get_by_id_noref(host1x, args->id);
        if (!sp)
                return -EINVAL;

        timeout_jiffies = drm_timeout_abs_to_jiffies(args->timeout_ns);

        return host1x_syncpt_wait(sp, args->threshold, timeout_jiffies, &args->value);
}