/* _NVRM_COPYRIGHT_BEGIN_
 *
 * Copyright 2020-2021 by NVIDIA Corporation.  All rights reserved.  All
 * information contained herein is proprietary and confidential to NVIDIA
 * Corporation.  Any use, reproduction, or disclosure without the written
 * permission of NVIDIA Corporation is prohibited.
 *
 * _NVRM_COPYRIGHT_END_
 */



#include <stddef.h>
#include <misc/bitops.h>            // for BIT64, bitmap ops
#include <misc/libc.h>              // for memset
#include <misc/nvmisc_drf.h>        // before riscv-mpu.h
#include <cpu/riscv-mpu.h>
#include <cpu/csr.h>

//
// MISRA 1.2 RFD: Inline assembly
// MISRA 4.9 RFD: function-like macros
// MISRA 15.1 RFD: Forward gotos
// MISRA 8.7 RFD: Functions with external linkage only referenced in one translation unit
//
START_RFD_BLOCK(MISRA, DEVIATE, Rule_1_2, "Approval: Bug 200531996, DR: SWE-FSP-009-SWSADR.docx",
                MISRA, DEVIATE, Directive_4_9, "Approval: Bug 200531995, DR: SWE-FSP-012-SWSADR.docx",
                MISRA, DEVIATE, Rule_15_1, "Approval: Bug 3176459, DR: SWE-FSP-028-SWSADR.docx",
                MISRA, DEVIATE, Rule_8_7, "Approval: Bug 200531999, DR: SWE-FSP-015-SWSADR.docx")

//
// each value of SMPUIDX allows us to access this many MPU entries via
// NV_RISCV_CSR_SMPUACC and NV_RISCV_CSR_SMPUDTY
//
#define MPUIDX2_ENTRIES_PER_INDEX DRF_SIZE(NV_RISCV_CSR_SMPUACC_ACC)

/**
 * @brief Select a HW MPU Entry. subsequent accesses to SMPUVA, SMPUPA,
 * SMPURNG, and SMPUATTR will access the selected entry.
 *
 * @param[in]  index index of the HW MPU entry to select.
 *
 * @return None
 */
static void mpu_idx_select(uint64_t index)
{
    csr_write(NV_RISCV_CSR_SMPUIDX,
              DRF_NUM64(_RISCV, _CSR_SMPUIDX, _INDEX, index));
}

/**
 * @brief Select a HW MPU Entry block. subsequent accesses to SMPUACC
 * and SMPUDTY will access the selected entry.
 *
 * @param[in]  index start index of the HW MPU entry block to select.
 *
 * @return None
 */
static void mpu_idx2_select(uint8_t index)
{
    uint8_t reg = index / MPUIDX2_ENTRIES_PER_INDEX;

    csr_write(NV_RISCV_CSR_SMPUIDX2, DRF_NUM64(_RISCV, _CSR_SMPUIDX2, _IDX, reg));
}

error_t
mpuInit(MpuContext *pCtx)
{
    error_t status = E_SUCCESS;
    uint64_t smpuctl;
    uint8_t idx;

    if (pCtx == NULL)
    {
        status = E_INVALID_PARAM;
        goto out;
    }

    (void) memset(pCtx, 0, sizeof(MpuContext));

    //
    // Read the entry count. The number of entries could be
    // limited by M-mode code if some entries are used by another partition
    //
    smpuctl = csr_read(NV_RISCV_CSR_SMPUCTL);
    pCtx->mpuEntryCount = (uint8_t) DRF_VAL64(_RISCV_CSR, _SMPUCTL, _ENTRY_COUNT, smpuctl);

    // Build entries for existing MPU settings
    for (idx = 0; idx < pCtx->mpuEntryCount; idx++)
    {
        // Check if the MPU entry has already been set
        mpu_idx_select(idx);
        uint64_t virtAddr = csr_read(NV_RISCV_CSR_SMPUVA);
        if (FLD_TEST_DRF_NUM64(_RISCV_CSR, _SMPUVA, _VLD, 1U, virtAddr))
        {
            bitmap_set_bit(pCtx->mpuReservedBitmap, NV_RISCV_CSR_MPU_ENTRY_COUNT / BITMAP_ELEMENT_SIZE, idx);
        }
    }

out:
    return status;
}

