/*
 * 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_base_pub.h"
#include "ka_task_pub.h"
#include "ka_kernel_def_pub.h"
#include "securec.h"
#include "pbl/pbl_uda.h"
#include "pbl/pbl_runenv_config.h"
#include "urd_acc_ctrl.h"
#include "dms/dms_cmd_def.h"
#include "pbl_urd_sub_cmd_def.h"
#include "udis_management.h"
#include "udis_log.h"
#include "udis_cmd.h"

STATIC ka_rw_semaphore_t g_udis_func_sema= {0};
STATIC struct udis_func_info g_udis_func_info_list[UDIS_FUNC_MAX_NUM] = {0};

int udis_feature_get_device_info(struct urd_cmd *cmd, struct urd_cmd_kernel_para *kernel_para,
    struct urd_cmd_para *cmd_para);
int udis_feature_set_device_info(struct urd_cmd *cmd, struct urd_cmd_kernel_para *kernel_para,
    struct urd_cmd_para *cmd_para);

#define DMS_MODULE_UDIS "udis"
INIT_MODULE_FUNC(DMS_MODULE_UDIS);
EXIT_MODULE_FUNC(DMS_MODULE_UDIS);
BEGIN_DMS_MODULE_DECLARATION(DMS_MODULE_UDIS)
BEGIN_FEATURE_COMMAND()
#ifdef DRV_HOST
ADD_DEV_FEATURE_COMMAND(DMS_MODULE_UDIS, DMS_MAIN_CMD_BASIC, DMS_SUBCMD_GET_UDIS_DEVICE_INFO,
    NULL, NULL, DMS_SUPPORT_ALL, udis_feature_get_device_info)
ADD_DEV_FEATURE_COMMAND(DMS_MODULE_UDIS, DMS_MAIN_CMD_BASIC, DMS_SUBCMD_SET_UDIS_DEVICE_INFO,
    NULL, NULL, DMS_ACC_ROOT_ONLY | DMS_ENV_ALL | DMS_VDEV_ALL, udis_feature_set_device_info)
#else
ADD_DEV_FEATURE_COMMAND(DMS_MODULE_UDIS, DMS_MAIN_CMD_BASIC, DMS_SUBCMD_GET_UDIS_DEVICE_INFO,
    NULL, NULL, DMS_SUPPORT_ALL_USER, udis_feature_get_device_info)
ADD_DEV_FEATURE_COMMAND(DMS_MODULE_UDIS, DMS_MAIN_CMD_BASIC, DMS_SUBCMD_SET_UDIS_DEVICE_INFO,
    NULL, "dmp_daemon", DMS_SUPPORT_ALL, udis_feature_set_device_info)
#endif
END_FEATURE_COMMAND()
END_MODULE_DECLARATION();

int hal_kernel_register_udis_func(UDIS_MODULE_TYPE module_type, const char *name, udis_trigger func)
{
    int i = 0;
    int ret;

    if (module_type >= UDIS_MODULE_MAX || name == NULL || func == NULL) {
        udis_err("Invalid para. (module_type=%u; module_type_max=%u; name=%s; func=%s)\n",
            module_type, UDIS_MODULE_MAX - 1, (name == NULL) ? "NULL" : "OK", (func == NULL) ? "NULL" : "OK");
        return -EINVAL;
    }

    ka_task_down_write(&g_udis_func_sema);
    for (i = 0; i < KA_BASE_ARRAY_SIZE(g_udis_func_info_list); i++) {
        if (g_udis_func_info_list[i].module_type == module_type &&
            ka_base_strcmp(g_udis_func_info_list[i].name, name) == 0) {
            ka_task_up_write(&g_udis_func_sema);
            udis_err("Udis func duplicate register. (module_type=%u; name=%s)\n", module_type, name);
            return -EEXIST;
        }

        if (g_udis_func_info_list[i].func == NULL) {
            ret = strcpy_s(g_udis_func_info_list[i].name, UDIS_MAX_NAME_LEN, name);
            if (ret != 0) {
                ka_task_up_write(&g_udis_func_sema);
                udis_err("Strcpy_s failed. (module_type=%u; name=%s; ret=%d)\n",
                    module_type, name, ret);
                return -EINVAL;
            }
            g_udis_func_info_list[i].module_type = module_type;
            g_udis_func_info_list[i].func = func;
            break;
        }
    }

    ka_task_up_write(&g_udis_func_sema);
    return 0;
}
KA_EXPORT_SYMBOL_GPL(hal_kernel_register_udis_func);

int hal_kernel_unregister_udis_func(UDIS_MODULE_TYPE module_type, const char *name)
{
    int i = 0;
    int ret;

    if (module_type >= UDIS_MODULE_MAX || name == NULL) {
        udis_err("Invalid para. (module_type=%u; module_type_max=%u; name=%s)\n",
            module_type, UDIS_MODULE_MAX - 1, (name == NULL) ? "NULL" : "OK");
        return -EINVAL;
    }

    ka_task_down_write(&g_udis_func_sema);
    for (i = 0; i < KA_BASE_ARRAY_SIZE(g_udis_func_info_list); i++) {
        if (g_udis_func_info_list[i].module_type == module_type &&
            ka_base_strcmp(g_udis_func_info_list[i].name, name) == 0) {
            ret = memset_s(&g_udis_func_info_list[i], sizeof(struct udis_func_info), 0, sizeof(struct udis_func_info));
            if (ret != 0) {
                ka_task_up_write(&g_udis_func_sema);
                udis_err("Memset_s failed. (module_type=%u; name=%s; ret=%d)\n",
                    module_type, name, ret);
                return -EINVAL;
            }       
            break;
        }
    }

    ka_task_up_write(&g_udis_func_sema);
    return 0;
}
KA_EXPORT_SYMBOL_GPL(hal_kernel_unregister_udis_func);

STATIC int udis_unregister_all_func(void)
{
    int ret;

    ret = memset_s(g_udis_func_info_list, sizeof(g_udis_func_info_list), 0, sizeof(g_udis_func_info_list));
    if (ret != 0) {
        udis_err("Memset_s failed. (ret=%d)\n", ret);
        return -EINVAL;
    }

    return 0;
}

STATIC udis_trigger udis_get_trigger_func(int module_type, const char *name)
{
    int i = 0;

    ka_task_down_read(&g_udis_func_sema);
    for (i = 0; i < KA_BASE_ARRAY_SIZE(g_udis_func_info_list); i++) {
        if (g_udis_func_info_list[i].module_type == module_type &&
            ka_base_strcmp(g_udis_func_info_list[i].name, name) == 0 &&
            g_udis_func_info_list[i].func != NULL) {
            ka_task_up_read(&g_udis_func_sema);
            return g_udis_func_info_list[i].func;
        }
    }

    ka_task_up_read(&g_udis_func_sema);
    return NULL;
}

STATIC int udis_ioctl_input_check(unsigned int udevid, unsigned int module_type, const char *name,
    const void *buf, unsigned int buf_len)
{
    unsigned int name_len = 0;

    if (module_type >= UDIS_MODULE_MAX) {
        udis_err("Invalid module_type. (udevid=%u; module_type=%u; name=%s; max_module_type=%u)\n",
            udevid, module_type, name, UDIS_MODULE_MAX - 1);
        return -EINVAL;
    }

    name_len = ka_base_strnlen(name, UDIS_MAX_NAME_LEN);
    if ((name_len == 0) || (name_len>= UDIS_MAX_NAME_LEN)) {
        udis_err("Invalid name. (udevid=%u; module_type=%u; name_len=%u; max_name_len=%d)\n",
            udevid, module_type, name_len, UDIS_MAX_NAME_LEN - 1);
        return -EINVAL;
    }

    if (buf == NULL) {
        udis_err("Invalid param, buf is NULL. (udevid=%u; module_type=%u; name=%s)\n",
            udevid, module_type, name);
        return -EINVAL;
    }

    if ((buf_len == 0) || (buf_len > UDIS_MAX_DATA_LEN)) {
        udis_err("Invalid buf len. (udevid=%u; module_type=%u; name=%s; buf_len=%u; max_buf_len=%u)\n",
            udevid, module_type, name, buf_len, UDIS_MAX_DATA_LEN);
        return -EINVAL;
    }
    return 0;
}

#define OWNER_ID_LEN 8
#define HEXADECIMAL 16
STATIC int udis_get_svm_process_mem_name(unsigned int udevid, const char *in_name, char *out_name)
{
    int ret;
#ifdef DRV_HOST
    u32 vfid = 0;
    u32 owner_id = 0;
    u32 hostpid = 0;
    char temp_name[UDIS_MAX_NAME_LEN] = {0};
    ka_struct_pid_t *pgrp = NULL;
    struct uda_mia_dev_para mia_para = {0};

    if (!uda_is_pf_dev(udevid)) {
        ret = uda_udevid_to_mia_devid(udevid, &mia_para);
        if (ret != 0) {
            udis_err("The udevid to mia devid failed. (udev_id=%u; ret=%d)\n");
            return ret;
        }

        vfid = mia_para.sub_devid + 1;
    }

    ret = strncpy_s(temp_name, UDIS_MAX_NAME_LEN, in_name, OWNER_ID_LEN);
    ret += ka_base_kstrtou32(temp_name, HEXADECIMAL, &owner_id);
    if (ret != 0) {
        udis_err("Get owner id from name failed. (udevid=%u; name=%s; ret=%d)\n", udevid, in_name, ret);
        return ret;
    }

    if (run_in_normal_docker() == true) {
        pgrp = ka_task_find_get_pid(owner_id);
        if (pgrp == NULL) {
            udis_err("Pgrp is NULL\n");
            return -EINVAL;
        }

        hostpid = pgrp->numbers[0].nr; // 0: hostpid
        ka_task_put_pid(pgrp);
    } else {
        hostpid = owner_id;
    }

    ret = snprintf_s(out_name, UDIS_MAX_NAME_LEN, UDIS_MAX_NAME_LEN - 1, "%08x%04xmem", hostpid, vfid);
    if (ret < 0) {
        udis_err("Snprintf_s failed. (udevid=%u; in_name=%s; ret=%d)\n", udevid, in_name, ret);
        return -EINVAL;
    }
#else
    ret = strcpy_s(out_name, UDIS_MAX_NAME_LEN, in_name);
    if (ret != 0) {
        udis_err("Strcpy_s failed. (udevid=%u; in_name=%s; ret=%d)\n", udevid, in_name, ret);
        return -EINVAL;
    }
#endif
    return 0;
}

STATIC int udis_get_dev_name(unsigned int udevid, UDIS_MODULE_TYPE module_type, const char *in_name, char *out_name)
{
    int ret;

    if (module_type == UDIS_MODULE_SVM && ka_base_strstr(in_name, "mem") != NULL) {
        return udis_get_svm_process_mem_name(udevid, in_name, out_name);
    }

    ret = strcpy_s(out_name, UDIS_MAX_NAME_LEN, in_name);
    if (ret != 0) {
        udis_err("Strcpy_s failed. (udevid=%u; module_type=%u; name=%s; ret=%d)\n",
            udevid, module_type, in_name, ret);
        return -EINVAL;
    }

    return 0;
}

#define TS_TRIGGER_INTERVAL_TIME 100
#define UDIS_TRIGGER_INTERVAL_TIME 200
STATIC int udis_trigger_device_info(unsigned int udevid, UDIS_MODULE_TYPE module_type, struct udis_dev_info *info)
{
    u32 interval_time = UDIS_TRIGGER_INTERVAL_TIME;
    udis_trigger func = NULL;
    int ret;

    func = udis_get_trigger_func(module_type, info->name);
    if (func == NULL) {
        return -ENODATA;
    }

    if (module_type == UDIS_MODULE_TS) {
        interval_time = TS_TRIGGER_INTERVAL_TIME;  // 100 ms
    }

    if (ka_system_ktime_get_raw_ns() / NSEC_PER_MSEC - info->last_update_time <= interval_time) {
        return 0;
    }

    ret = func(udevid, info);
    if (ret != 0) {
        udis_err("Trigger udis info failed. (udevid=%u; module_type=%u; name=%s; ret=%d)\n",
            udevid, module_type, info->name, ret);
        return ret;
    }

    return 0;
}

STATIC int udis_get_device_info(unsigned int udevid, struct udis_get_ioctl_in *input,
    struct udis_get_ioctl_out *output)
{
    int ret;
    int ret1;
    struct udis_dev_info info = {{0}};

    ret = udis_get_dev_name(udevid, input->module_type, input->name, info.name);
    if (ret != 0) {
        udis_err("Udis get dev name failed. (udevid=%u; module_type=%u; name=%s; ret=%d)\n",
            udevid, input->module_type, input->name, ret);
        return ret;
    }

    ret = hal_kernel_get_udis_info(udevid, input->module_type, &info);
    if (ret != 0 && ret != -ENODATA) {
        udis_ex_notsupport_err(ret, "Get udis info failed. (udevid=%u; module_type=%u; name=%s; ret=%d)\n",
            udevid, input->module_type, info.name, ret);
        return ret;
    }

    ret1 = udis_trigger_device_info(udevid, input->module_type, &info);
    if (ret1 != 0 && ret1 != -ENODATA) {
        udis_ex_notsupport_err(ret, "Trigger udis info failed. (udevid=%u; module_type=%u; name=%s; ret=%d)\n",
            udevid, input->module_type, info.name, ret);
        return ret1;
    }

    if (ret == -ENODATA && ret1 == -ENODATA) {
        /* If a certain type of information does not support VF, the driver will not register or set the `name` for VF.
         * In this case, `get intf` will return `-ENODATA` because it cannot find the same name,
         * so `-ENODATA` is considered equivalent to `-EOPNOTSUPP` here.
         */
        ret = -EOPNOTSUPP;
        udis_ex_notsupport_err(ret, "Trigger udis info failed. (udevid=%u; module_type=%u; name=%s; ret=%d)\n",
            udevid, input->module_type, info.name, ret);
        return ret;
    }


    if (input->in_len < info.data_len) {
        udis_err("User buf length is too small. (udevid=%u; module_type=%u; name=%s; in_len=%u; data_len=%u)\n",
            udevid, input->module_type, info.name, input->in_len, info.data_len);
        return -EINVAL;
    }

    ret = (int)ka_base_copy_to_user((void *)((uintptr_t)input->data), &info.data, info.data_len);
    if (ret != 0) {
        udis_err("Copy_to_user failed. (udevid=%u; module_type=%u; name=%s; ret=%d)\n",
            udevid, input->module_type, info.name, ret);
        return ret;
    }

    output->out_len = info.data_len;
    output->last_update_time = info.last_update_time;
    output->acc_ctrl = info.acc_ctrl;

    return 0;
}

