root/drivers/gpu/drm/loongson/lsdc_gem.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2023 Loongson Technology Corporation Limited
 */

#include <linux/dma-buf.h>

#include <drm/drm_debugfs.h>
#include <drm/drm_dumb_buffers.h>
#include <drm/drm_file.h>
#include <drm/drm_gem.h>
#include <drm/drm_prime.h>
#include <drm/drm_print.h>

#include "lsdc_drv.h"
#include "lsdc_gem.h"
#include "lsdc_ttm.h"

static int lsdc_gem_prime_pin(struct drm_gem_object *obj)
{
        struct lsdc_bo *lbo = gem_to_lsdc_bo(obj);
        int ret;

        dma_resv_assert_held(obj->resv);

        ret = lsdc_bo_pin(lbo, LSDC_GEM_DOMAIN_GTT, NULL);
        if (likely(ret == 0))
                lbo->sharing_count++;

        return ret;
}

static void lsdc_gem_prime_unpin(struct drm_gem_object *obj)
{
        struct lsdc_bo *lbo = gem_to_lsdc_bo(obj);

        dma_resv_assert_held(obj->resv);

        lsdc_bo_unpin(lbo);
        if (lbo->sharing_count)
                lbo->sharing_count--;
}

static struct sg_table *lsdc_gem_prime_get_sg_table(struct drm_gem_object *obj)
{
        struct ttm_buffer_object *tbo = to_ttm_bo(obj);
        struct ttm_tt *tt = tbo->ttm;

        if (!tt) {
                drm_err(obj->dev, "sharing a buffer without backing memory\n");
                return ERR_PTR(-ENOMEM);
        }

        return drm_prime_pages_to_sg(obj->dev, tt->pages, tt->num_pages);
}

static void lsdc_gem_object_free(struct drm_gem_object *obj)
{
        struct ttm_buffer_object *tbo = to_ttm_bo(obj);

        if (tbo)
                ttm_bo_fini(tbo);
}

static int lsdc_gem_object_vmap(struct drm_gem_object *obj, struct iosys_map *map)
{
        struct ttm_buffer_object *tbo = to_ttm_bo(obj);
        struct lsdc_bo *lbo = to_lsdc_bo(tbo);
        int ret;

        if (lbo->vmap_count > 0) {
                ++lbo->vmap_count;
                goto out;
        }

        ret = lsdc_bo_pin(lbo, 0, NULL);
        if (unlikely(ret)) {
                drm_err(obj->dev, "pin %p for vmap failed\n", lbo);
                return ret;
        }

        ret = ttm_bo_vmap(tbo, &lbo->map);
        if (ret) {
                drm_err(obj->dev, "ttm bo vmap failed\n");
                lsdc_bo_unpin(lbo);
                return ret;
        }

        lbo->vmap_count = 1;

out:
        *map = lbo->map;

        return 0;
}

static void lsdc_gem_object_vunmap(struct drm_gem_object *obj, struct iosys_map *map)
{
        struct ttm_buffer_object *tbo = to_ttm_bo(obj);
        struct lsdc_bo *lbo = to_lsdc_bo(tbo);

        if (unlikely(!lbo->vmap_count)) {
                drm_warn(obj->dev, "%p is not mapped\n", lbo);
                return;
        }

        --lbo->vmap_count;
        if (lbo->vmap_count == 0) {
                ttm_bo_vunmap(tbo, &lbo->map);

                lsdc_bo_unpin(lbo);
        }
}

static int lsdc_gem_object_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
{
        struct ttm_buffer_object *tbo = to_ttm_bo(obj);
        int ret;

        ret = ttm_bo_mmap_obj(vma, tbo);
        if (unlikely(ret)) {
                drm_warn(obj->dev, "mmap %p failed\n", tbo);
                return ret;
        }

        drm_gem_object_put(obj);

        return 0;
}

static const struct drm_gem_object_funcs lsdc_gem_object_funcs = {
        .free = lsdc_gem_object_free,
        .export = drm_gem_prime_export,
        .pin = lsdc_gem_prime_pin,
        .unpin = lsdc_gem_prime_unpin,
        .get_sg_table = lsdc_gem_prime_get_sg_table,
        .vmap = lsdc_gem_object_vmap,
        .vunmap = lsdc_gem_object_vunmap,
        .mmap = lsdc_gem_object_mmap,
};

struct drm_gem_object *lsdc_gem_object_create(struct drm_device *ddev,
                                              u32 domain,
                                              size_t size,
                                              bool kerenl,
                                              struct sg_table *sg,
                                              struct dma_resv *resv)
{
        struct lsdc_device *ldev = to_lsdc(ddev);
        struct drm_gem_object *gobj;
        struct lsdc_bo *lbo;
        int ret;