error_t
mpuEnable(void)
{
    csr_write(NV_RISCV_CSR_SATP,
        DRF_DEF64(_RISCV_CSR, _SATP, _MODE, _NVMPU) |
        DRF_DEF64(_RISCV_CSR, _SATP, _ASID, _BARE)  |
        DRF_DEF64(_RISCV_CSR, _SATP, _PPN, _BARE));
    return E_SUCCESS;
}

error_t
mpuReserveEntry(MpuContext *pCtx, uint32_t searchOrigin, MpuHandle *pReservedHandle)
{
    error_t status = E_FAULT;
    uint32_t idx;

    if ((pCtx == NULL) || (pReservedHandle == NULL))
    {
        status = E_INVALID_PARAM;
        goto out;
    }

    for (idx = searchOrigin; idx < pCtx->mpuEntryCount; idx++)
    {
        if (!bitmap_test_bit(pCtx->mpuReservedBitmap, NV_RISCV_CSR_MPU_ENTRY_COUNT / BITMAP_ELEMENT_SIZE, idx))
        {
            bitmap_set_bit(pCtx->mpuReservedBitmap, NV_RISCV_CSR_MPU_ENTRY_COUNT / BITMAP_ELEMENT_SIZE, idx);
            *pReservedHandle = idx;
            status = E_SUCCESS;
            goto out;
        }
    }

out:
    return status;
}

error_t
mpuFreeEntry(MpuContext *pCtx, MpuHandle handle)
{
    error_t status = E_SUCCESS;

    if ((pCtx == NULL) ||
        (handle >= pCtx->mpuEntryCount) ||
        (!bitmap_test_bit(pCtx->mpuReservedBitmap, NV_RISCV_CSR_MPU_ENTRY_COUNT / BITMAP_ELEMENT_SIZE, handle)))
    {
        status = E_INVALID_PARAM;
        goto out;
    }

    bitmap_clear_bit(pCtx->mpuReservedBitmap, NV_RISCV_CSR_MPU_ENTRY_COUNT / BITMAP_ELEMENT_SIZE, handle);

out:
    return status;
}

error_t
mpuWriteEntry
(
    const MpuContext *pCtx,
    MpuHandle handle,
    uint64_t va,
    uint64_t pa,
    uint64_t rng,
    uint64_t attr
)
{
    error_t status = E_SUCCESS;

    if ((pCtx == NULL) ||
        (handle >= pCtx->mpuEntryCount) ||
        (!bitmap_test_bit(pCtx->mpuReservedBitmap, NV_RISCV_CSR_MPU_ENTRY_COUNT / BITMAP_ELEMENT_SIZE, handle)))
    {
        status = E_INVALID_PARAM;
        goto out;
    }

    mpu_idx_select(handle);
    csr_clear(NV_RISCV_CSR_SMPUVA, DRF_NUM64(_RISCV_CSR, _SMPUVA, _VLD, 1));
    csr_write(NV_RISCV_CSR_SMPUPA, pa);
    csr_write(NV_RISCV_CSR_SMPURNG, rng);
    csr_write(NV_RISCV_CSR_SMPUATTR, attr);
    csr_write(NV_RISCV_CSR_SMPUVA, va);

out:
    return status;
}

error_t
mpuReadEntry
(
    const MpuContext *pCtx,
    MpuHandle handle,
    uint64_t *pVa,
    uint64_t *pPa,
    uint64_t *pRng,
    uint64_t *pAttr
)
{
    error_t status = E_SUCCESS;

    if ((pCtx == NULL) ||
        (handle >= pCtx->mpuEntryCount) ||
        (!bitmap_test_bit(pCtx->mpuReservedBitmap, NV_RISCV_CSR_MPU_ENTRY_COUNT / BITMAP_ELEMENT_SIZE, handle)))
    {
        status = E_INVALID_PARAM;
        goto out;
    }

    mpu_idx_select(handle);
    if (pVa != NULL)
    {
        *pVa = csr_read(NV_RISCV_CSR_SMPUVA);
    }

    if (pPa != NULL)
    {
        *pPa = csr_read(NV_RISCV_CSR_SMPUPA);
    }

    if (pRng != NULL)
    {
        *pRng = csr_read(NV_RISCV_CSR_SMPURNG);
    }

    if (pAttr != NULL)
    {
        *pAttr = csr_read(NV_RISCV_CSR_SMPUATTR);
    }

out:
    return status;
}