STATIC int udis_set_device_info(unsigned int udevid, const struct udis_set_ioctl_in *input)
{
    int ret;
    struct udis_dev_info info = {{0}};

    ret = strcpy_s(info.name, UDIS_MAX_NAME_LEN, input->name);
    if (ret != 0) {
        udis_err("Strcpy_s failed. (udevid=%u; module_type=%u; name=%s; ret=%d)\n",
            udevid, input->module_type, input->name, ret);
        return -EINVAL;
    }

    ret = (int)ka_base_copy_from_user(&info.data, (void *)((uintptr_t)input->data), input->data_len);
    if (ret != 0) {
        udis_err("Copy_from_user failed. (udevid=%u; module_type=%u; name=%s; ret=%d)\n",
            udevid, input->module_type, info.name, ret);
        return ret;
    }
    info.acc_ctrl = input->acc_ctrl;
    info.data_len = input->data_len;

    ret = hal_kernel_set_udis_info(udevid, input->module_type, &info);
    if (ret != 0) {
        udis_ex_notsupport_err(ret, "Set udis info failed. (udevid=%u; module_type=%u; name=%s; ret=%d)\n",
            udevid, input->module_type, info.name, ret);
        return ret;
    }

    return 0;
}

int udis_feature_get_device_info(struct urd_cmd *cmd, struct urd_cmd_kernel_para *kernel_para,
    struct urd_cmd_para *cmd_para)
{
    int ret;
    struct udis_get_ioctl_in *input = NULL;
    struct udis_get_ioctl_out *output = NULL;

