/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only 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 "dvt.h"
#include "hw_vdavinci.h"
#include "mmio.h"
#include "domain_manage.h"
#include "vfio_ops.h"

static void hw_vdavinci_reset_debugfs_info(struct hw_vdavinci *vdavinci)
{
    int ret;
    size_t destmax_count = vdavinci->debugfs.nvec * sizeof(unsigned long long);

    if (vdavinci->debugfs.msix_count) {
        ret = memset_s(vdavinci->debugfs.msix_count, destmax_count, 0, destmax_count);
        if (ret)
            vascend_err(vdavinci_to_dev(vdavinci), "vdavinci reset msix_count failed, "
                "ret: %d\n", ret);
    }

    vdavinci->debugfs.notify_count = 0;
}

STATIC void hw_dvt_vdavinci_init(struct hw_vdavinci *vdavinci, struct hw_dvt *dvt,
                                 struct hw_vdavinci_type *type, unsigned int id)
{
    vdavinci->id = id;
    vdavinci->dvt = dvt;
    vdavinci->type = type;
    vdavinci->dev.dev = dvt->vdavinci_priv->dev;
    vdavinci->dev.dev_index = type->dev_index;
    vdavinci->dev.fid = vdavinci->id;
    vdavinci->dev.resource_dev = dvt->vdavinci_priv->dev;
    vdavinci->vf_index = -1;
    mutex_init(&vdavinci->vdavinci_lock);
}

LIST_HEAD(g_vdavinci_list);

STATIC struct list_head *get_vdavinci_list(void)
{
    return &g_vdavinci_list;
}

struct hw_vdavinci *find_vdavinci(struct device *dev)
{
    struct hw_vdavinci *vdavinci = NULL, *next = NULL;
    struct list_head *head = get_vdavinci_list();

    if (dev == NULL) {
        return NULL;
    }

    list_for_each_entry_safe(vdavinci, next, head, list) {
        if (vdavinci->dev.resource_dev == dev) {
            return vdavinci;
        }
    }
    return NULL;
}

STATIC void remove_vdavinci_node(struct hw_vdavinci *vdavinci)
{
    struct hw_vdavinci *vdavinci_node = NULL, *next = NULL;
    struct list_head *head = get_vdavinci_list();

    list_for_each_entry_safe(vdavinci_node, next, head, list) {
        if (vdavinci_node == vdavinci) {
            list_del(&vdavinci_node->list);
        }
    }
}

STATIC int hw_get_available_vf(struct hw_vdavinci *vdavinci)
{
    struct hw_dvt *dvt = vdavinci->dvt;
    unsigned int i;
    for (i = 0; i < dvt->sriov.vf_num; i++) {
        if (!dvt->sriov.vf_array[i].used) {
            vdavinci->vf_index = (int)i;
            dvt->sriov.vf_array[i].used = true;
            dvt->sriov.vf_array[i].vdavinci = vdavinci;
            return i;
        }
    }

    return -EINVAL;
}

STATIC int hw_dvt_alloc_vf(struct hw_vdavinci *vdavinci)
{
    struct hw_dvt *dvt = vdavinci->dvt;
    struct device *dev = dvt->vdavinci_priv->dev;
    struct list_head *head = get_vdavinci_list();
    int index;

    if (dvt->sriov.vf_used >= dvt->sriov.vf_num) {
        vascend_err(dev, "No available VFs.\n");
        return -ENODEV;
    }
    index = hw_get_available_vf(vdavinci);
    if (index < 0) {
        vascend_err(dev, "No available VFs.\n");
        return -ENODEV;
    }
    vdavinci->vf.pdev = dvt->sriov.vf_array[index].vf;
    vdavinci->dev.resource_dev = &vdavinci->vf.pdev->dev;
    dvt->sriov.vf_used += 1;
    vdavinci->vf.irq_type = VFIO_PCI_NUM_IRQS;
    vdavinci->is_passthrough = true;
    list_add_tail(&(vdavinci->list), head);
    return 0;
}

