/**
 * @file        scp_rand.c
 * @brief       SCP features for random-number generation.
 *
 * Copyright (c) 2021-2022, NVIDIA CORPORATION. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * * Neither the name of NVIDIA CORPORATION nor the names of its
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#if !NVRISCV_FEATURE_SCP
#error "Attempting to build scp_rand.c but the SCP driver is disabled!"
#endif // NVRISCV_FEATURE_SCP


/* Compiler headers */
#include <stdbool.h>                    // for bool, true, false
#include <stddef.h>                     // for size_t, NULL
#include <stdint.h>                     // for uintptr_t, uint32_t

/* Early FSP headers */
#include <misc/ct-assert.h>             // for HEADER_CHECK

/* Hardware headers */

/* Late FSP headers */
#include <cpu/io.h>                     // for local_read, local_write
#include <error/common-errors.h>        // for error_t, common error codes
#include <misc/nvmisc_drf.h>            // for DRF macros

/* Module-specific FSP headers */
#include <scp/scp-errors.h>             // for SCP error codes
#include <scp/scp_common.h>             // for scp_register_index_t
#include <scp/scp_direct.h>             // for SCP primitives
#include <scp/scp_general.h>            // for scp_Is_Debug
#include <scp/scp_private.h>            // for scp_pvt_*
#include <scp/scp_rand.h>               // for SCP_RAND_SIZE, etc.

/*
 * Compile-time check for FSP header files
 *   Each FSP header file contains a signature unique to that file, and the
 *   FSP project.  The HEADER_CHECK macro (contained in misc/ct-assert.h) can
 *   check for this signature.  If it does not exist, then the build will
 *   abort.
 *
 *   This is a trap for projects which have their own include files of the
 *   same names, but different contents.  This trap ensures that only the
 *   files from the FSP project, are built into the FSP source code.
 */
HEADER_CHECK(FSP__CPU__IO_H)
HEADER_CHECK(FSP__ERROR__COMMON_ERRORS_H)
HEADER_CHECK(FSP__MISC__NVMISC_DRF_H)
HEADER_CHECK(FSP__SCP__SCP_ERRORS_H)
HEADER_CHECK(FSP__SCP__SCP_COMMON_H)
HEADER_CHECK(FSP__SCP__SCP_DIRECT_H)
HEADER_CHECK(FSP__SCP__SCP_GENERAL_H)
HEADER_CHECK(FSP__SCP__SCP_PRIVATE_H)
HEADER_CHECK(FSP__SCP__SCP_RAND_H)


/**
 * @brief Default tap selections for the two non-SCC ring-oscillators.
 *
 * Values taken from https://confluence.nvidia.com/x/MO1QGw.
 *
 * @macro-title Default ring-oscillator tap selections.
 *
 * @SCP_DEFAULT_RO_TAP_A    Tap selection for ring-oscillator A.
 * @SCP_DEFAULT_RO_TAP_B    Tap selection for ring-oscillator B.
 */
#define SCP_DEFAULT_RO_TAP_A 0x7U
#define SCP_DEFAULT_RO_TAP_B 0x5U


//
// Prototypes for local helper functions.
// See definitions for details.
//
static error_t     s_scp_Load_Rand_Sequence(void);

static inline bool s_scp_Is_Rand_Enabled(void);

static inline void s_scp_Configure_Oscillators(void);

static inline void s_scp_Disable_Fake_Rand(void);
static inline void s_scp_Enable_Fake_Rand(void);


/**
 * @copydoc scp_Configure_Rand
 */
error_t
scp_Configure_Rand
(
    scp_rand_config_t config
)
{
    // Verify driver state before proceeding further.
    if (!g_scp_state.b_initialized)
    {
        return E_SCP_NOT_READY;
    }

    // Invalidate previous configuration settings, if any.
    g_scp_state.b_rand_configured = false;

    // This function cannot be called from direct mode.
    if (g_scp_state.b_direct_mode_active)
    {
        return E_INVALID_STATE;
    }

    // Verify access to lockable SCP registers/fields.
    if (scp_pvt_Is_Access_Restricted())
    {
        return E_SCP_INVALID_LOCK_STATE;
    }

    // Ensure that the RNG hardware is not currently active.
    if (s_scp_Is_Rand_Enabled())
    {
        return E_BUSY;
    }

    // Apply the desired configuration settings.
    switch (config)
    {
        case SCP_RAND_CONFIG_DEFAULT:
            // Tap the ring-oscillators at their recommended lengths.
            s_scp_Configure_Oscillators();

            // Ensure that deterministic mode is off.
            s_scp_Disable_Fake_Rand();
            break;

        case SCP_RAND_CONFIG_FAKE:
            // Build-time configuration is mandatory per security-bar rules.
            if (!NVRISCV_DRIVER_SCP_FAKE_RNG_ENABLED)
            {
                return E_SCP_FEATURE_NOT_ENABLED;
            }

            // Deterministic mode is not permitted in production.
            if (!scp_Is_Debug())
            {
                return E_SCP_INVALID_REQUEST;
            }

            //
            // Enable deterministic mode as requested (no further configuration
            // is needed as of SCP RNG v2.0).
            //
            s_scp_Enable_Fake_Rand();
            break;

        default:
            // Not a recognized configuration.
            return E_INVALID_PARAM;
    }

    // Perform a pipe reset to clear any residual RNG data.
    scp_pvt_Reset_Pipeline();

    // Update internal state.
    g_scp_state.b_rand_configured = true;

    // Configuration settings were applied successfully.
    return E_SUCCESS;
}

