/*
 * 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_ioctl_pub.h"
#include "ka_fs_pub.h"
#include "ka_memory_pub.h"
#include "ka_kernel_def_pub.h"

#include "pbl_feature_loader.h"
#include "pbl/pbl_davinci_api.h"
#include "pbl_uda.h"

#include "svm_addr_desc.h"
#include "svm_ioctl_ex.h"
#include "svm_kern_log.h"
#include "svm_task.h"
#include "svm_slab.h"
#include "framework_cmd.h"
#include "svm_fops.h"

static int svm_setup_private_data(ka_file_t *file, int tgid)
{
    struct task_id_entity *task_id = NULL;

    if (ka_fs_get_file_private_data(file) != NULL) {
        svm_err("Private_data isn't null.\n");
        return -EINVAL;
    }

    task_id = (struct task_id_entity *)svm_kzalloc(sizeof(struct task_id_entity), KA_GFP_KERNEL | __KA_GFP_ACCOUNT);
    if (task_id == NULL) {
        svm_err("Failed to kzalloc task_id_entity.\n");
        return -ENOMEM;
    }

    task_id->tgid = tgid;
    task_get_start_time_by_tgid(tgid, &task_id->start_time);
    ka_fs_set_file_private_data(file, (void *)task_id);

    return 0;
}

static void svm_clear_private_data(ka_file_t *file)
{
    struct task_id_entity *task_id = (struct task_id_entity *)ka_fs_get_file_private_data(file);

    if (task_id != NULL) {
        svm_kfree(task_id);
        ka_fs_set_file_private_data(file, NULL);
    }
}

static int svm_open(ka_inode_t *inode, ka_file_t *file)
{
    struct task_id_entity *task_id = NULL;
    u32 udevid = drv_davinci_get_device_id(file);
    int tgid = (int)ka_task_get_current_tgid();
    int ret;

    if (!uda_can_access_udevid(udevid) && (udevid != uda_get_host_id())) {
        svm_info("Can not access device. (udevid=%u; tgid=%d)\n", udevid, tgid);
        return -EPERM;
    }

    ret = svm_setup_private_data(file, tgid);
    if (ret != 0) {
        return ret;
    }

    task_id = (struct task_id_entity *)ka_fs_get_file_private_data(file);
    ret = svm_add_task(udevid, tgid, &task_id->start_time);
    if (ret != 0) {
        svm_clear_private_data(file);
        return ret;
    }

    svm_info("Open. (udevid=%u; tgid=%d)\n", udevid, tgid);

    return 0;
}

static int svm_release(ka_inode_t *inode, ka_file_t *file)
{
    return 0;
}

static int svm_pre_release(ka_file_t *file, unsigned long mode)
{
    u32 udevid = drv_davinci_get_device_id(file);
    struct task_id_entity *task_id = (struct task_id_entity *)ka_fs_get_file_private_data(file);
    if (task_id != NULL) {
        int tgid = task_id->tgid;
        int ret = svm_del_task(udevid, tgid, &task_id->start_time);

        svm_info("Release. (udevid=%u; tgid=%d; ret=%d)\n", udevid, tgid, ret);
        svm_clear_private_data(file);
    }

    return 0;
}

static int (* svm_ioctl_handler[SVM_MAX_CMD])(u32 udevid, u32 cmd, unsigned long arg) = {NULL, };
static int (* svm_ioctl_pre_handler[SVM_MAX_CMD])(u32 udevid, u32 cmd, void *para) = {NULL, };
static void (* svm_ioctl_pre_cancle_handler[SVM_MAX_CMD])(u32 udevid, u32 cmd, void *para) = {NULL, };
static int (* svm_ioctl_post_handler[SVM_MAX_CMD])(u32 udevid, u32 cmd, void *para) = {NULL, };

void svm_register_ioctl_cmd_handle(int nr, int (*fn)(u32 udevid, u32 cmd, unsigned long arg))
{
    svm_ioctl_handler[nr] = fn;
}

void svm_register_ioctl_cmd_pre_handle(int nr, int (*fn)(u32 udevid, u32 cmd, void *para))
{
    svm_ioctl_pre_handler[nr] = fn;
}

void svm_register_ioctl_cmd_pre_cancle_handle(int nr, void (*fn)(u32 udevid, u32 cmd, void *para))
{
    svm_ioctl_pre_cancle_handler[nr] = fn;
}

void svm_register_ioctl_cmd_post_handle(int nr, int (*fn)(u32 udevid, u32 cmd, void *para))
{
    svm_ioctl_post_handler[nr] = fn;
}

int svm_call_ioctl_pre_handler(u32 udevid, u32 cmd, void *para)
{
    u32 cmd_nr = _KA_IOC_NR(cmd);
    int ret;

    if (svm_ioctl_pre_handler[cmd_nr] != NULL) {
        ret = svm_ioctl_pre_handler[cmd_nr](udevid, cmd, para);
        if (ret != 0) {
            return ret;
        }
    }

    return 0;
}

void svm_call_ioctl_pre_cancle_handler(u32 udevid, u32 cmd, void *para)
{
    u32 cmd_nr = _KA_IOC_NR(cmd);
    if (svm_ioctl_pre_cancle_handler[cmd_nr] != NULL) {
        svm_ioctl_pre_cancle_handler[cmd_nr](udevid, cmd, para);
    }
}

int svm_call_ioctl_post_handler(u32 udevid, u32 cmd, void *para)
{
    u32 cmd_nr = _KA_IOC_NR(cmd);
    int ret;

    if (svm_ioctl_post_handler[cmd_nr] != NULL) {
        ret = svm_ioctl_post_handler[cmd_nr](udevid, cmd, para);
        if (ret != 0) {
            return ret;
        }
    }

    return 0;
}

static long svm_ioctl(ka_file_t *file, u32 cmd, unsigned long arg)
{
    u32 cmd_nr = _KA_IOC_NR(cmd);
    u32 udevid;

    if ((file == NULL) || (arg == 0)) {
        svm_err("The file or arg is null.\n");
        return -EINVAL;
    }

    if (cmd_nr >= SVM_MAX_CMD) {
        svm_err("The command is invalid, which is out of range. (cmd=%u)\n", cmd_nr);
        return -EINVAL;
    }

    if (svm_ioctl_handler[cmd_nr] == NULL) {
        return -EOPNOTSUPP;
    }

    udevid = drv_davinci_get_device_id(file);
    return (long)svm_ioctl_handler[cmd_nr](udevid, cmd, arg);
}

static int svm_mmap(ka_file_t *file, ka_vm_area_struct_t *vma)
{
    return -EOPNOTSUPP;
}

static ka_file_operations_t svm_fops = {
    .owner = KA_THIS_MODULE,
    .open = svm_open,
    .release = svm_release,
    .unlocked_ioctl = svm_ioctl,
    .mmap = svm_mmap
};

static const struct notifier_operations svm_notifier_ops = {
    .notifier_call = svm_pre_release,
};

int svm_fops_init(void)
{
    int ret;

    ret = drv_davinci_register_sub_module(SVM_CHAR_DEV_NAME, &svm_fops);
    if (ret != 0) {
        svm_err("Module register fail. (ret=%d)\n", ret);
        return ret;
    }

    ret = drv_ascend_register_notify(SVM_CHAR_DEV_NAME, &svm_notifier_ops);
    if (ret != 0) {
        (void)drv_ascend_unregister_sub_module(SVM_CHAR_DEV_NAME);
        svm_err("Notifier register fail. (ret=%d)\n", ret);
        return ret;
    }

    return 0;
}

void svm_fops_uninit(void)
{
    (void)drv_ascend_unregister_notify(SVM_CHAR_DEV_NAME);
    (void)drv_ascend_unregister_sub_module(SVM_CHAR_DEV_NAME);
}