error_t
mpuEnableEntry(const MpuContext *pCtx, MpuHandle handle)
{
    error_t status = E_SUCCESS;

    if ((pCtx == NULL) ||
        (handle >= pCtx->mpuEntryCount) ||
        (!bitmap_test_bit(pCtx->mpuReservedBitmap, NV_RISCV_CSR_MPU_ENTRY_COUNT / BITMAP_ELEMENT_SIZE, handle)))
    {
        status = E_INVALID_PARAM;
        goto out;
    }

    mpu_idx_select(handle);
    csr_set(NV_RISCV_CSR_SMPUVA, DRF_NUM64(_RISCV_CSR, _SMPUVA, _VLD, 1));

out:
    return status;
}

error_t
mpuDisableEntry(const MpuContext *pCtx, MpuHandle handle)
{
    error_t status = E_SUCCESS;

    if ((pCtx == NULL) ||
        (handle >= pCtx->mpuEntryCount) ||
        (!bitmap_test_bit(pCtx->mpuReservedBitmap, NV_RISCV_CSR_MPU_ENTRY_COUNT / BITMAP_ELEMENT_SIZE, handle)))
    {
        status = E_INVALID_PARAM;
        goto out;
    }

    mpu_idx_select(handle);
    csr_clear(NV_RISCV_CSR_SMPUVA, DRF_NUM64(_RISCV_CSR, _SMPUVA, _VLD, 1));

out:
    return status;
}

error_t
mpuIsAccessed(const MpuContext *pCtx, MpuHandle handle, bool *bAccessed)
{
    error_t status = E_SUCCESS;
    uint64_t smpuacc;
    uint8_t idxToTest;

    if ((pCtx == NULL) ||
        (handle >= pCtx->mpuEntryCount) ||
        (!bitmap_test_bit(pCtx->mpuReservedBitmap, NV_RISCV_CSR_MPU_ENTRY_COUNT / BITMAP_ELEMENT_SIZE, handle)) ||
        (bAccessed == NULL))
    {
        status = E_INVALID_PARAM;
        goto out;
    }

    mpu_idx2_select((uint8_t)handle);
    smpuacc = csr_read(NV_RISCV_CSR_SMPUACC);
    idxToTest = (uint8_t) handle % MPUIDX2_ENTRIES_PER_INDEX;
    *bAccessed = ((smpuacc & BIT64(idxToTest)) != 0U);

out:
    return status;
}

error_t
mpuIsDirty(const MpuContext *pCtx, MpuHandle handle, bool *bDirty)
{
    error_t status = E_SUCCESS;
    uint64_t smpudty;
    uint8_t idxToTest;

    if ((pCtx == NULL) ||
        (handle >= pCtx->mpuEntryCount) ||
        (!bitmap_test_bit(pCtx->mpuReservedBitmap, NV_RISCV_CSR_MPU_ENTRY_COUNT / BITMAP_ELEMENT_SIZE, handle)) ||
        (bDirty == NULL))
    {
        status = E_INVALID_PARAM;
        goto out;
    }

    mpu_idx2_select((uint8_t)handle);
    smpudty = csr_read(NV_RISCV_CSR_SMPUDTY);
    idxToTest = (uint8_t) handle % MPUIDX2_ENTRIES_PER_INDEX;
    *bDirty = ((smpudty & BIT64(idxToTest)) != 0U);

out:
    return status;
}

