/*
 * Arm SCP/MCP Software
 * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "config_mhu3.h"
#include "scp_unity.h"
#include "unity.h"

#include <Mockfwk_core.h>
#include <Mockfwk_interrupt.h>
#include <Mockfwk_module.h>

#include <internal/mhu3.h>

#include <mod_mhu3.h>

#include UNIT_TEST_SRC

/* Some invalid values */
#define MHU3_TEST_INVALID_VALUE     0xFFFF
#define MHU3_TEST_INVALID_API_VALUE 0xF

/*!
 * For unit tests, there is no real MHUv3 hardware.
 * Below is the allocated memory that will be accessed by unit test
 * functions as a MHUv3 hardware, therefore unit tests must pre-load desired
 * values in the memory before calling unit test functions.
 */
struct mhu3_pbx_reg *fake_device_1_pbx_base;
struct mhu3_pbx_pdbcw_reg *fake_device_1_pdbcw;
struct mhu3_mbx_reg *fake_device_1_mbx_base;
struct mhu3_mbx_mdbcw_reg *fake_device_1_mdbcw;

void setUp(void)
{
}

void tearDown(void)
{
}

/*!
 * \brief mhu3 unit test: mhu3_raise_interrupt(), wrong channel type.
 *
 *  \details Handle case in mhu3_raise_interrupt where it detects unexpected
 *      channel type only valid value is doorbell channel type.
 */
void test_mhu3_raise_interrupt_wrong_channel_type(void)
{
    int status;
    enum mod_mhu3_channel_type prev;

    struct mhu3_device_ctx *device_ctx;
    struct mod_mhu3_channel_config *channel;

    device_ctx = &mhu3_ctx.device_ctx_table[FAKE_DEVICE_1_CHANNEL_DBCH_0];
    channel = &(device_ctx->config->channels[FAKE_DEVICE_1_CHANNEL_DBCH_0]);

    prev = channel->type;

    /* Force wrong channel type */
    channel->type = MHU3_TEST_INVALID_VALUE;

    fwk_id_t ch_id = FWK_ID_SUB_ELEMENT(
        FWK_MODULE_IDX_MHU3,
        MHU3_DEVICE_IDX_DEVICE_1,
        FAKE_DEVICE_1_CHANNEL_DBCH_0);

    status = mhu3_raise_interrupt(ch_id);
    TEST_ASSERT(status == FWK_E_PARAM);

    channel->type = prev;
}

/*!
 * \brief mhu3 unit test: mhu3_raise_interrupt(), valid case.
 *
 *  \details Handle case in mhu3_raise_interrupt raises interrupt
 *       without any failures.
 */
void test_mhu3_raise_interrupt_valid_case(void)
{
    int status;

    fwk_id_t ch_id = FWK_ID_SUB_ELEMENT(
        FWK_MODULE_IDX_MHU3,
        MHU3_DEVICE_IDX_DEVICE_1,
        FAKE_DEVICE_1_CHANNEL_DBCH_0_IDX);

    status = mhu3_raise_interrupt(ch_id);
    TEST_ASSERT(status == FWK_SUCCESS);
}

/*!
 * \brief mhu3 unit test: mhu3_bind(), round 0, successful case.
 *
 *  \details Handle case where fwk_module_bind is successful
 */
void test_mhu3_bind_round_0_success(void)
{
    int status;
    fwk_id_t device_id = { 0 };

    status = mhu3_bind(device_id, 0);
    TEST_ASSERT(status == FWK_SUCCESS);
}

/*!
 * \brief mhu3 unit test: mhu3_bind(), round 1, successful case.
 *
 *  \details Handle case where fwk_module_bind is successful
 */
void test_mhu3_bind_round_1_success(void)
{
    int status;
    struct mhu3_device_ctx *device_ctx;
    struct mhu3_channel_ctx *channel_ctx;

    device_ctx = &mhu3_ctx.device_ctx_table[MHU3_DEVICE_IDX_DEVICE_1];
    channel_ctx =
        &device_ctx->channel_ctx_table[FAKE_DEVICE_1_CHANNEL_DBCH_0_IDX];
    fwk_module_bind_ExpectAnyArgsAndReturn(FWK_SUCCESS);
    channel_ctx->transport_id_bound = true;
    fwk_id_t device_id =
        FWK_ID_ELEMENT(FWK_MODULE_IDX_MHU3, MHU3_DEVICE_IDX_DEVICE_1);

    status = mhu3_bind(device_id, 1);
    TEST_ASSERT(status == FWK_SUCCESS);

    channel_ctx->transport_id_bound = false;
}

