/*

 * 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 "pbl/pbl_davinci_api.h"

#include "davinci_interface.h"

#include "urd_define.h"

#include "urd_notifier.h"

#include "urd_feature.h"

#include "urd_kv.h"

#include "securec.h"

#include "urd_init.h"

#include "pbl/pbl_urd.h"

#include "urd_cmd.h"

#include "ka_compiler_pub.h"

#include "ka_ioctl_pub.h"

#include "ka_fs_pub.h"

#include "ka_task_pub.h"

#include "ka_system_pub.h"

#include "ka_memory_pub.h"

#include "ka_kernel_def_pub.h"

#include "ka_base_pub.h"

#include "ka_common_pub.h"



#define COPY_FUNC_NUM 2

#define FILTER_MAX_LEN 128



STATIC int copy_from_user_local(void *to, unsigned long to_len, const void *from, unsigned long from_len)

{

    return (int)ka_base_copy_from_user(to, (void __ka_user *)from, from_len);

}



STATIC int copy_to_user_local(void *to, unsigned long to_len, const void *from, unsigned long from_len)

{

    return (int)ka_base_copy_to_user((void __ka_user *)to, from, from_len);

}



STATIC int memcpy_s_local(void *to, unsigned long to_len, const void *from, unsigned long from_len)

{

    return memcpy_s(to, to_len, from, from_len);

}



int (*copy_func_to[MSG_FROM_TYPE_MAX])(void *to, unsigned long to_len, const void *from, unsigned long from_len) = {

    copy_to_user_local,

    memcpy_s_local,

    copy_to_user_local,

};



int (*copy_func_from[MSG_FROM_TYPE_MAX])(void *to, unsigned long to_len, const void *from, unsigned long from_len) = {

    copy_from_user_local,

    memcpy_s_local,

    copy_from_user_local,

};



STATIC int dms_open(ka_inode_t* inode, ka_file_t* filep)

{

    struct urd_file_private_stru* file_private = NULL;

    if (filep == NULL) {

        return -EINVAL;

    }



    if (!ka_system_try_module_get(KA_THIS_MODULE)) {

        return -EBUSY;

    }



    file_private =

        (struct urd_file_private_stru*)ka_mm_kzalloc(sizeof(struct urd_file_private_stru), KA_GFP_KERNEL | __KA_GFP_ACCOUNT);

    if (file_private == NULL) {

        dms_err("ka_mm_kzalloc failed. (size=%lu)\n", sizeof(struct urd_file_private_stru));

        ka_system_module_put(KA_THIS_MODULE);

        return -ENOMEM;

    }

    file_private->owner_pid = ka_task_get_current_tgid();

    filep->private_data = (void *)file_private;

    ka_base_atomic_set(&file_private->work_count, 0);

    return 0;

}



STATIC int dms_release(ka_inode_t* inode, ka_file_t* filep)

{

    struct urd_file_private_stru* file_private = NULL;



    if (filep == NULL) {

        return -EINVAL;

    }

    ka_system_module_put(KA_THIS_MODULE);



    file_private = filep->private_data;

    if (file_private == NULL) {

        return -EBADFD;

    }

    urd_notifier_call(URD_NOTIFIER_RELEASE, file_private);



    ka_mm_kfree(file_private);

    file_private = NULL;

    return 0;

}



STATIC unsigned int dms_msg_poll(ka_file_t* filep, ka_poll_table_struct_t* wait)

{

    return KA_POLLERR;

}



STATIC int dms_make_feature_key(struct urd_cmd* cmd, DMS_FEATURE_ARG_S* arg)

{

    int ret;

    char* filter = NULL;



    if ((cmd->filter_len > FILTER_MAX_LEN) || ((cmd->filter_len == 0) && (cmd->filter != NULL))) {

        dms_err("filter_len is error. (filter_len=%u)", cmd->filter_len);

        return -EINVAL;

    }

    arg->key = (char*)ka_mm_kzalloc(KV_KEY_MAX_LEN + 1, KA_GFP_KERNEL | __KA_GFP_ACCOUNT);

    if (arg->key == NULL) {

        dms_err("ka_mm_kzalloc failed. (size=%d)\n", KV_KEY_MAX_LEN);

        return -ENOMEM;

    }

    if (cmd->filter != NULL) {

        filter = ka_mm_kzalloc(cmd->filter_len + 1, KA_GFP_KERNEL | __KA_GFP_ACCOUNT);

        if (filter == NULL) {

            dms_err("ka_mm_kzalloc failed. (size=%u)\n", cmd->filter_len + 1);

            ka_mm_kfree(arg->key);

            arg->key = NULL;

            return -ENOMEM;

        }

        ret = copy_func_from[arg->msg_source]((void*)filter, (unsigned long)cmd->filter_len + 1,

            (void*)cmd->filter, (unsigned long)cmd->filter_len);

        if (ret != 0) {

            dms_err("copy_func_from failed. (ret=%d; size=%u)\n", ret, cmd->filter_len);

            ka_mm_kfree(arg->key);

            arg->key = NULL;

            ka_mm_kfree(filter);

            filter = NULL;

            return -EFAULT;

        }

    }

    ret = dms_feature_make_key(cmd->main_cmd, cmd->sub_cmd, filter, arg->key, KV_KEY_MAX_LEN + 1);

    if (ret != 0) {

        dms_err(

            "make feature's key failed. (ret=%d; main_cmd=%u; sub_cmd=%u)\n", ret, cmd->main_cmd, cmd->sub_cmd);

        ka_mm_kfree(arg->key);

        arg->key = NULL;

    }

    if (filter != NULL) {

        ka_mm_kfree(filter);

        filter = NULL;

    }

    return ret;

}



STATIC int dms_make_feature_input(struct urd_cmd_para* cmd_para, DMS_FEATURE_ARG_S* arg)

{

    int ret;

    arg->input = NULL;

    arg->input_len = 0;

    /* means no input data */

    if ((cmd_para->input_len == 0) && (cmd_para->input == NULL)) {

        return 0;

    }



    if ((cmd_para->input_len != 0) && (cmd_para->input != NULL) && (cmd_para->input_len <= DMS_MAX_INPUT_LEN)) {

        arg->input = ka_mm_kmalloc(cmd_para->input_len, KA_GFP_KERNEL | __KA_GFP_ACCOUNT);

        if (arg->input == NULL) {

            dms_err("ka_mm_kzalloc failed. (size=%u)\n", cmd_para->input_len);

            return -ENOMEM;

        }

        arg->input_len = cmd_para->input_len;

        ret = copy_func_from[arg->msg_source]((void*)arg->input, (unsigned long)arg->input_len,

            (void*)cmd_para->input, (unsigned long)cmd_para->input_len);

        if (ret != 0) {

            dms_err("copy_func_from failed. (ret=%d; size=%u)\n", ret, cmd_para->input_len);

            ka_mm_kfree(arg->input);

            arg->input = NULL;

            return -EFAULT;

        }

        return 0;

    } else {

        dms_err("Input para error. (input_len=%u)\n", cmd_para->input_len);

        return -EINVAL;

    }

}



