/*

 * 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 <linux/types.h>

#include "pbl_spod_info.h"

#include "devmm_common.h"

#include "devmm_adapt.h"

#include "svm_gfp.h"

#include "svm_mem_create.h"

#include "svm_cgroup_mng.h"

#include "svm_phy_addr_blk_mng.h"

#include "devmm_mem_alloc_interface.h"

#include "svm_kernel_msg.h"

#include "svm_mem_share.h"

#include "svm_ioctl.h"



void devmm_share_phy_addr_blk_put(struct devmm_phy_addr_blk *share_blk)

{

    devmm_phy_addr_blk_put(share_blk);

}



struct devmm_phy_addr_blk *devmm_share_phy_addr_blk_get(u32 devid, int share_id)

{

    struct devmm_phy_addr_blk_mng *share_mng = &devmm_svm->share_phy_addr_blk_mng[devid];



    return devmm_phy_addr_blk_get(share_mng, share_id);

}



static int _devmm_target_blk_query_pa_process(struct devmm_chan_target_blk_query_msg *msg,

    struct devmm_phy_addr_blk *share_blk, int side)

{

    u64 page_size = (share_blk->attr.pg_type == MEM_NORMAL_PAGE_TYPE) ? KA_MM_PAGE_SIZE : SVM_MASTER_HUGE_PAGE_SIZE;

    u32 stamp = (u32)ka_jiffies;

    u32 i;



    if (share_blk->attr.pg_type == MEM_GIANT_PAGE_TYPE) {

        page_size = SVM_MASTER_GIANT_PAGE_SIZE;

    }



    if ((msg->num == 0) || (msg->offset > share_blk->addr_info.total_num) ||

        (msg->num > (share_blk->addr_info.total_num - msg->offset))) {

        devmm_drv_err("Invalid num. (num=%u; offset=%u; total_num=%llu)\n",

            msg->num, msg->offset, share_blk->addr_info.total_num);

        return -ERANGE;

    }



    for (i = 0; i < msg->num; i++) {

        u32 offset;



        offset = msg->offset + i;

        if (offset >= share_blk->pg_info.saved_num) {

            break;

        }



        msg->blk[i].target_addr = ka_mm_page_to_phys(share_blk->pg_info.pages[offset]);

        if (side == MEM_HOST_SIDE) {

            msg->blk[i].dma_blk.dma_addr = (ka_dma_addr_t)msg->blk[i].target_addr;

            msg->blk[i].dma_blk.size = page_size;

        } else {

            msg->blk[i].dma_blk= msg->blk[i].dma_blk;

        }



        devmm_try_cond_resched(&stamp);

    }

    msg->dma_saved = share_blk->pg_info.saved_num;

    return 0;

}



int devmm_target_blk_query_pa_process(u32 devid, struct devmm_chan_target_blk_query_msg *msg, int side)

{

    struct devmm_phy_addr_blk *share_blk = NULL;

    int ret;



    if (side == MEM_HOST_SIDE) {

        share_blk = devmm_share_phy_addr_blk_get(0, msg->share_id); /* master use devid 0 */

    } else {

        share_blk = devmm_share_phy_addr_blk_get(devid, msg->share_id);

    }

    if (share_blk == NULL) {

        devmm_drv_err("Share id doesn't exist. (devid=%u; share_id=%d;side=%d)\n", devid, msg->share_id, side);

        return -EBADR;

    }



    ret = _devmm_target_blk_query_pa_process(msg, share_blk, side);

    devmm_share_phy_addr_blk_put(share_blk);

    return ret;

}



static void devmm_pg_dma_info_init(struct devmm_phy_addr_blk *to_blk, struct devmm_phy_addr_blk *from_blk,

    u32 to_create_pg_num)

{

    u32 dma_copy_num, offset;

    u64 size;



    size = to_create_pg_num * sizeof(ka_page_t*);

    offset = to_blk->pg_info.saved_num;

    (void)memcpy_s(&to_blk->pg_info.pages[offset], size, &from_blk->pg_info.pages[offset], size);

    to_blk->pg_info.saved_num += to_create_pg_num;



    dma_copy_num = ka_base_min(to_create_pg_num, (u32)(from_blk->dma_blk_info.saved_num - to_blk->dma_blk_info.saved_num));

    if (dma_copy_num == 0) {

        return;

    }

    size = dma_copy_num * sizeof(struct devmm_dma_blk);

    offset = to_blk->dma_blk_info.saved_num;

    (void)memcpy_s(&to_blk->dma_blk_info.dma_blks[offset], size, &from_blk->dma_blk_info.dma_blks[offset], size);

    to_blk->dma_blk_info.saved_num += dma_copy_num;

}