/**
 * @copydoc scp_Start_Rand
 */
error_t
scp_Start_Rand(void)
{
    // Verify driver state before proceeding further.
    if (!g_scp_state.b_initialized)
    {
        return E_SCP_NOT_READY;
    }

    // Ensure that the RNG hardware has been properly configured.
    if (!g_scp_state.b_rand_configured)
    {
        return E_INVALID_STATE;
    }

    // Verify access to lockable SCP registers/fields.
    if (scp_pvt_Is_Access_Restricted())
    {
        return E_SCP_INVALID_LOCK_STATE;
    }

    // Check whether the RNG hardware is already running.
    if (s_scp_Is_Rand_Enabled())
    {
        return E_SCP_NOTHING_TO_DO;
    }

    // Start the RNG hardware.
    scp_pvt_Enable_Rand();

    // RNG hardware was started successfully.
    return E_SUCCESS;
}

/**
 * @copydoc scp_Stop_Rand
 */
error_t
scp_Stop_Rand(void)
{
    // Verify driver state before proceeding further.
    if (!g_scp_state.b_initialized)
    {
        return E_SCP_NOT_READY;
    }

    // Verify access to lockable SCP registers/fields.
    if (scp_pvt_Is_Access_Restricted())
    {
        return E_SCP_INVALID_LOCK_STATE;
    }

    // Check whether the RNG hardware is already stopped.
    if (!s_scp_Is_Rand_Enabled())
    {
        return E_SCP_NOTHING_TO_DO;
    }

    //
    // Make sure the SCP unit is idle before disabling RNG. Otherwise, we may
    // cause some pending RNG operation to hang.
    //
    scp_wait_dma();

    // Stop the RNG hardware.
    scp_pvt_Disable_Rand();

    // RNG hardware was stopped successfully.
    return E_SUCCESS;
}

/**
 * @copydoc scp_Get_Rand
 */
error_t
scp_Get_Rand
(
    uintptr_t   dest_pa,
    size_t      size,
    uintptr_t   context_pa
)
{
    error_t status = E_SUCCESS;

    // Verify driver state before proceeding further.
    if (!g_scp_state.b_initialized)
    {
        // Early exit as clean-up code may not be able to execute safely.
        return E_SCP_NOT_READY;
    }

    // This function cannot be called from direct mode.
    if (g_scp_state.b_direct_mode_active)
    {
        // Early exit as clean-up code may not be able to execute safely.
        return E_INVALID_STATE;
    }

    // Verify access to lockable SCP registers/fields.
    if (scp_pvt_Is_Access_Restricted())
    {
        // Early exit as clean-up code may not be able to execute safely.
        return E_SCP_INVALID_LOCK_STATE;
    }

    // Make sure the RNG hardware is active and properly configured.
    if (!s_scp_Is_Rand_Enabled() || !g_scp_state.b_rand_configured)
    {
        status = E_SCP_INVALID_OPERATION;
        goto cleanup;
    }

    // Verify destination buffer.
    status = scp_pvt_Validate_Buffer(dest_pa, size, true);
    if (status != E_SUCCESS)
    {
        goto cleanup;
    }

    // Verify byte count.
    if ((size == 0U) || (size % SCP_RAND_SIZE != 0U))
    {
        status = E_SCP_INVALID_REQUEST;
        goto cleanup;
    }

    // Handle context buffer, if provided.
    if (context_pa != (uintptr_t)NULL)
    {
        // Verify context buffer.
        status = scp_pvt_Validate_Buffer(context_pa, SCP_RAND_SIZE, false);
        if (status != E_SUCCESS)
        {
            goto cleanup;
        }

        // Load initial context information into register-index one.
        status = scp_pvt_Load_Buffer(context_pa, SCP_REGISTER_INDEX_1);
        if (status != E_SUCCESS)
        {
            goto cleanup;
        }
    }

    // Load sequencer zero with the instructions for RNG and update Ku.
    status = s_scp_Load_Rand_Sequence();
    if (status != E_SUCCESS)
    {
        goto cleanup;
    }

    // Run the sequence we loaded above to generate the output data.
    status = scp_pvt_Run_Sequence((uintptr_t)NULL, dest_pa, size);
    if (status != E_SUCCESS)
    {
        goto cleanup;
    }

cleanup:
    //
    // Clear the sequencers to free them up for future use and prevent
    // anything accidentally carrying forward into other interfaces.
    //
    scp_pvt_Reset_Sequencers();

    //
    // Clear the SCP GPRs to scrub any potentially sensitive data from them and
    // unlock their associated ACLs. Report failures only if no higher-priority
    // error is already pending.
    //
    error_t clear_status = scp_pvt_Clear_All();
    if (status == E_SUCCESS)
    {
        status = clear_status;
    }

    // Done.
    return status;
}