#define DMS_OUTPUT_2M_LIMIT (2 * 1024 * 1024)

// apply high-capacity memory for output

STATIC int dms_make_feature_max_output(struct urd_cmd_para* cmd_para, DMS_FEATURE_ARG_S* arg)

{

    if (cmd_para->output_len > DMS_OUTPUT_2M_LIMIT) {

        dms_err("Output len is larger than 2M limit. (output_len=%u)\n", cmd_para->output_len);

        return -EINVAL;

    }



    arg->output = ka_mm_vzalloc(cmd_para->output_len);

    if (arg->output == NULL) {

        dms_err("ka_mm_vzalloc failed. (size=%u)\n", cmd_para->output_len);

        return -ENOMEM;

    }

    arg->output_len = cmd_para->output_len;

    return 0;

}



// if need high-capacity memory(greater than DMS_MAX_OUTPUT_LEN) return true

static bool dms_is_need_high_capacity_memory(struct urd_cmd* cmd)

{

    //  get history fault event info need high-capacity memory

    if ((cmd->main_cmd == DMS_MAIN_CMD_BASIC && cmd->sub_cmd == DMS_SUBCMD_GET_HISTORY_FAULT_EVENT) ||

        (cmd->main_cmd == DMS_MAIN_CMD_BASIC && cmd->sub_cmd == DMS_SUBCMD_GET_FAULT_EVENT_OBJ) ||

        (cmd->main_cmd == DMS_MAIN_CMD_BASIC && cmd->sub_cmd == DMS_SUBCMD_GET_DEVICE_STATE) ||

        (cmd->main_cmd == DMS_MAIN_CMD_BASIC && cmd->sub_cmd == DMS_SUBCMD_GET_DEV_PROBE_LIST) ||

        (cmd->main_cmd == DMS_MAIN_CMD_BASIC && cmd->sub_cmd == DMS_SUBCMD_GET_ALL_DEV_LIST) ||

        (cmd->main_cmd == DMS_MAIN_CMD_BASIC && cmd->sub_cmd == DMS_SUBCMD_GET_DEVICE_FROM_CHIP)) {

        return true;

    }

    return false;

}



