/*
 * 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_fs_pub.h"
#include "ka_task_pub.h"
#include "ka_list_pub.h"
#include "ka_common_pub.h"
#include "ka_system_pub.h"
#include "ka_sched_pub.h"

#include "svm_kern_log.h"
#include "casm_ctx.h"
#include "casm_key.h"
#include "casm_src.h"

static struct svm_casm_src_ops *casm_src_ops = NULL;

void svm_casm_register_src_ops(const struct svm_casm_src_ops *ops)
{
    casm_src_ops = (struct svm_casm_src_ops *)ops;
}

static int casm_src_add_handle(u32 udevid, struct svm_global_va *src_va, struct casm_src_ex *src_ex)
{
    if (casm_src_ops != NULL) {
        return casm_src_ops->add_src(udevid, src_va, src_ex);
    } else {
        return 0;
    }
}

static void casm_src_del_handle(u32 udevid, int tgid, struct svm_global_va *src_va, struct casm_src_ex *src_ex)
{
    if (casm_src_ops != NULL) {
        return casm_src_ops->del_src(udevid, tgid, src_va, src_ex);
    }
}

int casm_add_src_node(u32 udevid, int tgid, struct casm_src_node *node)
{
    struct casm_ctx *ctx = NULL;
    struct casm_src_ctx *src_ctx = NULL;
    int ret;

    node->src_ex.owner_tgid = tgid;
    ret = casm_src_add_handle(udevid, &node->src_va, &node->src_ex);
    if (ret != 0) {
        return ret;
    }

    ctx = casm_ctx_get(udevid, tgid);
    if (ctx == NULL) {
        casm_src_del_handle(udevid, tgid, &node->src_va, &node->src_ex);
        svm_err("Get casm ctx failed. (udevid=%u; tgid=%d)\n", udevid, tgid);
        return -EINVAL;
    }

    src_ctx = &ctx->src_ctx;
    ka_task_write_lock_bh(&src_ctx->lock);
    ka_list_add_tail(&node->node, &src_ctx->head);
    ka_task_write_unlock_bh(&src_ctx->lock);
    casm_ctx_put(ctx);

    return 0;
}

void casm_del_src_node(u32 udevid, int tgid, struct casm_src_node *node)
{
    struct casm_ctx *ctx = NULL;
    struct casm_src_ctx *src_ctx = NULL;

    ctx = casm_ctx_get(udevid, tgid);
    if (ctx == NULL) {
        svm_err("Get casm ctx failed. (udevid=%u; tgid=%d)\n", udevid, tgid);
        return;
    }

    src_ctx = &ctx->src_ctx;
    ka_task_write_lock_bh(&src_ctx->lock);
    ka_list_del(&node->node);
    ka_task_write_unlock_bh(&src_ctx->lock);
    casm_ctx_put(ctx);

    casm_src_del_handle(udevid, tgid, &node->src_va, &node->src_ex);
}

void casm_src_ctx_show(struct casm_src_ctx *src_ctx, ka_seq_file_t *seq)
{
    struct casm_src_node *node = NULL;
    int i = 0;

    ka_task_read_lock_bh(&src_ctx->lock);

    ka_list_for_each_entry(node, &src_ctx->head, node) {
        struct svm_global_va *src_va = &node->src_va;
        if (i == 0) {
            ka_fs_seq_printf(seq, "casm src info:index    key    owner_tgid  udevid   tgid   va   size    updated_va\n");
        }

        ka_fs_seq_printf(seq, "    %d   0x%llx    %d   %u  %d  0x%llx   0x%llx   0x%llx\n",
            i++, node->key, node->src_ex.owner_tgid, src_va->udevid, src_va->tgid, src_va->va, src_va->size,
            node->src_ex.updated_va);
    }

    ka_task_read_unlock_bh(&src_ctx->lock);
}

void casm_src_ctx_init(u32 udevid, struct casm_src_ctx *src_ctx)
{
    ka_task_rwlock_init(&src_ctx->lock);
    KA_INIT_LIST_HEAD(&src_ctx->head);
}

static int casm_get_first_src_node_key(struct casm_src_ctx *src_ctx, u64 *key)
{
    int ret = -EFAULT;

    ka_task_read_lock_bh(&src_ctx->lock);
    if (!ka_list_empty_careful(&src_ctx->head)) {
        struct casm_src_node *node = ka_list_first_entry(&src_ctx->head, struct casm_src_node, node);
        *key = node->key;
        ret = 0;
    }
    ka_task_read_unlock_bh(&src_ctx->lock);

    return ret;
}

void casm_src_ctx_uninit(u32 udevid, struct casm_src_ctx *src_ctx)
{
    unsigned long stamp = (unsigned long)ka_jiffies;
    u64 last_key = 0;
    int num = 0;

    while (1) {
        u64 key;

        if (casm_get_first_src_node_key(src_ctx, &key) != 0) {
            break;
        }

        if (last_key == key) {
            svm_warn("Recycle failed. (udevid=%u; key=0x%llx)\n", udevid, key);
            break;
        }

        (void)casm_destroy_key(key);
        last_key = key;
        num++;
        ka_try_cond_resched(&stamp);
    }

    if (num > 0) {
        svm_info("Recycle src key. (udevid=%u; num=%d)\n", udevid, num);
    }
}