/*!
 * \brief mhu3 unit test: mhu3_bind(), failure case.
 *
 *  \details Handle case where fwk_module_bind fails
 */
void test_mhu3_bind_fail(void)
{
    int status;
    struct mhu3_device_ctx *device_ctx;
    struct mhu3_channel_ctx *channel_ctx;

    device_ctx = &mhu3_ctx.device_ctx_table[MHU3_DEVICE_IDX_DEVICE_1];
    channel_ctx =
        &device_ctx->channel_ctx_table[FAKE_DEVICE_1_CHANNEL_DBCH_0_IDX];

    /* Force fwk_module_bind to fail */
    fwk_module_bind_ExpectAnyArgsAndReturn(FWK_E_PARAM);
    channel_ctx->transport_id_bound = true;
    fwk_id_t device_id =
        FWK_ID_ELEMENT(FWK_MODULE_IDX_MHU3, MHU3_DEVICE_IDX_DEVICE_1);

    status = mhu3_bind(device_id, 1);
    channel_ctx->transport_id_bound = false;
    TEST_ASSERT(status == FWK_E_PARAM);
}

/*!
 * \brief mhu3 unit test: mhu3_process_bind_request(), failure bind request
 *     for invalid api_id
 *
 *  \details Handle case where mhu3_process_bind_request fails because
 *      binding is requested for invalid api_id
 */
void test_mhu3_process_bind_request_fail_invalid_api_id(void)
{
    int status;
    fwk_id_t source_id;
    fwk_id_t target_id;
    fwk_id_t api_id;
    const void *api;

    source_id = FWK_ID_SUB_ELEMENT(FWK_MODULE_IDX_TRANSPORT, 0, 0);
    target_id = FWK_ID_SUB_ELEMENT(FWK_MODULE_IDX_MHU3, 0, 0);

    /* Send invalid api_id */
    api_id = FWK_ID_API(FWK_MODULE_IDX_MHU3, MOD_MHU3_API_IDX_COUNT);
    status = mhu3_process_bind_request(source_id, target_id, api_id, &api);
    TEST_ASSERT(status == FWK_E_PARAM);
}

/*!
 * \brief mhu3 unit test: mhu3_process_bind_request(), successful
 *
 *  \details Handle case where mhu3_process_bind_request succeeds
 *
 */
void test_mhu3_process_bind_request_success(void)
{
    int status;
    fwk_id_t source_id;
    fwk_id_t target_id;
    fwk_id_t api_id;
    const void *api;

    source_id = FWK_ID_SUB_ELEMENT(FWK_MODULE_IDX_TRANSPORT, 0, 0);
    target_id = FWK_ID_SUB_ELEMENT(FWK_MODULE_IDX_MHU3, 0, 0);

    api_id = FWK_ID_API(FWK_MODULE_IDX_MHU3, MOD_MHU3_API_IDX_TRANSPORT_DRIVER);
    status = mhu3_process_bind_request(source_id, target_id, api_id, &api);
    TEST_ASSERT(status == FWK_SUCCESS);
}
/*!
 * \brief mhu3 unit test: mhu3_process_bind_request(), failure bind request
 *     for non sub element
 *
 *  \details Handle case where mhu3_process_bind_request fails because
 *      binding is requested for by non-subelement type.
 */
void test_mhu3_process_bind_request_fail_non_sub_element(void)
{
    int status;
    fwk_id_t source_id;
    fwk_id_t target_id;
    fwk_id_t api_id;
    const void *api;

    source_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_TRANSPORT, 0);
    target_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_MHU3, 0);
    api_id = FWK_ID_API(FWK_MODULE_IDX_MHU3, MOD_MHU3_API_IDX_TRANSPORT_DRIVER);
    status = mhu3_process_bind_request(source_id, target_id, api_id, &api);

    TEST_ASSERT(status == FWK_E_ACCESS);
}

