/*
 * 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 "ka_fs_pub.h"
#include "dvt.h"
#include "kvmdt.h"
#include "vfio_ops.h"

#define MAX_NAME_LEN 32

STATIC struct kref debugfs_ref;
STATIC DEFINE_MUTEX(debugfs_vascend_lock);
STATIC struct dentry *vascend_debugfs_root = NULL;

STATIC int vdavinci_msix_count_show(struct seq_file *s, void *unused)
{
    int i;
    struct hw_vdavinci *vdavinci = s->private;

    seq_printf(s, "%-16s %-16s\n", "Vector", "Count");
    for (i = 0; i < vdavinci->debugfs.nvec; i++) {
        seq_printf(s, "%-16d %-16llu\n", i, vdavinci->debugfs.msix_count[i]);
    }

    return 0;
}

static int seq_file_msix_count_open(struct inode *inode, struct file *file)
{
    return single_open(file, &vdavinci_msix_count_show, inode->i_private);
}

static const struct file_operations vdavinci_msix_count_fops = {
    .owner = THIS_MODULE,
    .open = seq_file_msix_count_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .release = single_release,
};

/**
 * hw_dvt_debugfs_add_vdavinci - register debugfs entries for a vdavinci
 */
void hw_dvt_debugfs_add_vdavinci(struct hw_vdavinci *vdavinci)
{
    int ret;
    char *name = NULL;

    name = kzalloc(MAX_NAME_LEN, GFP_KERNEL);
    if (!name) {
        ret = -ENOMEM;
        vascend_err(vdavinci_to_dev(vdavinci), "add debugfs failed, "
            "malloc name fialed, vid: %u, ret: %d\n", vdavinci->id, ret);
        return;
    }

    if (vdavinci->dvt->dev_num > 1) {
        ret = snprintf_s(name, MAX_NAME_LEN, MAX_NAME_LEN - 1, "vascend_p%u_%u",
                         vdavinci->dev.dev_index, vdavinci->id);
    } else {
        ret = snprintf_s(name, MAX_NAME_LEN, MAX_NAME_LEN - 1, "vascend%u",
                         vdavinci->id);
    }
    if (ret < 0) {
        vascend_err(vdavinci_to_dev(vdavinci), "add debugfs failed, "
            "vid: %u, ret: %d\n", vdavinci->id, ret);
        goto out;
    }

    vdavinci->debugfs.debugfs = vdavinci_debugfs_create_dir(name, vdavinci->dvt->debugfs_root);
    if (vdavinci->debugfs.debugfs == NULL) {
        goto out;
    }

    debugfs_create_u64("notify_count", 0400, vdavinci->debugfs.debugfs,
        &vdavinci->debugfs.notify_count);
    debugfs_create_file("msix_count", 0400, vdavinci->debugfs.debugfs, vdavinci,
        &vdavinci_msix_count_fops);
out:
    kfree(name);
}

/**
 * hw_dvt_debugfs_remove_vdavinci - remove debugfs entries of a vdavinci
 */
void hw_dvt_debugfs_remove_vdavinci(struct hw_vdavinci *vdavinci)
{
    vdavinci_debugfs_remove(vdavinci->debugfs.debugfs);
    vdavinci->debugfs.debugfs = NULL;
}

STATIC void hw_dvt_debugfs_release(struct kref *ref)
{
    vdavinci_debugfs_remove(vascend_debugfs_root);
    vascend_debugfs_root = NULL;
}

/**
 * hw_dvt_debugfs_init - register dvt debugfs root entry
 */
void hw_dvt_debugfs_init(struct hw_dvt *dvt)
{
    int ret;
    char *name = NULL;
    struct vdavinci_priv *vdavinci_priv = dvt->vdavinci_priv;
    struct pci_dev *pdev = container_of(vdavinci_priv->dev, struct pci_dev, dev);

    mutex_lock(&debugfs_vascend_lock);
    if (vascend_debugfs_root == NULL) {
        kref_init(&debugfs_ref);
        vascend_debugfs_root = vdavinci_debugfs_create_dir("vascend", NULL);
        if (vascend_debugfs_root == NULL) {
            goto debugfs_root;
        }
    } else {
        kref_get(&debugfs_ref);
    }
    mutex_unlock(&debugfs_vascend_lock);

    name = kzalloc(MAX_NAME_LEN, GFP_KERNEL);
    if (!name) {
        ret = -ENOMEM;
        vascend_err(vdavinci_priv->dev, "debugfs init failed, "
                    "malloc name fialed, ret: %d\n", ret);
        goto debugfs_root;
    }

    ret = snprintf_s(name, MAX_NAME_LEN, MAX_NAME_LEN - 1, "vascend_%02x_%02x_%u",
                     pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn));
    if (ret < 0) {
        vascend_err(vdavinci_priv->dev, "debugfs init failed, "
                    "snprientf_s fialed, ret: %d\n", ret);
        goto free_name;
    }

    dvt->debugfs_root = vdavinci_debugfs_create_dir(name, vascend_debugfs_root);
    kfree(name);
    return;

free_name:
    kfree(name);
debugfs_root:
    mutex_lock(&debugfs_vascend_lock);
    (void)kref_put(&debugfs_ref, hw_dvt_debugfs_release);
    mutex_unlock(&debugfs_vascend_lock);
}

/**
 * hw_dvt_debugfs_clean - remove debugfs entries
 */
void hw_dvt_debugfs_clean(struct hw_dvt *dvt)
{
    vdavinci_debugfs_remove(dvt->debugfs_root);
    dvt->debugfs_root = NULL;

    mutex_lock(&debugfs_vascend_lock);
    (void)kref_put(&debugfs_ref, hw_dvt_debugfs_release);
    mutex_unlock(&debugfs_vascend_lock);
}