STATIC int dms_make_feature_output(struct urd_cmd *cmd, struct urd_cmd_para *cmd_para, DMS_FEATURE_ARG_S* arg)

{

    arg->output = NULL;

    arg->output_len = 0;



    /* means no output data */

    if (cmd_para->output_len == 0) {

        return 0;

    }



    // need high-capacity memory, maybe greater than DMS_MAX_OUTPUT_LEN

    if (dms_is_need_high_capacity_memory(cmd)) {

        return dms_make_feature_max_output(cmd_para, arg);

    }



    if (cmd_para->output_len <= DMS_MAX_OUTPUT_LEN) {

        arg->output = ka_mm_vzalloc(cmd_para->output_len);

        if (arg->output == NULL) {

            dms_err("ka_mm_vzalloc failed. (size=%u)\n", cmd_para->output_len);

            return -ENOMEM;

        }

        arg->output_len = cmd_para->output_len;

        return 0;

    } else {

        dms_err("Output len error. (output_len=%u)\n", cmd_para->output_len);

        return -EINVAL;

    }

}



STATIC int dms_proc_feature_output(struct urd_cmd_para *cmd_para, DMS_FEATURE_ARG_S* arg)

{

    int ret;



    if (cmd_para->output != NULL) {

        ret = copy_func_to[arg->msg_source]((void*)cmd_para->output, (unsigned long)cmd_para->output_len,

            (void*)arg->output, (unsigned long)cmd_para->output_len);

        if (ret != 0) {

            dms_err("copy_func_to failed. (size=%u)\n", cmd_para->output_len);

            return -EFAULT;

        }

    }



    return 0;

}



STATIC void dms_free_msg(DMS_FEATURE_ARG_S* arg)

{

    if (arg->key != NULL) {

        ka_mm_kfree(arg->key);

        arg->key = NULL;

    }

    if (arg->input != NULL) {

        ka_mm_kfree(arg->input);

        arg->input = NULL;

    }

    if (arg->output != NULL) {

        ka_mm_vfree(arg->output);

        arg->output = NULL;

    }

    return;

}



STATIC int dms_make_up_msg_arg(struct urd_cmd *cmd, struct urd_cmd_para *cmd_para, DMS_FEATURE_ARG_S* arg)

{

    int ret;

    ret = dms_make_feature_key(cmd, arg);

    if (ret != 0) {

        return ret;

    }



    ret = dms_make_feature_input(cmd_para, arg);

    if (ret != 0) {

        dms_free_msg(arg);

        return ret;

    }



    ret = dms_make_feature_output(cmd, cmd_para, arg);

    if (ret != 0) {

        dms_free_msg(arg);

        return ret;

    }

    return 0;

}



STATIC int dms_cmd_process(u32 devid, struct urd_cmd *cmd, struct urd_cmd_para *cmd_para, u32 msg_source)

{

    int ret;

    DMS_FEATURE_ARG_S arg = {0};

    if (msg_source >= MSG_FROM_TYPE_MAX) {

        dms_err("msg_source is wrong. (msg_source=%u)\n", msg_source);

        return -EINVAL;

    }

    arg.msg_source = msg_source;

    arg.devid = devid;



    /* make ka_task_up call arg */

    ret = dms_make_up_msg_arg(cmd, cmd_para, &arg);

    if (ret != 0) {

        dms_err("Make ka_task_up message arg failed. (ret=%d)\n", ret);

        return ret;

    }



    /* call feature process */

    ret = dms_feature_process(&arg);

    if (ret != 0) {

        /* do not print err info */

        goto out;

    }



    /* copy out data to user */

    ret = dms_proc_feature_output(cmd_para, &arg);

    if (ret != 0) {

        dms_err("Process output failed. (ret=%d; size=%u)\n", ret, cmd_para->output_len);

    }



out:

    dms_free_msg(&arg);

    return ret;

}



#define CMD_MASK (0x0000FFFFU)

STATIC bool check_cmd_arg_valid(unsigned int cmd, struct urd_ioctl_arg ctl_arg)

