/*
 * 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_pci_pub.h"
#include "ka_task_pub.h"
#include "ka_kernel_def_pub.h"
#include "ka_list_pub.h"
#include "securec.h"
#include "pbl_uda.h"
#include "pbl_mem_alloc_interface.h"
#include "pbl/pbl_davinci_api.h"
#include "davinci_interface.h"
#include "udis_management.h"
#include "udis_timer.h"
#include "udis_msg.h"
#include "udis_log.h"
#include "udis_interface.h"


#define UDIS_HOST_NOTIFIER "udis_host"
#define UDIS_HB_MONITOR "udis_hb_mon"
#define UDIS_HB_MONITOR_PERIOD_MS 1000

#define PCI_VENDOR_ID_HUAWEI            0x19e5
#define DEVDRV_DIVERSITY_PCIE_VENDOR_ID 0xFFFF
static const ka_pci_device_id_t g_udis_tbl[] = {
    { KA_PCI_VDEVICE(HUAWEI, 0xd100),           0 },
    { KA_PCI_VDEVICE(HUAWEI, 0xd105),           0 },
    { KA_PCI_VDEVICE(HUAWEI, PCI_DEVICE_CLOUD), 0 },
    { KA_PCI_VDEVICE(HUAWEI, 0xd801),           0 },
    { KA_PCI_VDEVICE(HUAWEI, 0xd500),           0 },
    { KA_PCI_VDEVICE(HUAWEI, 0xd501),           0 },
    { KA_PCI_VDEVICE(HUAWEI, 0xd802),           0 },
    { KA_PCI_VDEVICE(HUAWEI, 0xd803),           0 },
    { KA_PCI_VDEVICE(HUAWEI, 0xd804),           0 },
    { KA_PCI_VDEVICE(HUAWEI, 0xd805),           0 },
    { DEVDRV_DIVERSITY_PCIE_VENDOR_ID, 0xd500, KA_PCI_ANY_ID, KA_PCI_ANY_ID, 0, 0, 0 },
    { 0x20C6, 0xd500, KA_PCI_ANY_ID, KA_PCI_ANY_ID, 0, 0, 0 },
    { 0x203F, 0xd500, KA_PCI_ANY_ID, KA_PCI_ANY_ID, 0, 0, 0 },
    { 0x20E9, 0xd500, KA_PCI_ANY_ID, KA_PCI_ANY_ID, 0, 0, 0 },
    { 0x20C6, 0xd802, KA_PCI_ANY_ID, KA_PCI_ANY_ID, 0, 0, 0 },
    { 0x203F, 0xd802, KA_PCI_ANY_ID, KA_PCI_ANY_ID, 0, 0, 0 },
    { 0x20E9, 0xd802, KA_PCI_ANY_ID, KA_PCI_ANY_ID, 0, 0, 0 },
    {}
};
KA_MODULE_DEVICE_TABLE(pci, g_udis_tbl);

STATIC int udis_common_chan_register(unsigned int udevid)
{
    int ret;
    struct devdrv_common_msg_client *client = NULL;

    client = udis_get_common_msg_client();
    if (client == NULL) {
        udis_err("Get common msg client failed. (udevid=%u)\n", udevid);
        return -EFAULT;
    }

    ret = devdrv_register_common_msg_client(client);
    if (ret != 0) {
        udis_err("Udis register common msg channel failed. (udevid=%u; ret=%d)\n", udevid, ret);
        return ret;
    }
    return 0;
}

STATIC void udis_common_chan_unregister(unsigned int udevid)
{
    struct devdrv_common_msg_client *client = NULL;
    client = udis_get_common_msg_client();
    if (client == NULL) {
        udis_err("Get common msg client failed. (udevid=%u)\n", udevid);
        return;
    }
    devdrv_unregister_common_msg_client(udevid, client);
}

STATIC int udis_cb_constructor(unsigned int udevid, struct udis_ctrl_block *udis_cb)
{
    int ret, i;

    if ((devdrv_get_connect_protocol(udevid) == CONNECT_PROTOCOL_UB)) {
        udis_cb->udis_info_buf = (struct udis_info_stu *)dbl_kzalloc(UDIS_MODULE_OFFSET * UDIS_MODULE_MAX, KA_GFP_KERNEL | __KA_GFP_ACCOUNT);
        if (udis_cb->udis_info_buf == NULL) {
            udis_err("Failed to call dbl_kzalloc. (udevid=%u)\n", udevid);
            return -ENOMEM;
        }
    } else {
        udis_cb->udis_info_buf = (struct udis_info_stu *)hal_kernel_devdrv_dma_alloc_coherent(uda_get_device(udevid),
            UDIS_MODULE_OFFSET * UDIS_MODULE_MAX, &udis_cb->udis_info_buf_dma, KA_GFP_KERNEL | __KA_GFP_ACCOUNT);
        if (udis_cb->udis_info_buf == NULL) {
            udis_err("Failed to call hal_kernel_devdrv_dma_alloc_coherent. (udevid=%u)\n", udevid);
            return -ENOMEM;
        }
    }

    ret = memset_s(udis_cb->udis_info_buf, UDIS_MODULE_OFFSET * UDIS_MODULE_MAX, 0,
        UDIS_MODULE_OFFSET * UDIS_MODULE_MAX);
    if (ret != 0) {
        udis_err("Call memset_s failed. (udevid=%u; ret=%d)\n", udevid, ret);
        ret = -EIO;
        goto free_dma_buf;
    }

    for (i = UPDATE_ONLY_ONCE; i < UPDATE_TYPE_MAX; ++i) {
        KA_INIT_LIST_HEAD(&udis_cb->addr_list[i]);
    }

    ka_task_init_rwsem(&udis_cb->udis_info_lock);
    ka_task_init_rwsem(&udis_cb->addr_list_lock);
    return 0;

free_dma_buf:
    if ((devdrv_get_connect_protocol(udevid) == CONNECT_PROTOCOL_UB)) {
        dbl_kfree(udis_cb->udis_info_buf);
    } else {
        hal_kernel_devdrv_dma_free_coherent(uda_get_device(udevid), UDIS_MODULE_OFFSET * UDIS_MODULE_MAX,
            udis_cb->udis_info_buf, udis_cb->udis_info_buf_dma);
    }
    udis_cb->udis_info_buf = NULL;
    udis_cb->udis_info_buf_dma = 0;
    return ret;
}

STATIC void udis_cb_destructor(unsigned int udevid, struct udis_ctrl_block *udis_cb)
{
    int i;
    struct udis_node *addr_node, *tmp;

    for (i = UPDATE_ONLY_ONCE; i < UPDATE_TYPE_MAX; ++i) {
        ka_list_for_each_entry_safe(addr_node, tmp, &udis_cb->addr_list[i], list) {
            ka_list_del(&addr_node->list);
            dbl_kfree(addr_node);
            addr_node = NULL;
        }
    }

    if ((devdrv_get_connect_protocol(udevid) == CONNECT_PROTOCOL_UB)) {
        dbl_kfree(udis_cb->udis_info_buf);
    } else {
        hal_kernel_devdrv_dma_free_coherent(uda_get_device(udevid), UDIS_MODULE_OFFSET * UDIS_MODULE_MAX,
            udis_cb->udis_info_buf, udis_cb->udis_info_buf_dma);
    }
    udis_cb->udis_info_buf = NULL;
    udis_cb->udis_info_buf_dma = 0;
}

STATIC int udis_init_ucb(unsigned int udevid)
{
    int ret;
    struct udis_ctrl_block *ucb = NULL;

    ucb = (struct udis_ctrl_block*)dbl_kzalloc(sizeof(struct udis_ctrl_block), KA_GFP_KERNEL | __KA_GFP_ACCOUNT);
    if (ucb == NULL) {
        udis_err("Failed to alloc for udis ctrl block. (udevid=%u)\n", udevid);
        return -ENOMEM;
    }

    ret = udis_cb_constructor(udevid, ucb);
    if (ret != 0) {
        dbl_kfree(ucb);
        ucb = NULL;
        udis_err("Failed to init udis ctrl block. (udevid=%u; ret=%d)\n", udevid, ret);
        return ret;
    }

    udis_cb_write_lock(udevid);
    if (udis_get_ctrl_block(udevid) != NULL) {
        udis_cb_write_unlock(udevid);
        udis_cb_destructor(udevid, ucb);
        dbl_kfree(ucb);
        ucb = NULL;
        udis_info("Udis ctrl block has been init. (udevid=%u)\n", udevid);
        return 0;
    }
    ucb->state = UDIS_DEV_READY;
    (void)udis_set_ctrl_block(udevid, ucb);
    udis_cb_write_unlock(udevid);

    return 0;
}

STATIC void udis_uninit_ucb(unsigned int udevid)
{
    struct udis_ctrl_block *ucb = NULL;

    udis_cb_write_lock(udevid);
    ucb = udis_get_ctrl_block(udevid);
    if (ucb == NULL) {
        udis_cb_write_unlock(udevid);
        udis_info("Udis ctrl block has been uninit. (udevid=%u)\n", udevid);
        return;
    }
    ucb->state = UDIS_DEV_UNINIT;
    (void)udis_set_ctrl_block(udevid, NULL);
    udis_cb_write_unlock(udevid);

    udis_cb_destructor(udevid, ucb);
    dbl_kfree(ucb);
    ucb = NULL;

    return;
}

enum {
    UDIS_LINK_NODES_NOTIFY_FUNC = 0,
    UDIS_CTRL_BLOCK_NOTIFY_FUNC,
    UDIS_COMMON_CHANNEL_NOTIFY_FUNC,
    UDIS_PERIOD_LINK_TASK_NOTIFY_FUNC,
    UDIS_NOTIFY_FUNC_TALBE_SIZE
};

STATIC int (*udis_device_up_notify_func_table[UDIS_NOTIFY_FUNC_TALBE_SIZE])(unsigned int udevid) = {
    [UDIS_LINK_NODES_NOTIFY_FUNC] = udis_link_nodes_init,
    [UDIS_CTRL_BLOCK_NOTIFY_FUNC] = udis_init_ucb,
    [UDIS_COMMON_CHANNEL_NOTIFY_FUNC] = udis_common_chan_register,
    [UDIS_PERIOD_LINK_TASK_NOTIFY_FUNC] = period_link_task_init,
};

STATIC void (*udis_device_down_notify_func_table[UDIS_NOTIFY_FUNC_TALBE_SIZE])(unsigned int udevid)= {
    [UDIS_LINK_NODES_NOTIFY_FUNC] = udis_link_nodes_uninit,
    [UDIS_CTRL_BLOCK_NOTIFY_FUNC] = udis_uninit_ucb,
    [UDIS_COMMON_CHANNEL_NOTIFY_FUNC] = udis_common_chan_unregister,
    [UDIS_PERIOD_LINK_TASK_NOTIFY_FUNC] = period_link_task_uninit,
};

STATIC int udis_device_up_notify(unsigned int udevid)
{
    int ret;
    int i, j;

    for (i = UDIS_LINK_NODES_NOTIFY_FUNC; i < UDIS_NOTIFY_FUNC_TALBE_SIZE; ++i) {
        ret = udis_device_up_notify_func_table[i](udevid);
        if (ret != 0) {
            udis_err("Call udis device up notify func failed. (udevid=%u; ret=%d; func_index=%d)\n", udevid, ret, i);
            goto out;
        }
    }

    ret = udis_send_host_ready_msg_to_device(udevid);
    if (ret != 0) {
        udis_err("Udis send host ready msg to device failed. (udevid=%u; ret=%d)\n", udevid, ret);
        goto out;
    }

    return 0;

out:
    for (j = i - 1; j >= 0; --j) {
        udis_device_down_notify_func_table[j](udevid);
    }
    return ret;
}

STATIC void udis_dev_down_notify(unsigned int udevid, enum udis_dev_state state)
{
    int i, func_idx;
    struct udis_ctrl_block *ucb = NULL;

    if ((udevid >= UDIS_DEVICE_UDEVID_MAX) || (state >= UDIS_DEV_STATE_MAX)) {
        udis_err("Invalid udevid or state. (udevid=%u; state=%u)\n", udevid, state);
        return;
    }

    udis_cb_write_lock(udevid);
    ucb = udis_get_ctrl_block(udevid);
    if (ucb == NULL) {
        udis_cb_write_unlock(udevid);
        return;
    }

    /*
    VM hot reset: hot reset、uninit
    VM reboot/clear:uninit
    */
    if (!uda_is_pf_dev(udevid) && ucb->state == UDIS_DEV_READY) {
        (void)udis_send_host_vf_uninit_notify(udevid);
    }

    ucb->state = state;
    udis_cb_write_unlock(udevid);

    func_idx = (state == UDIS_DEV_UNINIT ? UDIS_LINK_NODES_NOTIFY_FUNC : UDIS_PERIOD_LINK_TASK_NOTIFY_FUNC);

    for (i = UDIS_PERIOD_LINK_TASK_NOTIFY_FUNC; i >= func_idx; --i) {
        udis_device_down_notify_func_table[i](udevid);
    }

    return;
}