void test_mhu3_start_id_module_success(void)
{
    int status;
    fwk_id_t id;

    id = FWK_ID_MODULE(FWK_MODULE_IDX_MHU3);
    status = mhu3_start(id);
    TEST_ASSERT(status == FWK_SUCCESS);
}

void test_mhu3_start_id_element_success(void)
{
    int status;
    fwk_id_t id;

    fwk_interrupt_set_isr_ExpectAnyArgsAndReturn(FWK_SUCCESS);
    fwk_interrupt_enable_ExpectAnyArgsAndReturn(FWK_SUCCESS);
    id = FWK_ID_ELEMENT(FWK_MODULE_IDX_MHU3, MHU3_DEVICE_IDX_DEVICE_1);
    status = mhu3_start(id);
    TEST_ASSERT(status == FWK_SUCCESS);
}

void test_mhu3_start_id_element_set_isr_fails(void)
{
    int status;
    fwk_id_t id;

    fwk_interrupt_set_isr_ExpectAnyArgsAndReturn(FWK_E_PARAM);
    id = FWK_ID_ELEMENT(FWK_MODULE_IDX_MHU3, MHU3_DEVICE_IDX_DEVICE_1);
    status = mhu3_start(id);
    TEST_ASSERT(status == FWK_E_PARAM);
}

void test_mhu3_start_id_interrupt_enable_fails(void)
{
    int status;
    fwk_id_t id;

    fwk_interrupt_set_isr_ExpectAnyArgsAndReturn(FWK_SUCCESS);
    fwk_interrupt_enable_ExpectAnyArgsAndReturn(FWK_E_STATE);
    id = FWK_ID_ELEMENT(FWK_MODULE_IDX_MHU3, MHU3_DEVICE_IDX_DEVICE_1);
    status = mhu3_start(id);
    TEST_ASSERT(status == FWK_E_STATE);
}

void test_mhu3_get_fch_invalid_sub_element(void)
{
    int status;
    fwk_id_t id = { 0 };
    struct fast_channel_addr fch;

    /*
     * mhu3_get_fch expects a valid id which has a subelement.
     * check if it returns an FWK_E_PARAM if it gets an id without
     * field subelement
     */
    fwk_module_is_valid_sub_element_id_ExpectAnyArgsAndReturn(false);
    status = mhu3_get_fch(id, &fch);
    TEST_ASSERT(status == FWK_E_PARAM);
}

void test_mhu3_get_fch_invalid_channel_type(void)
{
    int status;
    fwk_id_t id = { 0 };
    struct fast_channel_addr fch;

    /*
     * mhu3_get_fch should return FWK_E_PARAM if first parameter fch_id
     * is of wrong type that is other than the fast channel type
     */
    fwk_module_is_valid_sub_element_id_ExpectAnyArgsAndReturn(true);
    id = FWK_ID_SUB_ELEMENT(
        FWK_MODULE_IDX_MHU3,
        MHU3_DEVICE_IDX_DEVICE_1,
        FAKE_DEVICE_1_CHANNEL_DBCH_0_IDX); /*
                                            * Note. no
                                            * FAKE_DEVICE_1_CHANNEL_DBCH_0_IDX
                                            * is not a fast channel
                                            */

    status = mhu3_get_fch(id, &fch);
    TEST_ASSERT(status == FWK_E_PARAM);
}

void test_mhu3_get_fch_null_fch_addr(void)
{
    int status;
    fwk_id_t id = { 0 };
    struct fast_channel_addr *null_fch_addr = NULL;

    /*
     * mhu3_get_fch should return FWK_E_PARAM if second parameter fch_addr
     * is NULL.
     */
    fwk_module_is_valid_sub_element_id_ExpectAnyArgsAndReturn(true);
    id = FWK_ID_SUB_ELEMENT(
        FWK_MODULE_IDX_MHU3,
        MHU3_DEVICE_IDX_DEVICE_1,
        FAKE_DEVICE_1_CHANNEL_FCH_0_IN_IDX);

    status = mhu3_get_fch(id, null_fch_addr);
    TEST_ASSERT(status == FWK_E_PARAM);
}