    if ((cmd_para->input == NULL) || (cmd_para->input_len != sizeof(struct udis_get_ioctl_in))) {
        udis_err("Invalid input. (in_is_null=%d; in_len=%d; valid_in_len=%zu)\n",
            cmd_para->input == NULL, cmd_para->input_len, sizeof(struct udis_get_ioctl_in));
        return -EINVAL;
    }

    if ((cmd_para->output == NULL) || (cmd_para->output_len != sizeof(struct udis_get_ioctl_out))) {
        udis_err("Invalid output. (out_is_null=%d; out_len=%d; valid_out_len=%zu)\n",
            cmd_para->output == NULL, cmd_para->output_len, sizeof(struct udis_get_ioctl_out));
        return -EINVAL;
    }

    input = (struct udis_get_ioctl_in *)cmd_para->input;
    output = (struct udis_get_ioctl_out *)cmd_para->output;

    ret = udis_ioctl_input_check(kernel_para->udevid, input->module_type, input->name, input->data, input->in_len);
    if (ret != 0) {
        return ret;
    }

    ret = udis_get_device_info(kernel_para->udevid, input, output);
    if (ret != 0) {
        return ret;
    }

    return 0;
}

int udis_feature_set_device_info(struct urd_cmd *cmd, struct urd_cmd_kernel_para *kernel_para,
    struct urd_cmd_para *cmd_para)
{
    int ret;
    struct udis_set_ioctl_in *input = NULL;

    if ((cmd_para->input == NULL) || (cmd_para->input_len != sizeof(struct udis_set_ioctl_in))) {
        udis_err("Invalid input. (in_is_null=%d; in_len=%d; valid_in_len=%zu)\n",
            cmd_para->input == NULL, cmd_para->input_len, sizeof(struct udis_set_ioctl_in));
        return -EINVAL;
    }

    input = (struct udis_set_ioctl_in *)cmd_para->input;

    ret = udis_ioctl_input_check(kernel_para->udevid, input->module_type, input->name, input->data, input->data_len);
    if (ret != 0) {
        return ret;
    }

    ret = udis_set_device_info(kernel_para->udevid, input);
    if (ret != 0) {
        return ret;
    }

    return 0;
}

void udis_cmd_init(void)
{
    CALL_INIT_MODULE(DMS_MODULE_UDIS);
    ka_task_init_rwsem(&g_udis_func_sema);
    udis_info("Udis register urd handle success.\n");
}

void udis_cmd_exit(void)
{
    udis_unregister_all_func();
    CALL_EXIT_MODULE(DMS_MODULE_UDIS);
    udis_info("Udis unregister urd handle success.\n");
}