STATIC int hw_dvt_reclaim_vf(struct hw_vdavinci *vdavinci)
{
    struct hw_dvt *dvt = vdavinci->dvt;
    int index;
    index = vdavinci->vf_index;
    if (index < 0) {
        return 0;
    }
    dvt->sriov.vf_array[index].vdavinci = NULL;
    dvt->sriov.vf_array[index].used = false;
    dvt->sriov.vf_used -= 1;
    remove_vdavinci_node(vdavinci);
    vdavinci->dev.resource_dev = NULL;
    vdavinci->vf.pdev = NULL;
    vdavinci->is_passthrough = false;
    return 0;
}

int init_vdavinci_type(struct hw_vdavinci_type *type,
                       struct vdavinci_type *tp)
{
    int ret = 0;

    if (type == NULL || tp == NULL) {
        return -EINVAL;
    }
    ret = snprintf_s(tp->template_name, HW_DVT_MAX_TYPE_NAME,
                     HW_DVT_MAX_TYPE_NAME - 1, "%s", type->template_name);
    if (ret < 0) {
        pr_err("vdavinci type init failed, ret: %d\n", ret);
        return ret;
    }
    tp->type = type->type;
    tp->bar0_size = type->bar0_size;
    tp->bar2_size = type->bar2_size;
    tp->bar4_size = type->bar4_size;
    tp->aicore_num = type->aicore_num;
    tp->mem_size = type->mem_size;
    tp->aicpu_num = type->aicpu_num;
    tp->vpc_num = type->vpc_num;
    tp->jpegd_num = type->jpegd_num;
    tp->jpege_num = type->jpege_num;
    tp->venc_num = type->venc_num;
    tp->vdec_num = type->vdec_num;
    tp->share = type->share;
    tp->ddrmem_size = type->ddrmem_size;
    tp->hbmmem_size = type->hbmmem_size;

    return 0;
}

STATIC int hw_dvt_ops_create_vdavinci(struct hw_vdavinci *vdavinci,
                                      struct hw_vdavinci_type *type,
                                      uuid_le uuid)
{
    struct hw_dvt *dvt = vdavinci->dvt;
    struct vdavinci_type tp;
    int ret = 0;

    if (dvt->vdavinci_priv->ops == NULL ||
        dvt->vdavinci_priv->ops->vdavinci_create == NULL) {
        vascend_err(vdavinci_to_dev(vdavinci), "vdavinci_priv's ops is null\n");
        return -EINVAL;
    }
    ret = init_vdavinci_type(type, &tp);
    if (ret != 0) {
        vascend_err(vdavinci_to_dev(vdavinci), "init vdavinci type failed\n");
        return ret;
    }
    ret = dvt->vdavinci_priv->ops->vdavinci_create(&vdavinci->dev, vdavinci,
                                                   &tp, uuid);
    if (ret != 0) {
        vascend_err(vdavinci_to_dev(vdavinci), "create vdavinci failed, call vdavinci_create failed, "
                    "pf : %u, vid: %u, ret: %d\n", vdavinci->dev.dev_index, vdavinci->id, ret);
        return ret;
    }

    return 0;
}

struct hw_vdavinci *hw_dvt_create_vdavinci(struct hw_dvt *dvt,
                                           struct hw_vdavinci_type *type, uuid_le uuid)
{
    struct hw_vdavinci *vdavinci;
    int ret;
    struct hw_pf_info *pf_info = &dvt->pf[type->dev_index];

    vdavinci = vzalloc(sizeof(*vdavinci));
    if (vdavinci == NULL) {
        return ERR_PTR(-ENOMEM);
    }

    ret = idr_alloc(&pf_info->vdavinci_idr, vdavinci, 0,
                    DVT_MAX_VDAVINCI, GFP_KERNEL);
    if (ret < 0)
        goto free_vdavinci;

    hw_dvt_vdavinci_init(vdavinci, dvt, type, (u32)ret);

    if (dvt->is_sriov_enabled) {
        ret = hw_dvt_alloc_vf(vdavinci);
        if (ret) {
            goto clean_idr;
        }
    }

    ret = dvt->mmio_init(vdavinci);
    if (ret) {
        goto put_vf;
    }