void test_mhu3_get_fch_valid_fch_dir_in(void)
{
    int status;
    fwk_id_t id = { 0 };
    struct fast_channel_addr fch;

    /* Success case, send valid id, address and fast channel direction (in) */
    fwk_module_is_valid_sub_element_id_ExpectAnyArgsAndReturn(true);
    id = FWK_ID_SUB_ELEMENT(
        FWK_MODULE_IDX_MHU3,
        MHU3_DEVICE_IDX_DEVICE_1,
        FAKE_DEVICE_1_CHANNEL_FCH_0_IN_IDX);

    status = mhu3_get_fch(id, &fch);
    TEST_ASSERT(status == FWK_SUCCESS);
}

void test_mhu3_get_fch_valid_fch_dir_out(void)
{
    int status;
    fwk_id_t id;
    struct fast_channel_addr fch;

    /* Success case, send valid id, address and fast channel direction (out) */
    fwk_module_is_valid_sub_element_id_ExpectAnyArgsAndReturn(true);
    id = FWK_ID_SUB_ELEMENT(
        FWK_MODULE_IDX_MHU3,
        MHU3_DEVICE_IDX_DEVICE_1,
        FAKE_DEVICE_1_CHANNEL_FCH_0_OUT_IDX);

    status = mhu3_get_fch(id, &fch);
    TEST_ASSERT(status == FWK_SUCCESS);
}

void fch_callback_test(uintptr_t param)
{
}
void test_mhu3_fch_register_callback_valid(void)
{
    int status;
    fwk_id_t id = FWK_ID_SUB_ELEMENT_INIT(
        FWK_MODULE_IDX_MHU3,
        MHU3_DEVICE_IDX_DEVICE_1,
        FAKE_DEVICE_1_CHANNEL_FCH_0_OUT_IDX);

    /* All valid params, expect success */
    fwk_module_is_valid_sub_element_id_ExpectAnyArgsAndReturn(true);
    status =
        mhu3_fch_register_callback(id, (uintptr_t)&status, fch_callback_test);
    TEST_ASSERT(status == FWK_SUCCESS);
}

void test_mhu3_fch_register_callback_invalid_sub_element_id(void)
{
    int status;
    fwk_id_t id = { 0 };

    /* Force fwk_module_is_valid_sub_element_id to return false */
    fwk_module_is_valid_sub_element_id_ExpectAnyArgsAndReturn(false);
    status =
        mhu3_fch_register_callback(id, (uintptr_t)&status, fch_callback_test);
    TEST_ASSERT(status == FWK_E_PARAM);
}

void test_mhu3_fch_register_callback_null_param(void)
{
    int status;
    fwk_id_t id = FWK_ID_SUB_ELEMENT_INIT(
        FWK_MODULE_IDX_MHU3,
        MHU3_DEVICE_IDX_DEVICE_1,
        FAKE_DEVICE_1_CHANNEL_FCH_0_OUT_IDX);

    /* Check if it returns FWK_E_PARAM if 'param' is NULL */
    status = mhu3_fch_register_callback(id, (uintptr_t)NULL, fch_callback_test);
    TEST_ASSERT(status == FWK_E_PARAM);
}

void test_mhu3_fch_register_callback_null_callback_addr(void)
{
    int status;
    fwk_id_t id = FWK_ID_SUB_ELEMENT_INIT(
        FWK_MODULE_IDX_MHU3,
        MHU3_DEVICE_IDX_DEVICE_1,
        FAKE_DEVICE_1_CHANNEL_FCH_0_OUT_IDX);

    /* Check if it returns FWK_E_PARAM if callback address is NULL */
    fwk_module_is_valid_sub_element_id_ExpectAnyArgsAndReturn(true);
    status = mhu3_fch_register_callback(id, (uintptr_t)&status, NULL);
    TEST_ASSERT(status == FWK_E_PARAM);
}