int devmm_share_phy_addr_blk_init(struct devmm_phy_addr_blk *to_blk,

    struct devmm_phy_addr_blk *from_blk, u32 to_create_pg_num, u32 blk_type)

{

    bool is_finish = false;



    ka_task_down_write(&to_blk->rw_sem);

    if (to_blk->init_state != SVM_PHY_ADDR_BLK_IS_INITING) {

        devmm_drv_err("Err state. (state=%u; blk_id=%d)\n", to_blk->init_state, to_blk->id);

        ka_task_up_write(&to_blk->rw_sem);

        return -EBUSY;

    }



    if (from_blk != NULL) {

        devmm_pg_dma_info_init(to_blk, from_blk, to_create_pg_num);

        to_blk->is_same_sys_share = true;

        is_finish = (to_blk->pg_info.saved_num == to_blk->pg_info.total_num);

    } else {

        to_blk->is_same_sys_share = false;

        is_finish = (to_blk->addr_info.saved_num == to_blk->addr_info.total_num);

    }

    to_blk->type = blk_type;

    to_blk->init_state = is_finish ? SVM_PHY_ADDR_BLK_IS_INITED : SVM_PHY_ADDR_BLK_IS_INITING;

    ka_task_up_write(&to_blk->rw_sem);

    return 0;

}



int devmm_share_phy_addr_blk_create(struct devmm_phy_addr_blk *blk, u32 devid, int *share_id)

{

    struct devmm_phy_addr_blk_mng *share_mng = &devmm_svm->share_phy_addr_blk_mng[devid];

    struct devmm_phy_addr_blk *share_blk = NULL;

    int ret, tmp_share_id;



    share_blk = devmm_phy_addr_blk_create(share_mng, &blk->attr, blk->pg_num, &tmp_share_id);

    if (share_blk == NULL) {

        return -ENOMEM;

    }



    ret = devmm_share_phy_addr_blk_init(share_blk, blk, blk->pg_num, SVM_PYH_ADDR_BLK_SHARE_TYPE);

    if (ret != 0) {

        devmm_phy_addr_blk_destroy(share_mng, share_blk);

        return ret;

    }

    *share_id = tmp_share_id;

    return 0;

}



void devmm_share_phy_addr_blks_destroy(u32 devid)

{

    struct devmm_phy_addr_blk_mng *share_mng = &devmm_svm->share_phy_addr_blk_mng[devid];



    _devmm_phy_addr_blks_destroy(NULL, share_mng);

}



int devmm_phy_addr_blk_init_in_same_os(struct devmm_phy_addr_blk *blk, u32 share_devid, int share_id,

    u32 to_create_pg_num)

{

    struct devmm_phy_addr_blk *share_blk = NULL;

    int ret;



    devmm_drv_debug("In same os. (share_devid=%u; share_id=%d; to_create_pg_num=%u)\n",

        share_devid, share_id, to_create_pg_num);



    share_blk = devmm_share_phy_addr_blk_get(share_devid, share_id);

    if (share_blk == NULL) {

        devmm_drv_err("Get share blk fail. (share_devid=%u; share_id=%d)\n",

            share_devid, share_id);

        return -EBADR;

    }

    if (share_blk->pg_num != blk->pg_num) {

        devmm_drv_err("Page num is invalid. (blk_pg_num=%llu; share_blk_pg_num=%llu)\n",

            blk->pg_num, share_blk->pg_num);

        devmm_share_phy_addr_blk_put(share_blk);

        return -ERANGE;

    }

    if (share_blk->init_state != SVM_PHY_ADDR_BLK_IS_INITED) {

        devmm_drv_err("Share blk state is invalid. (init_state=%u)\n", share_blk->init_state);

        devmm_share_phy_addr_blk_put(share_blk);

        return -EINVAL;

    }



    ret = devmm_share_phy_addr_blk_init(blk, share_blk, to_create_pg_num, SVM_PYH_ADDR_BLK_IMPORT_TYPE);

    devmm_share_phy_addr_blk_put(share_blk);

    return ret;

}