STATIC void udis_hb_broken_notify(unsigned int udevid)
{
    return udis_dev_down_notify(udevid, UDIS_DEV_HEART_BEAT_LOSS);
}

STATIC int udis_notify_device_hotreset_cancel(unsigned int udevid)
{
    int ret = 0;
    struct udis_ctrl_block *ucb = NULL;

    udis_cb_write_lock(udevid);
    ucb = udis_get_ctrl_block(udevid);
    if (ucb == NULL) {
        udis_cb_write_unlock(udevid);
        udis_err("Get udis ctrl block failed. (udevid=%u)\n", udevid);
        return -ENODEV;
    }
    ucb->state = UDIS_DEV_READY;
    udis_cb_write_unlock(udevid);

    ret = period_link_task_init(udevid);
    if (ret != 0) {
        udis_err("Init period link task failed. (udevid=%u; ret=%d)\n", udevid, ret);
        return ret;
    }

    return 0;
}

STATIC int udis_host_notifier_func(unsigned int udevid, enum uda_notified_action action)
{
    int ret = 0;

    if (udevid >= UDIS_DEVICE_UDEVID_MAX) {
        udis_err("Invalid udevid. (udevid=%u)\n", udevid);
        return -EINVAL;
    }

    if (action == UDA_INIT) {
        ret = udis_device_up_notify(udevid);
    } else if (action == UDA_UNINIT) {
        udis_dev_down_notify(udevid, UDIS_DEV_UNINIT);
    } else if (action == UDA_HOTRESET) {
        udis_dev_down_notify(udevid, UDIS_DEV_HOTRESET);
    } else if (action == UDA_PRE_HOTRESET) {
        udis_dev_down_notify(udevid, UDIS_DEV_PREHOTRESET);
    } else if ((action == UDA_HOTRESET_CANCEL) || (action == UDA_PRE_HOTRESET_CANCEL)) {
        ret = udis_notify_device_hotreset_cancel(udevid);
    }

    udis_info("udis notifier action. (udevid=%u; action=%d; ret=%d)\n", udevid, action, ret);
    return ret;
}