{

    if ((cmd & CMD_MASK) == ((ctl_arg.cmd.main_cmd << _KA_IOC_TYPESHIFT) + (ctl_arg.cmd.sub_cmd << _KA_IOC_NRSHIFT))) {

        return true;

    } else {

        return false;

    }

}



int dms_cmd_process_from_kernel(u32 devid, struct urd_cmd *cmd, struct urd_cmd_para *cmd_para)

{

    if ((cmd == NULL) || (cmd_para == NULL)) {

        dms_err("Invalid file_private_data.\n");

        return -EINVAL;

    }



    return dms_cmd_process(devid, cmd, cmd_para, MSG_FROM_KERNEL);

}

KA_EXPORT_SYMBOL(dms_cmd_process_from_kernel);



STATIC long dms_ioctl(ka_file_t* filep, unsigned int ioctl_cmd, unsigned long arg)

{

    int ret;

    struct urd_ioctl_arg ctl_arg = {0};

    u32 msg_source;



    if ((filep == NULL) || (arg == 0)) {

        dms_err("Invalid file_private_data.\n");

        return -EINVAL;

    }



    /* copy ioctl arg from user */

    ret = ka_base_copy_from_user(&ctl_arg, (void*)((uintptr_t)arg), sizeof(struct urd_ioctl_arg));

    if (ret != 0) {

        dms_err("copy_from_user_safe failed.\n");

        return ret;

    }

    if ((ioctl_cmd == URD_IOCTL_CMD) || (check_cmd_arg_valid(ioctl_cmd, ctl_arg))) {

        msg_source = (ascend_intf_is_restrict_access(filep) ? MSG_FROM_USER_REST_ACC : MSG_FROM_USER);

        ret = dms_cmd_process(ctl_arg.devid, &ctl_arg.cmd, &ctl_arg.cmd_para, msg_source);

    } else {

        dms_err("Invalid commmad. (command=%u; main_cmd=%u; sub_cmd=%u)\n", ioctl_cmd, ctl_arg.cmd.main_cmd, ctl_arg.cmd.sub_cmd);

        ret = -ENODEV;

    }

    return ret;

}



const ka_file_operations_t g_dms_file_operations = {

    ka_fs_init_f_owner(KA_THIS_MODULE)

    ka_fs_init_f_open(dms_open)

    ka_fs_init_f_release(dms_release)

    ka_fs_init_f_poll(dms_msg_poll)

    ka_fs_init_f_unlocked_ioctl(dms_ioctl)

};



STATIC int urd_release_prepare(ka_file_t *file_op, unsigned long mode)

{

    if (mode != NOTIFY_MODE_RELEASE_PREPARE) {

        dms_err("Invalid mode. (mode=%lu)\n", mode);

        return -EINVAL;

    }



    if ((file_op == NULL) || (file_op->private_data == NULL)) {

        return -EBADFD;

    }



    urd_notifier_call(URD_NOTIFIER_RELEASE_PREPARE, file_op->private_data);

    return 0;

}



static const struct notifier_operations urd_notifier_ops = {

    .owner = KA_THIS_MODULE,

    .notifier_call = urd_release_prepare,

};



int urd_init(void)

{

    int ret;



    dms_info("dms urd driver init start.\n");

    ret = drv_davinci_register_sub_module(DAVINCI_INTF_MODULE_URD, &g_dms_file_operations);

    if (ret != 0) {

        dms_err("Failed to register DMS module. (ret=%d)\n", ret);

        return ret;

    }



    ret = drv_ascend_register_notify(DAVINCI_INTF_MODULE_URD, &urd_notifier_ops);

    if (ret != 0) {

        dms_err("Failed to register davinci notify. (ret=%d)\n", ret);

        goto unregister_sub_module;

    }



    ret = dms_kv_init();

    if (ret) {

        dms_err("Failed to init dms kv. (ret=%d)\n", ret);

        goto unregister_davinci_notify;

    }



    dms_info("dms urd driver init success.\n");

    return 0;



unregister_davinci_notify:

    (void)drv_ascend_unregister_notify(DAVINCI_INTF_MODULE_URD);

unregister_sub_module:

    (void)drv_ascend_unregister_sub_module(DAVINCI_INTF_MODULE_URD);

    return ret;

}



void urd_exit(void)

{

    dms_info("Dms urd exit start.\n");

    dms_kv_uninit();

    (void)drv_ascend_unregister_notify(DAVINCI_INTF_MODULE_URD);

    (void)drv_ascend_unregister_sub_module(DAVINCI_INTF_MODULE_URD);

    dms_info("Dms urd driver exit success.\n");

    return;

}