/* Copyright (c) 2017-2018 ARM Limited
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "psa_defs.h"
#include "spm_client.h"
#include "spm_messages.h"
#include "spm_internal.h"
#include "mbed_assert.h"
#include "ipc_queue.h"
#include "ipc_defs.h"
#include "spm_api.h"

static os_mutex_t queue_mutex_storage;
static os_semaphore_t full_sema;
static os_semaphore_t read_sema;

static const osMutexAttr_t queue_mutex_attr = {
    .name      = "Q_MUT",
    .attr_bits = osMutexRecursive | osMutexPrioInherit | osMutexRobust,
    .cb_mem    = &queue_mutex_storage,
    .cb_size   = sizeof(queue_mutex_storage)
};

// Full queue semaphore attributes for the consumer queue
static const osSemaphoreAttr_t full_sem_attr = {
    .name      = "Q_W_SEM",
    .attr_bits = 0,
    .cb_mem    = &full_sema,
    .cb_size   = sizeof(full_sema)
};

// Read semaphore attributes for the consumer queue
static const osSemaphoreAttr_t read_sem_attr = {
    .name      = "Q_R_SEM",
    .attr_bits = 0,
    .cb_mem    = &read_sema,
    .cb_size   = sizeof(read_sema)
};


static ipc_producer_queue_t _prod_queue;
ipc_producer_queue_t *prod_queue = &_prod_queue;
static ipc_consumer_queue_t _cons_queue;
ipc_consumer_queue_t *cons_queue = &_cons_queue;

MBED_STATIC_ASSERT(
    (sizeof(addr_table_t) + sizeof(ipc_base_queue_t) + sizeof(ipc_base_queue_t)) <= PSA_SHARED_RAM_SIZE,
    "shared memory size is too small!"
);

void spm_mailbox_irq_callback(void)
{
    osStatus_t os_status = osSemaphoreRelease(prod_queue->full_queue_sem);
    MBED_ASSERT((osOK == os_status) || (osErrorResource == os_status));

    os_status = osSemaphoreRelease(cons_queue->read_sem);
    MBED_ASSERT((osOK == os_status) || (osErrorResource == os_status));

    PSA_UNUSED(os_status);
}

void on_new_item(void)
{
    spm_hal_mailbox_notify();
}

void on_vacancy(void)
{
    spm_hal_mailbox_notify();
}

void on_popped_item(ipc_queue_item_t item)
{
    // item.a hold the message type (connect / call /close)
    switch (item.a) {
        case PSA_IPC_CONNECT: {
            psa_connect_async(item.c, (spm_pending_connect_msg_t *)(item.b));
            break;
        }

        case PSA_IPC_CALL: {
            psa_call_async((psa_handle_t)(item.c), (spm_pending_call_msg_t *)(item.b));
            break;
        }

        case PSA_IPC_DISCONNECT: {
            psa_close_async((psa_handle_t)(item.c), (spm_pending_close_msg_t *)(item.b));
            break;
        }

        case PSA_IPC_VERSION: {
            spm_pending_version_msg_t *msg = (spm_pending_version_msg_t *)(item.b);
            if (!is_buffer_accessible(msg, sizeof(*msg), NULL)) {
                SPM_PANIC("message data is inaccessible\n");
            }

            msg->rc = psa_version(item.c);
            nspe_done(msg->completion_sem_id);
            break;
        }

        default: {
            MBED_ASSERT(false);
        }
    }
}

void spm_ipc_mailbox_init(void)
{
    uint32_t *shared_memory_start = (uint32_t *)PSA_SHARED_RAM_START;

    // This struct is set with initial values for the address table (addresses of CM0+ / CM4 shared memory)
    const addr_table_t shared_addr_table = {
        .magic        = ADDR_TABLE_MAGIC,
        .tx_queue_ptr = (uintptr_t)(shared_memory_start +
                                    (sizeof(addr_table_t) / sizeof(uint32_t))
                                   ),
        .rx_queue_ptr = (uintptr_t)(shared_memory_start +
                                    ((sizeof(addr_table_t) + sizeof(ipc_base_queue_t)) / sizeof(uint32_t))
                                   )
    };

    // This struct is set with initial values to be used for IPC queues initialization.
    // Same values are used to initial both tx and rx queues.
    const ipc_base_queue_t queue_mem = {
        .magic        = IPC_QUEUE_BASE_MAGIC,
        .read_idx     = 0,
        .write_idx    = 0,
        .data         = {{0, 0}}
    };

    ipc_base_queue_t *tx_queue_mem_ptr = (ipc_base_queue_t *)(shared_addr_table.tx_queue_ptr);
    ipc_base_queue_t *rx_queue_mem_ptr = (ipc_base_queue_t *)(shared_addr_table.rx_queue_ptr);

    // Copy initial queue values for tx & tx queues to the right location in the CM0+ / CM4 shared memory
    memcpy(tx_queue_mem_ptr, &queue_mem, sizeof(ipc_base_queue_t));
    memcpy(rx_queue_mem_ptr, &queue_mem, sizeof(ipc_base_queue_t));

    // Copy the content of shared_addr_table to the start address of CM0+ / CM4 shared memory.
    memcpy(shared_memory_start, &shared_addr_table, sizeof(shared_addr_table));

    // Init producer queue and consumer queue

    osMutexId_t queue_mutex = osMutexNew(&queue_mutex_attr);
    MBED_ASSERT(queue_mutex != NULL);    // TODO: Panic instead

    osSemaphoreId_t full_queue_sem = osSemaphoreNew(IPC_QUEUE_SEM_MAX_COUNT, IPC_QUEUE_SEM_INITIAL_COUNT, &full_sem_attr);
    MBED_ASSERT(full_queue_sem != NULL);    // TODO: Panic instead

    osSemaphoreId_t queue_read_sem = osSemaphoreNew(IPC_QUEUE_SEM_MAX_COUNT, IPC_QUEUE_SEM_INITIAL_COUNT, &read_sem_attr);
    MBED_ASSERT(queue_read_sem != NULL);    // TODO: Panic instead

    ipc_producer_queue_init(prod_queue, rx_queue_mem_ptr, queue_mutex, full_queue_sem);
    ipc_consumer_queue_init(cons_queue, tx_queue_mem_ptr, queue_read_sem);
}

void nspe_done(osSemaphoreId_t completion_sem_id)
{
    ipc_queue_item_t item_to_enqueue = {
        .b = (uint32_t)(completion_sem_id)
    };

    ipc_queue_enqueue(prod_queue, item_to_enqueue);
}
