Newer
Older
mbed-os / features / FEATURE_EXPERIMENTAL_API / FEATURE_PSA / TARGET_TFM / TARGET_TFM_V1_1 / TARGET_TFM_DUALCPU / src / platform_ns_mailbox.c
@Charley Chu Charley Chu on 7 Aug 2020 9 KB Move current TFM to TFM_V1_1
/*
 * Copyright (c) 2019-2020, Arm Limited. All rights reserved.
 * Copyright (c) 2019, Cypress Semiconductor Corporation. All rights reserved
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

/* -------------------------------------- Includes ----------------------------------- */
#include <limits.h>
#include <string.h>

#include "cmsis_compiler.h"

#include "cy_ipc_drv.h"
#include "cy_sysint.h"
#include "cy_ipc_sema.h"

#include "ns_ipc_config.h"
#include "tfm_ns_mailbox.h"
#include "platform_multicore.h"
#include "cmsis_os2.h"

static uint8_t saved_irq_state = 1;

/* -------------------------------------- HAL API ------------------------------------ */

static void mailbox_ipc_init(void)
{
    Cy_IPC_Drv_SetInterruptMask(Cy_IPC_Drv_GetIntrBaseAddr(IPC_RX_INTR_STRUCT),
                                0, IPC_RX_INT_MASK);
}

static void mailbox_ipc_config(void)
{
    NVIC_SetPriority(PSA_CLIENT_REPLY_NVIC_IRQn, PSA_CLIENT_REPLY_IRQ_PRIORITY);

    NVIC_EnableIRQ(PSA_CLIENT_REPLY_NVIC_IRQn);
}

int32_t tfm_ns_mailbox_hal_notify_peer(void)
{
    cy_en_ipcdrv_status_t status;

    status = Cy_IPC_Drv_SendMsgWord(Cy_IPC_Drv_GetIpcBaseAddress(IPC_TX_CHAN),
                                    IPC_TX_NOTIFY_MASK,
                                    PSA_CLIENT_CALL_REQ_MAGIC);

    if (status == CY_IPC_DRV_SUCCESS) {
        return MAILBOX_SUCCESS;
    } else {
        return MAILBOX_CHAN_BUSY;
    }
}

static int32_t mailbox_sema_init(void)
{
    /* semaphore data */
    static uint32_t tfm_sema __attribute__((section("TFM_SHARED_DATA")));

    if (Cy_IPC_Sema_Init(PLATFORM_MAILBOX_IPC_CHAN_SEMA,
                         sizeof(tfm_sema) * CHAR_BIT,
                         &tfm_sema) != CY_IPC_SEMA_SUCCESS) {
        return PLATFORM_MAILBOX_INIT_ERROR;
    }
    return PLATFORM_MAILBOX_SUCCESS;
}

int32_t tfm_ns_mailbox_hal_init(struct ns_mailbox_queue_t *queue)
{
    uint32_t stage;

    if (!queue) {
        return MAILBOX_INVAL_PARAMS;
    }

    /* Init semaphores used for critical sections */
    if (mailbox_sema_init() != PLATFORM_MAILBOX_SUCCESS)
        return MAILBOX_INIT_ERROR;

    /*
     * FIXME
     * Further verification of mailbox queue address may be required according
     * to diverse NSPE implementations.
     */

    mailbox_ipc_init();

    /*
     * Wait until SPE mailbox library is ready to receive NSPE mailbox queue
     * address.
     */
    while (1) {
        platform_mailbox_wait_for_notify();

        platform_mailbox_fetch_msg_data(&stage);
        if  (stage == NS_MAILBOX_INIT_ENABLE) {
            break;
        }
    }

    /* Send out the address */
    platform_mailbox_send_msg_ptr(queue);

    /* Wait until SPE mailbox service is ready */
    while (1) {
        platform_mailbox_wait_for_notify();

        platform_mailbox_fetch_msg_data(&stage);
        if  (stage == S_MAILBOX_READY) {
            break;
        }
    }

    mailbox_ipc_config();

    return MAILBOX_SUCCESS;
}

const void *tfm_ns_mailbox_get_task_handle(void)
{
#ifdef TFM_MULTI_CORE_MULTI_CLIENT_CALL
    return osThreadGetId();
#else
    return NULL;
#endif
}

void tfm_ns_mailbox_hal_wait_reply(mailbox_msg_handle_t handle)
{
    osThreadFlagsWait(handle, osFlagsWaitAll, osWaitForever);
}

