/*
 * 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_common_pub.h"
#include "ka_ioctl_pub.h"
#include "ka_memory_pub.h"
#include "ka_task_pub.h"
#include "ka_kernel_def_pub.h"

#include "apm_auto_init.h"
#include "apm_slab.h"
#include "pbl/pbl_task_ctx.h"

#include "pbl/pbl_davinci_api.h"
#include "apm_fops.h"

static int apm_open(ka_inode_t *inode, ka_file_t *file)
{
    struct task_id_entity *task = NULL;

    task = (struct task_id_entity *)apm_kzalloc(sizeof(struct task_id_entity), KA_GFP_KERNEL | __KA_GFP_ACCOUNT);
    if (task == NULL) {
#ifndef EMU_ST
        apm_err("Failed to kzalloc task_id_entity.\n");
        return -ENOMEM;
#endif
    }

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

    apm_info("Open. (tgid=%d)\n", ka_task_get_current_tgid());
    return 0;
}

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

static int apm_pre_release(ka_file_t *file, unsigned long mode)
{
#ifndef EMU_ST
    struct task_id_entity *task = (struct task_id_entity *)ka_fs_get_file_private_data(file);
    if (task != NULL) {
        module_feature_auto_uninit_task(0, task->tgid, (void*)&(task->start_time));
        apm_info("Release. (tgid=%d)\n", task->tgid);
        apm_kfree(task);
        ka_fs_set_file_private_data(file, NULL);
    }
#endif
    return 0;
}

static int (* apm_ioctl_handler[APM_MAX_CMD])(u32 cmd, unsigned long arg) = {NULL, };

void apm_register_ioctl_cmd_func(int nr, int (*fn)(u32 cmd, unsigned long arg))
{
    apm_ioctl_handler[nr] = fn;
}

static long apm_ioctl(ka_file_t *file, u32 cmd, unsigned long arg)
{
    if (arg == 0) {
        return -EINVAL;
    }
    if (_KA_IOC_NR(cmd) >= APM_MAX_CMD) {
        apm_err("The command is invalid, which is out of range. (cmd=%u)\n", _KA_IOC_NR(cmd));
        return -EINVAL;
    }

    if (apm_ioctl_handler[_KA_IOC_NR(cmd)] == NULL) {
#ifndef EMU_ST
        apm_warn("The command is not support. (cmd=%u)\n", _KA_IOC_NR(cmd));
#endif
        return -EOPNOTSUPP;
    }

    return apm_ioctl_handler[_KA_IOC_NR(cmd)](cmd, arg);
}

static ka_file_operations_t apm_fops = {
    .owner = KA_THIS_MODULE,
    .open = apm_open,
    .release = apm_release,
    .unlocked_ioctl = apm_ioctl
};

static const struct notifier_operations apm_notifier_ops = {
    .notifier_call = apm_pre_release,
};

int apm_fops_init(void)
{
    int ret;

    ret = drv_davinci_register_sub_module(APM_CHAR_DEV_NAME, &apm_fops);
    if (ret != 0) {
        apm_err("Module register fail. (ret=%d)\n", ret);
        return ret;
    }

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

    return 0;
}

void apm_fops_uninit(void)
{
    (void)drv_ascend_unregister_notify(APM_CHAR_DEV_NAME);
    (void)drv_ascend_unregister_sub_module(APM_CHAR_DEV_NAME);
}