        lbo = lsdc_bo_create(ddev, domain, size, kerenl, sg, resv);
        if (IS_ERR(lbo)) {
                ret = PTR_ERR(lbo);
                return ERR_PTR(ret);
        }

        if (!sg) {
                /* VRAM is filled with random data */
                lsdc_bo_clear(lbo);
        }

        gobj = &lbo->tbo.base;
        gobj->funcs = &lsdc_gem_object_funcs;

        /* tracking the BOs we created */
        mutex_lock(&ldev->gem.mutex);
        list_add_tail(&lbo->list, &ldev->gem.objects);
        mutex_unlock(&ldev->gem.mutex);

        return gobj;
}

struct drm_gem_object *
lsdc_prime_import_sg_table(struct drm_device *ddev,
                           struct dma_buf_attachment *attach,
                           struct sg_table *sg)
{
        struct dma_resv *resv = attach->dmabuf->resv;
        u64 size = attach->dmabuf->size;
        struct drm_gem_object *gobj;
        struct lsdc_bo *lbo;

        dma_resv_lock(resv, NULL);
        gobj = lsdc_gem_object_create(ddev, LSDC_GEM_DOMAIN_GTT, size, false,
                                      sg, resv);
        dma_resv_unlock(resv);

        if (IS_ERR(gobj)) {
                drm_err(ddev, "Failed to import sg table\n");
                return gobj;
        }

        lbo = gem_to_lsdc_bo(gobj);
        lbo->sharing_count = 1;

        return gobj;
}

int lsdc_dumb_create(struct drm_file *file, struct drm_device *ddev,
                     struct drm_mode_create_dumb *args)
{
        struct lsdc_device *ldev = to_lsdc(ddev);
        const struct lsdc_desc *descp = ldev->descp;
        u32 domain = LSDC_GEM_DOMAIN_VRAM;
        struct drm_gem_object *gobj;
        int ret;

        ret = drm_mode_size_dumb(ddev, args, descp->pitch_align, 0);
        if (ret)
                return ret;

        /* Maximum single bo size allowed is the half vram size available */
        if (args->size > ldev->vram_size / 2) {
                drm_err(ddev, "Requesting(%zuMiB) failed\n", (size_t)(args->size >> PAGE_SHIFT));
                return -ENOMEM;
        }

        gobj = lsdc_gem_object_create(ddev, domain, args->size, false, NULL, NULL);
        if (IS_ERR(gobj)) {
                drm_err(ddev, "Failed to create gem object\n");
                return PTR_ERR(gobj);
        }

        ret = drm_gem_handle_create(file, gobj, &args->handle);

        /* drop reference from allocate, handle holds it now */
        drm_gem_object_put(gobj);
        if (ret)
                return ret;

        return 0;
}

int lsdc_dumb_map_offset(struct drm_file *filp, struct drm_device *ddev,
                         u32 handle, uint64_t *offset)
{
        struct drm_gem_object *gobj;

        gobj = drm_gem_object_lookup(filp, handle);
        if (!gobj)
                return -ENOENT;

        *offset = drm_vma_node_offset_addr(&gobj->vma_node);

        drm_gem_object_put(gobj);

        return 0;
}

void lsdc_gem_init(struct drm_device *ddev)
{
        struct lsdc_device *ldev = to_lsdc(ddev);

        mutex_init(&ldev->gem.mutex);
        INIT_LIST_HEAD(&ldev->gem.objects);
}

int lsdc_show_buffer_object(struct seq_file *m, void *arg)
{
        struct drm_info_node *node = (struct drm_info_node *)m->private;
        struct drm_device *ddev = node->minor->dev;
        struct lsdc_device *ldev = to_lsdc(ddev);
        struct lsdc_bo *lbo;
        unsigned int i;

        mutex_lock(&ldev->gem.mutex);

        i = 0;

        list_for_each_entry(lbo, &ldev->gem.objects, list) {
                struct ttm_buffer_object *tbo = &lbo->tbo;
                struct ttm_resource *resource = tbo->resource;

                seq_printf(m, "bo[%04u][%p]: size: %8zuKiB %s offset: %8llx\n",
                           i, lbo, lsdc_bo_size(lbo) >> 10,
                           lsdc_mem_type_to_str(resource->mem_type),
                           lsdc_bo_gpu_offset(lbo));
                i++;
        }

        mutex_unlock(&ldev->gem.mutex);

        seq_printf(m, "Pinned BO size: VRAM: %zuKiB, GTT: %zu KiB\n",
                   ldev->vram_pinned_size >> 10, ldev->gtt_pinned_size >> 10);

        return 0;
}