STATIC int udis_heart_broken_moniter(unsigned int udevid, unsigned long privilege_data)
{
    unsigned int i;
    unsigned int status;
    int ret;
    struct ascend_intf_get_status_para hb_mon_para = {0};
    static unsigned int g_last_dev_status[UDIS_DEVICE_UDEVID_MAX] = {0};

    hb_mon_para.type = DAVINCI_STATUS_TYPE_DEVICE;

    for (i = 0; i < UDIS_DEVICE_UDEVID_MAX; ++i) {
        hb_mon_para.para.device_id = i;
        status = 0;
        ret = ascend_intf_get_status(hb_mon_para, &status);
        if (ret != 0) {
            udis_err("Failed to get device status. (udevid=%u; ret=%d)\n", i, ret);
            return ret;
        }

        if (((status & DAVINCI_INTF_DEVICE_STATUS_HEARTBIT_LOST) != 0) &&
            ((g_last_dev_status[i] & DAVINCI_INTF_DEVICE_STATUS_HEARTBIT_LOST) == 0)) {
            udis_hb_broken_notify(i);
        }
        g_last_dev_status[i] = status;
    }
    return 0;
}

STATIC int udis_hb_monitor_init(void)
{
    int ret;
    struct udis_timer_task task = {0};

    task.period_ms = UDIS_HB_MONITOR_PERIOD_MS;
    task.cur_ms = 0;
    task.work_type = COMMON_WORK;
    task.privilege_data = 0;
    task.period_task_func = udis_heart_broken_moniter;

    ret = strcpy_s(task.task_name, TASK_NAME_MAX_LEN, UDIS_HB_MONITOR);
    if (ret != 0) {
        udis_err("Failed to copy task name. (ret=%d; name=%s)\n", ret, UDIS_HB_MONITOR);
        return -ENOMEM;
    }

    ret = hal_kernel_register_period_task(0, &task);
    if (ret != 0) {
        udis_err("Failed to register udis heart beat monitor task. (ret=%d)\n", ret);
        return ret;
    }
    return 0;
}