/* Helper function to setup values for unit tests */
static int mhu3_fake_init(void)
{
    int status;
    fwk_id_t module_id;
    fwk_id_t device_id;
    unsigned int sub_element_count;

    uint8_t *tmp_pbx_base;
    uint8_t *tmp_mbx_base;
    uint32_t *feat_spt0;

    struct mod_mhu3_device_config *device_config;

    device_config =
        (struct mod_mhu3_device_config *)element_table[MHU3_DEVICE_IDX_DEVICE_1]
            .data;

    /* Allocate some memory for MHU fake device1 PBX/MBX */
    tmp_pbx_base = aligned_alloc(
        sizeof(unsigned long),
        sizeof(struct mhu3_pbx_reg) + MHU3_PBX_PDBCW_PAGE_OFFSET +
            sizeof(struct mhu3_pbx_pdbcw_reg));
    tmp_mbx_base = aligned_alloc(
        sizeof(unsigned long),
        sizeof(struct mhu3_mbx_reg) + MHU3_MBX_MDBCW_PAGE_OFFSET +
            sizeof(struct mhu3_mbx_mdbcw_reg));

    fake_device_1_pbx_base = (struct mhu3_pbx_reg *)tmp_pbx_base;
    fake_device_1_mbx_base = (struct mhu3_mbx_reg *)tmp_mbx_base;

    fake_device_1_pdbcw = (struct mhu3_pbx_pdbcw_reg
                               *)(tmp_pbx_base + MHU3_PBX_PDBCW_PAGE_OFFSET);
    fake_device_1_mdbcw = (struct mhu3_mbx_mdbcw_reg
                               *)(tmp_mbx_base + MHU3_MBX_MDBCW_PAGE_OFFSET);

    feat_spt0 = (uint32_t *)(&fake_device_1_mbx_base->MBX_FEAT_SPT0);
    *feat_spt0 |= (1U << MHU3_FEAT_SPT0_FCE_SPT_BITSTART);
    feat_spt0 = (uint32_t *)(&fake_device_1_pbx_base->PBX_FEAT_SPT0);
    *feat_spt0 |= (1U << MHU3_FEAT_SPT0_FCE_SPT_BITSTART);

    device_config->in = (uintptr_t)fake_device_1_mbx_base;
    device_config->out = (uintptr_t)fake_device_1_pbx_base;

    module_id = FWK_ID_MODULE(FWK_MODULE_IDX_MHU3);
    status = mhu3_init(module_id, MHU3_DEVICE_IDX_COUNT, NULL);
    if (status != FWK_SUCCESS) {
        printf("[MHU3 UT] Can not execute test cases mhu3_init failed\n");
        return -1;
    }

    device_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_MHU3, MHU3_DEVICE_IDX_DEVICE_1);
    sub_element_count = FAKE_DEVICE_1_NUM_CH;
    status = mhu3_device_init(device_id, sub_element_count, device_config);
    if (status != FWK_SUCCESS) {
        printf(
            "[MHU3 UT] Can not execute test cases mhu3_device_init failed\n");
        return -1;
    }

    return 0;
}

int mhu3_test_main(void)
{
    int status;

    status = mhu3_fake_init();
    if (status != 0) {
        return status;
    }

    UNITY_BEGIN();
    RUN_TEST(test_mhu3_raise_interrupt_wrong_channel_type);
    RUN_TEST(test_mhu3_raise_interrupt_valid_case);
    RUN_TEST(test_mhu3_bind_round_0_success);
    RUN_TEST(test_mhu3_bind_round_1_success);
    RUN_TEST(test_mhu3_bind_fail);
    RUN_TEST(test_mhu3_process_bind_request_success);
    RUN_TEST(test_mhu3_process_bind_request_fail_invalid_api_id);
    RUN_TEST(test_mhu3_process_bind_request_fail_non_sub_element);
    RUN_TEST(test_mhu3_start_id_module_success);
    RUN_TEST(test_mhu3_start_id_element_success);
    RUN_TEST(test_mhu3_start_id_element_set_isr_fails);
    RUN_TEST(test_mhu3_start_id_interrupt_enable_fails);
    RUN_TEST(test_mhu3_get_fch_invalid_sub_element);
    RUN_TEST(test_mhu3_get_fch_invalid_channel_type);
    RUN_TEST(test_mhu3_get_fch_null_fch_addr);
    RUN_TEST(test_mhu3_get_fch_valid_fch_dir_in);
    RUN_TEST(test_mhu3_get_fch_valid_fch_dir_out);
    RUN_TEST(test_mhu3_fch_register_callback_valid);
    RUN_TEST(test_mhu3_fch_register_callback_invalid_sub_element_id);
    RUN_TEST(test_mhu3_fch_register_callback_null_param);
    RUN_TEST(test_mhu3_fch_register_callback_null_callback_addr);

    return UNITY_END();
}

int main(void)
{
    return mhu3_test_main();
}