///////////////////////////////////////////////////////////////////////////////

/**
 * @brief Loads the SCP instructions for RNG into sequencer zero.
 *
 * Loads sequencer zero with the set of SCP instructions required to generate a
 * single block of true-random data. Also updates the key register (Ku).
 *
 * The loaded algorithm assumes that the initial context value, if any, has
 * been loaded to register-index one and that register-indices two and three
 * are available for use as intermediate storage.
 *
 * @pre The SCP hardware must have been properly initialized.
 *
 * @retval E_SUCCESS                if instructions were loaded successfully.
 * @retval E_SCP_GENERIC            if an unexpected error occurred.
 */
static error_t
s_scp_Load_Rand_Sequence(void)
{
    // Prepare to write five instructions to sequencer zero.
    scp_load_trace0(5U);
    {
        //
        // Improve RNG quality by collecting entropy across two consecutive
        // random numbers.
        //
        scp_rand(SCP_REGISTER_INDEX_2);
        scp_rand(SCP_REGISTER_INDEX_3);

        // Mix the current context value into the first number.
        scp_xor(SCP_REGISTER_INDEX_1, SCP_REGISTER_INDEX_2);

        //
        // Whiten the second number with an encryption step using the above
        // value as the key. The result will be carried forward as the new
        // context value to be used in the next iteration.
        //
        scp_encrypt(SCP_REGISTER_INDEX_3, SCP_REGISTER_INDEX_1);

        // Store the random data we produced.
        scp_fetch(SCP_REGISTER_INDEX_1);
    }

    //
    // Select the register from which the encrypt stage of each sequencer
    // iteration will source its key. This only needs to be specified once,
    // hence its exclusion from the above sequencer load.
    //
    scp_key(SCP_REGISTER_INDEX_2);

    // Wait for completion.
    scp_wait_dma();

    // Check for any unexpected failures.
    if (scp_check_dma(true) != E_SUCCESS)
    {
        return E_SCP_GENERIC;
    }

    // Sequence loaded successfully.
    return E_SUCCESS;
}

///////////////////////////////////////////////////////////////////////////////

/**
 * @brief Checks whether the SCP unit's RNG hardware is enabled.
 *
 * @pre Must be called from a secure context if SCP lockdown is enabled.
 *
 * @retval true  if the RNG hardware is enabled.
 * @retval false if the RNG hardware is disabled.
 */
static inline bool
s_scp_Is_Rand_Enabled(void)
{
    return FLD_TEST_DRF(_PRGNLCL_SCP, _CTL1, _RNG_EN, _ENABLED,
                        local_read(NV_PRGNLCL_SCP_CTL1));
}

/**
 * @brief Configures the ring-oscillators for the SCP unit's RNG hardware.
 *
 * @pre Must be called from a secure context if SCP lockdown is enabled.
 */
static inline void
s_scp_Configure_Oscillators(void)
{
    uint32_t reg = local_read(NV_PRGNLCL_SCP_RNDCTL11);

    reg = FLD_SET_DRF_NUM(_PRGNLCL_SCP, _RNDCTL11, _AUTOCAL_STATIC_TAP_A,
                          SCP_DEFAULT_RO_TAP_A, reg);
    reg = FLD_SET_DRF_NUM(_PRGNLCL_SCP, _RNDCTL11, _AUTOCAL_STATIC_TAP_B,
                          SCP_DEFAULT_RO_TAP_B, reg);

    local_write(NV_PRGNLCL_SCP_RNDCTL11, reg);
}

/**
 * @brief Disables deterministic mode for the SCP unit's RNG hardware.
 *
 * @pre Must be called from a secure context if SCP lockdown is enabled.
 */
static inline void
s_scp_Disable_Fake_Rand(void)
{
    local_write(NV_PRGNLCL_SCP_CTL1,
        FLD_SET_DRF(_PRGNLCL_SCP, _CTL1, _RNG_FAKE_MODE, _DISABLED,
            local_read(NV_PRGNLCL_SCP_CTL1)));
}

/**
 * @brief Enables deterministic mode for the SCP unit's RNG hardware.
 *
 * @pre Must be called from a secure context if SCP lockdown is enabled.
 */
static inline void
s_scp_Enable_Fake_Rand(void)
{
    local_write(NV_PRGNLCL_SCP_CTL1,
        FLD_SET_DRF(_PRGNLCL_SCP, _CTL1, _RNG_FAKE_MODE, _ENABLED,
            local_read(NV_PRGNLCL_SCP_CTL1)));
}