static cy_en_ipcsema_status_t mailbox_raw_spin_lock(uint32_t ipc_channel,
                                                    uint32_t sema_num)
{
    uint32_t semaIndex;
    uint32_t semaMask;
    cy_stc_ipc_sema_t *semaStruct;
    cy_en_ipcdrv_status_t acqStatus;
    cy_en_ipcsema_status_t ret = CY_IPC_SEMA_BAD_PARAM;
    bool is_lock = false;
    IPC_STRUCT_Type *semaIpcStruct;

    /* Get IPC register structure */
    semaIpcStruct = Cy_IPC_Drv_GetIpcBaseAddress(ipc_channel);
    /* Get pointer to structure */
    semaStruct = (cy_stc_ipc_sema_t *)Cy_IPC_Drv_ReadDataValue(semaIpcStruct);

    if (sema_num < semaStruct->maxSema) {
        semaIndex = sema_num / CY_IPC_SEMA_PER_WORD;
        semaMask = (uint32_t)(1ul << (sema_num - \
                                      (semaIndex * CY_IPC_SEMA_PER_WORD)));

        while (!is_lock) {
            /* Check to make sure the IPC channel is released
               If so, check if specific channel can be locked. */
            do {
                acqStatus = Cy_IPC_Drv_LockAcquire(semaIpcStruct);
            } while (acqStatus != CY_IPC_DRV_SUCCESS);

            if ((semaStruct->arrayPtr[semaIndex] & semaMask) == 0ul) {
                semaStruct->arrayPtr[semaIndex] |= semaMask;
                is_lock = true;
            }

            /* Release, but do not trigger a release event */
            (void)Cy_IPC_Drv_LockRelease(semaIpcStruct,
                                         CY_IPC_NO_NOTIFICATION);

            if (!is_lock) {
                /*
                 * The secure core is occupying this lock. Insert a small delay
                 * to give the secure core a chance to acquire the IPC channel
                 * and release the lock.
                 * Otherwise, the secure core may not be able to release the
                 * lock if non-secure core has higher CPU frequency. It will
                 * generate a deadlock.
                 * This delay won't harm performance too much since non-secure
                 * core has to busy wait here anyway.
                 * Alternatively, non-secure core can wait for release
                 * notification event from secure core. However, it is more
                 * complex and requires more code and more modifications.
                 */
                volatile uint32_t count = 1000;
                while(count > 0) {
                    count--;
                }
                Cy_IPC_Sema_Status(sema_num);
            }
        }

        ret = CY_IPC_SEMA_SUCCESS;
    }

    return ret;
}

static cy_en_ipcsema_status_t mailbox_raw_spin_unlock(uint32_t ipc_channel,
                                                      uint32_t sema_num)
{
    uint32_t semaIndex;
    uint32_t semaMask;
    cy_stc_ipc_sema_t *semaStruct;
    cy_en_ipcdrv_status_t acqStatus;
    cy_en_ipcsema_status_t ret = CY_IPC_SEMA_BAD_PARAM;
    bool is_unlock = false;
    IPC_STRUCT_Type *semaIpcStruct;

    /* Get IPC register structure */
    semaIpcStruct = Cy_IPC_Drv_GetIpcBaseAddress(ipc_channel);
    /* Get pointer to structure */
    semaStruct = (cy_stc_ipc_sema_t *)Cy_IPC_Drv_ReadDataValue(semaIpcStruct);

    if (sema_num < semaStruct->maxSema) {
        semaIndex = sema_num / CY_IPC_SEMA_PER_WORD;
        semaMask = (uint32_t)(1ul << (sema_num - \
                                      (semaIndex * CY_IPC_SEMA_PER_WORD)));

        while (!is_unlock) {
            /* Check to make sure the IPC channel is released
               If so, check if specific channel can be locked. */
            do {
                acqStatus = Cy_IPC_Drv_LockAcquire(semaIpcStruct);
            } while (acqStatus != CY_IPC_DRV_SUCCESS);

            if ((semaStruct->arrayPtr[semaIndex] & semaMask) != 0ul) {
                semaStruct->arrayPtr[semaIndex] &= ~semaMask;
                is_unlock = true;
            }

            /* Release, but do not trigger a release event */
            (void)Cy_IPC_Drv_LockRelease(semaIpcStruct,
                                         CY_IPC_NO_NOTIFICATION);
        }

        ret = CY_IPC_SEMA_SUCCESS;
    }

    return ret;
}

void tfm_ns_mailbox_hal_enter_critical(void)
{
    saved_irq_state = Cy_SysLib_EnterCriticalSection();

    mailbox_raw_spin_lock(CY_IPC_CHAN_SEMA, MAILBOX_SEMAPHORE_NUM);
}

void tfm_ns_mailbox_hal_exit_critical(void)
{
    mailbox_raw_spin_unlock(CY_IPC_CHAN_SEMA, MAILBOX_SEMAPHORE_NUM);

    Cy_SysLib_ExitCriticalSection(saved_irq_state);
}

void tfm_ns_mailbox_hal_enter_critical_isr(void)
{
    mailbox_raw_spin_lock(CY_IPC_CHAN_SEMA, MAILBOX_SEMAPHORE_NUM);
}

void tfm_ns_mailbox_hal_exit_critical_isr(void)
{
    mailbox_raw_spin_unlock(CY_IPC_CHAN_SEMA, MAILBOX_SEMAPHORE_NUM);
}

static bool mailbox_clear_intr(void)
{
    uint32_t status;

    status = Cy_IPC_Drv_GetInterruptStatusMasked(
                            Cy_IPC_Drv_GetIntrBaseAddr(IPC_RX_INTR_STRUCT));
    status >>= CY_IPC_NOTIFY_SHIFT;
    if ((status & IPC_RX_INT_MASK) == 0) {
        return false;
    }

    Cy_IPC_Drv_ClearInterrupt(Cy_IPC_Drv_GetIntrBaseAddr(IPC_RX_INTR_STRUCT),
                              0, IPC_RX_INT_MASK);
    return true;
}

void cpuss_interrupts_ipc_5_IRQHandler(void)
{
    uint32_t magic;
    mailbox_msg_handle_t handle;
    osThreadId_t task_handle;

    if (!mailbox_clear_intr())
        return;

    platform_mailbox_fetch_msg_data(&magic);
    if (magic == PSA_CLIENT_CALL_REPLY_MAGIC) {
        /* Handle all the pending replies */
        while (1) {
            handle = tfm_ns_mailbox_fetch_reply_msg_isr();
            if (handle == MAILBOX_MSG_NULL_HANDLE) {
                break;
            }

            task_handle = (osThreadId_t)tfm_ns_mailbox_get_msg_owner(handle);
            if (task_handle) {
                /* According to the description of CMSIS-RTOS v2 Thread Flags,
                 * osThreadFlagsSet() can be called inside Interrupt Service
                 * Routine. */
                osThreadFlagsSet(task_handle, handle);
            }
        }
    }
}