    hw_vdavinci_init_cfg_space(vdavinci);
    ret = hw_dvt_ops_create_vdavinci(vdavinci, type, uuid);
    if (ret != 0) {
        goto mmio_uninit;
    }
    hw_dvt_debugfs_add_vdavinci(vdavinci);
    mutex_init(&vdavinci->ioeventfds_lock);
    INIT_LIST_HEAD(&vdavinci->ioeventfds_list);

    return vdavinci;

mmio_uninit:
    dvt->mmio_uninit(vdavinci);
put_vf:
    hw_dvt_reclaim_vf(vdavinci);
clean_idr:
    idr_remove(&pf_info->vdavinci_idr, vdavinci->id);
free_vdavinci:
    vfree(vdavinci);
    return ERR_PTR(ret);
}

void hw_dvt_destroy_vdavinci(struct hw_vdavinci *vdavinci)
{
    struct hw_dvt *dvt = vdavinci->dvt;
    struct hw_pf_info *pf_info = &dvt->pf[vdavinci->dev.dev_index];

    if (dvt->vdavinci_priv->ops &&
        dvt->vdavinci_priv->ops->vdavinci_destroy) {
        dvt->vdavinci_priv->ops->vdavinci_destroy(&vdavinci->dev);
    }

    dvt->mmio_uninit(vdavinci);
    hw_dvt_reclaim_vf(vdavinci);
    idr_remove(&pf_info->vdavinci_idr, vdavinci->id);

    hw_dvt_debugfs_remove_vdavinci(vdavinci);
    mutex_destroy(&vdavinci->ioeventfds_lock);

    if (vdavinci->debugfs.msix_count) {
        kfree(vdavinci->debugfs.msix_count);
        vdavinci->debugfs.msix_count = NULL;
    }
    vfree(vdavinci);
}

void hw_dvt_release_vdavinci(struct hw_vdavinci *vdavinci)
{
    struct hw_dvt *dvt = vdavinci->dvt;

    hw_dvt_deactivate_vdavinci(vdavinci);

    mutex_lock(&vdavinci->vdavinci_lock);
    if (dvt->vdavinci_priv->ops &&
        dvt->vdavinci_priv->ops->vdavinci_release) {
        dvt->vdavinci_priv->ops->vdavinci_release(&vdavinci->dev);
    }

    mutex_unlock(&vdavinci->vdavinci_lock);
}

int hw_dvt_reset_vdavinci(struct hw_vdavinci *vdavinci)
{
    int ret = -1;
    struct hw_dvt *dvt = vdavinci->dvt;

    vascend_info(vdavinci_to_dev(vdavinci),
                 "enter reset vdavinci, pf : %u, vid: %u\n", vdavinci->dev.dev_index, vdavinci->id);

    mutex_lock(&vdavinci->vdavinci_lock);
    if (dvt->vdavinci_priv->ops &&
        dvt->vdavinci_priv->ops->vdavinci_reset) {
        ret = dvt->vdavinci_priv->ops->vdavinci_reset(&vdavinci->dev);
        if (ret) {
            vascend_err(vdavinci_to_dev(vdavinci),
                        "reset vdavinci failed, call vdavinci_reset failed, "
                        "pf : %u, vid: %u, ret: %d\n", vdavinci->dev.dev_index, vdavinci->id, ret);
            goto out;
        }
    }

    hw_vdavinci_reset_mmio(vdavinci);
    hw_vdavinci_reset_cfg_space(vdavinci);
    hw_vdavinci_reset_debugfs_info(vdavinci);

    vascend_info(vdavinci_to_dev(vdavinci),
                 "leave reset vdavinci, pf : %u, vid: %u\n", vdavinci->dev.dev_index, vdavinci->id);

out:
    mutex_unlock(&vdavinci->vdavinci_lock);
    return ret;
}

void hw_dvt_activate_vdavinci(struct hw_vdavinci *vdavinci)
{
    mutex_lock(&vdavinci->dvt->lock);
    vdavinci->active = true;
    mutex_unlock(&vdavinci->dvt->lock);
}

void hw_dvt_deactivate_vdavinci(struct hw_vdavinci *vdavinci)
{
    mutex_lock(&vdavinci->dvt->lock);
    vdavinci->active = false;
    mutex_unlock(&vdavinci->dvt->lock);
}