STATIC void udis_hb_monitor_uninit(void)
{
    int ret;
    char task_name[TASK_NAME_MAX_LEN] = {0};

    ret = strcpy_s(task_name, TASK_NAME_MAX_LEN, UDIS_HB_MONITOR);
    if (ret != 0) {
        udis_err("strcpy_s failed. (ret=%d; name=%s)\n", ret, UDIS_HB_MONITOR);
        return;
    }

    ret = hal_kernel_unregister_period_task(0, task_name);
    if (ret != 0) {
        udis_err("Failed to unregister heart broken monitor. (ret=%d)\n", ret);
        return;
    }
}

int udis_init(void)
{
    int ret = 0;
    int i;
    struct uda_dev_type type = {0};

    for (i = 0; i < UDIS_DEVICE_UDEVID_MAX; ++i) {
        udis_cb_rwlock_init(i);
    }

    ret = udis_common_chan_init();
    if (ret != 0) {
        udis_err("Udis common chan init failed. (ret=%d)\n", ret);
        return ret;
    }

    ret = udis_timer_init();
    if (ret != 0) {
        udis_err("Init udis timer failed. (ret=%d)\n", ret);
        goto udis_timer_init_fail;
    }

    uda_davinci_near_real_entity_type_pack(&type);
    ret = uda_notifier_register(UDIS_HOST_NOTIFIER, &type, UDA_PRI0, udis_host_notifier_func);
    if (ret != 0) {
        udis_err("Rigister uda notifier failed. (ret=%d)\n", ret);
        goto uda_register_fail;
    }

    ret = udis_hb_monitor_init();
    if (ret != 0) {
        udis_err("udis bh monitor init failed. (ret=%d)\n", ret);
        goto hb_monitor_init_fail;
    }

    udis_cmd_init();
    udis_info("udis driver init success.\n");
    return 0;

hb_monitor_init_fail:
    uda_davinci_near_real_entity_type_pack(&type);
    (void)uda_notifier_unregister(UDIS_HOST_NOTIFIER, &type);
uda_register_fail:
    udis_timer_uninit();
udis_timer_init_fail:
    udis_common_chan_uninit();
    return ret;
}

void udis_exit(void)
{
    struct uda_dev_type type = {0};

    udis_cmd_exit();
    udis_hb_monitor_uninit();
    uda_davinci_near_real_entity_type_pack(&type);
    (void)uda_notifier_unregister(UDIS_HOST_NOTIFIER, &type);
    udis_timer_uninit();
    udis_common_chan_uninit();

    udis_info("udis driver exit success.\n");
    return;
}

KA_MODULE_LICENSE("GPL");
KA_MODULE_AUTHOR("Huawei Tech. Co., Ltd.");
KA_MODULE_DESCRIPTION("DAVINCI driver");