error_t
mpuClearAccessedBit(const MpuContext *pCtx, MpuHandle handle)
{
    error_t status = E_SUCCESS;
    uint8_t idxToClear;

    if ((pCtx == NULL) ||
        (handle >= pCtx->mpuEntryCount) ||
        (!bitmap_test_bit(pCtx->mpuReservedBitmap, NV_RISCV_CSR_MPU_ENTRY_COUNT / BITMAP_ELEMENT_SIZE, handle)))
    {
        status = E_INVALID_PARAM;
        goto out;
    }

    mpu_idx2_select((uint8_t)handle);
    idxToClear = (uint8_t) handle % MPUIDX2_ENTRIES_PER_INDEX;
    csr_clear(NV_RISCV_CSR_SMPUACC, BIT64(idxToClear));

out:
    return status;
}

error_t
mpuClearDirtyBit(const MpuContext *pCtx, MpuHandle handle)
{
    error_t status = E_SUCCESS;
    uint8_t idxToClear;

    if ((pCtx == NULL) ||
        (handle >= pCtx->mpuEntryCount) ||
        (!bitmap_test_bit(pCtx->mpuReservedBitmap, NV_RISCV_CSR_MPU_ENTRY_COUNT / BITMAP_ELEMENT_SIZE, handle)))
    {
        status = E_INVALID_PARAM;
        goto out;
    }

    mpu_idx2_select((uint8_t)handle);
    idxToClear = (uint8_t) handle % MPUIDX2_ENTRIES_PER_INDEX;
    csr_clear(NV_RISCV_CSR_SMPUDTY, BIT64(idxToClear));

out:
    return status;
}

error_t
mpuVaToPa(const MpuContext *pCtx, uint64_t va, bool bOnlyEnabled, uint64_t *pPa)
{
    error_t status = E_INVALID_PARAM;
    uint8_t idx;

    if ((pCtx == NULL) || (pPa == NULL))
    {
        status = E_INVALID_PARAM;
        goto out;
    }

    for (idx = 0; idx < pCtx->mpuEntryCount; idx++)
    {
        if (bitmap_test_bit(pCtx->mpuReservedBitmap, NV_RISCV_CSR_MPU_ENTRY_COUNT / BITMAP_ELEMENT_SIZE, idx))
        {
            mpu_idx_select(idx);
            uint64_t vaReg = csr_read(NV_RISCV_CSR_SMPUVA);
            uint64_t vaBase = vaReg & ~NV_RISCV_CSR_MPU_PAGE_MASK;
            uint64_t rng;

            //If entry is disabled or va is below range, don't check this entry
            if ((bOnlyEnabled && FLD_TEST_DRF_NUM64(_RISCV_CSR, _SMPUVA, _VLD, 0U, vaReg)) ||
                (vaBase > va))
            {
                continue;
            }

            rng = csr_read(NV_RISCV_CSR_SMPURNG) & ~NV_RISCV_CSR_MPU_PAGE_MASK;

            // check for overflow in vaBase + rng (CERT-C). Shoudln't happen in a sanely-programmed entry.
            if ((UINT64_MAX - vaBase) < rng)
            {
                continue;
            }

            if (va < (vaBase + rng))
            {
                //Do a manual translation
                uint64_t paBase = csr_read(NV_RISCV_CSR_SMPUPA) & ~NV_RISCV_CSR_MPU_PAGE_MASK;

                // check for overflow in (va - vaBase) + paBase; (CERT-C). Shoudln't happen in a sanely-programmed entry.
                if ((UINT64_MAX - paBase) < (va - vaBase))
                {
                    continue;
                }

                *pPa = (va - vaBase) + paBase;
                status = E_SUCCESS;
                break;
            }
        }
    }

out:
    return status;
}

END_RFD_BLOCK(MISRA, DEVIATE, Rule_1_2, "Approval: Bug 200531996, DR: SWE-FSP-009-SWSADR.docx",
              MISRA, DEVIATE, Directive_4_9, "Approval: Bug 200531995, DR: SWE-FSP-012-SWSADR.docx",
              MISRA, DEVIATE, Rule_15_1, "Approval: Bug 3176459, DR: SWE-FSP-028-SWSADR.docx",
              MISRA, DEVIATE, Rule_8_7, "Approval: Bug 200531999, DR: SWE-FSP-015-SWSADR.docx")
