/**
 * @file        scp_private.h
 * @brief       Private interfaces for internal use by the SCP driver.
 *
 * @note        Not intended for use by client applications.
 *
 * 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.
 */

#ifndef SCP__SCP_PRIVATE_H
#define SCP__SCP_PRIVATE_H
#define FSP__SCP__SCP_PRIVATE_H 1

/* Compiler headers */
#include <stdbool.h>                    // for bool
#include <stddef.h>                     // for size_t
#include <stdint.h>                     // for uint8_t, uintptr_t, etc.

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

/* Hardware headers */

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

/* Module-specific FSP headers */
#include <scp/scp_common.h>             // for scp_register_index_t

/*
 * 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__CSR_H)
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_COMMON_H)


/**
 * @brief The hardware secret to use when clearing registers in secure contexts.
 *
 * @note Not to be used for any other purpose, per the security bar.
 */
#define SCP_SECRET_INDEX_CLEAR 0U


/**
 * @brief A structure describing the internal state of the SCP driver.
 *
 * @b_initialized           Tracks whether the driver has been initialized
 *                          correctly. Most interfaces will early-out if this
 *                          flag is not set.
 *
 * @b_shutdown              Tracks whether the driver has been shut down after a
 *                          prior initialization. Mostly a courtesy feature to
 *                          detect duplicate calls to scp_Shutdown().
 *
 * @b_direct_mode_active    Tracks whether the driver is currently operating in
 *                          "direct mode", which affects whether certain higher-
 *                          level interfaces are available.
 *
 * @b_rand_configured       Tracks whether the SCP unit's RNG hardware has been
 *                          configured. Requests for random-number generation
 *                          will be declined unless this flag is set.
 *
 * @b_shortcut_configured   Tracks whether the shortcut-DMA path has been
 *                          configured. dma_index below is only valid if this
 *                          flag is set and any requests for transfers to
 *                          external memory will be declined otherwise.
 *
 * @dma_index               Stores the index of the DMA aperture to be used for
 *                          shortcut transfers to external memory. Valid only if
 *                          b_shortcut_configured is set.
 */
typedef struct scp_state_t
{
    bool    b_initialized           : 1;
    bool    b_shutdown              : 1;
    bool    b_direct_mode_active    : 1;
    bool    b_rand_configured       : 1;
    bool    b_shortcut_configured   : 1;
    uint8_t dma_index;
} scp_state_t;


/**
 * @brief Clears any data from the specified SCP GPR and then unlocks its ACL.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre The target register must be writable and fetchable if called from an
 *      insecure context.
 *
 * @param[in] reg_index The index of the SCP register to clear.
 *
 * @retval E_SUCCESS                    if the register was cleared
 *                                      successfully.
 *
 * @retval E_SCP_INSUFFICIENT_RESOURCES if the application lacks access to
 *                                      secret-index zero (required in secure
 *                                      contexts).
 *
 * @retval E_SCP_GENERIC                if an unexpected error occurred.
 *
 * @note This is an internal-only version of scp_Clear_Reg() that skips certain
 *       redundant checks.
 */
error_t scp_pvt_Clear_Reg(scp_register_index_t reg_index);

/**
 * @brief Clears any data from all SCP GPRs and then unlocks their ACLs.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre All GPRs must be writable and fetchable if called from an insecure
 *      context.
 *
 * @retval E_SUCCESS                    if the registers were cleared
 *                                      successfully.
 *
 * @retval E_SCP_INSUFFICIENT_RESOURCES if the application lacks access to
 *                                      secret-index zero (required in secure
 *                                      contexts).
 *
 * @retval E_SCP_GENERIC                if an unexpected error occurred.
 *
 * @note This is an internal-only version of scp_Clear_All() that skips certain
 *       redundant checks.
 */
error_t scp_pvt_Clear_All(void);

/**
 * @brief Resets the ACLs of all SCP GPRs to a fully-open state.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre This function must only be called from secure contexts.
 * @pre All GPRs must have both secure permissions (Fs and Ks) enabled.
 *
 * @retval E_SUCCESS        if the registers were unlocked successfully.
 * @retval E_SCP_GENERIC    if an unexpected error occurred.
 *
 * @note As a precaution, the contents of each register are cleared after
 *       unlocking. It is recommended that callers also clear the registers
 *       before unlocking by calling scp_pvt_Zero_All() and checking that it
 *       succeeded.
 */
error_t scp_pvt_Unlock_All(void);

/**
 * @brief Zeroes all SCP GPRs.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre All GPRs must be writable and fetchable if called from an insecure
 *      context.
 *
 * @retval E_SUCCESS                    if the registers were zeroed
 *                                      successfully.
 *
 * @retval E_SCP_INSUFFICIENT_RESOURCES if the application lacks access to
 *                                      secret-index zero (required in secure
 *                                      contexts).
 *
 * @retval E_SCP_GENERIC                if an unexpected error occurred.
 *
 * @note Calling this function from a secure context will leave the SCP GPRs
 *       inaccessible from insecure contexts unless explicitly unlocked later.
 */
error_t scp_pvt_Zero_All(void);

/**
 * @brief Loads data from a buffer into a general-purpose SCP register.
 *
 * Loads the first SCP_REGISTER_SIZE bytes from source_pa into the register
 * specified by reg_index. Assumes that the destination register is writable
 * and that the caller has performed all necessary validation on source_pa and
 * reg_index.
 *
 * Intended as an internal-only convenience wrapper for scp_load().
 *
 * @pre The SCP hardware must have been properly initialized.
 *
 * @param[in]  source_pa    The physical address of the input buffer containing
 *                          the data to be loaded. Must be aligned to and
 *                          contain at least SCP_REGISTER_SIZE bytes. Supports
 *                          IMEM and DMEM locations only.
 *
 * @param[in]  reg_index    The index of the SCP register to which the data is
 *                          to be written.
 *
 * @retval E_SUCCESS                if data was loaded successfully.
 * @retval E_FAULT                  if an IO-PMP fault was detected.
 * @retval E_SCP_GENERIC            if an unexpected error occurred.
 */
error_t scp_pvt_Load_Buffer(uintptr_t               source_pa,
                            scp_register_index_t    reg_index);

/**
 * @brief Stores data from a general-purpose SCP register into a buffer.
 *
 * Writes the contents of the register specified by reg_index to the first
 * SCP_REGISTER_SIZE bytes of dest_pa. Assumes that the source register is
 * fetchable and that the caller has performed all necessary validation on
 * dest_pa and reg_index.
 *
 * In the event that dest_pa refers to an external memory location, the
 * caller is also responsible for ensuring that the shortcut-DMA path has been
 * properly enabled and configured accordingly.
 *
 * Intended as an internal-only convenience wrapper for scp_store().
 *
 * @pre The SCP hardware must have been properly initialized.
 *
 * @param[in]  reg_index The index of the SCP register containing the data to
 *                       be stored.
 *
 * @param[out] dest_pa   The physical address of the output buffer to which the
 *                       data is to be written. Must be aligned to and have
 *                       capacity for at least SCP_REGISTER_SIZE bytes. Supports
 *                       IMEM, DMEM, and external locations.
 *
 * @retval E_SUCCESS                if data was stored successfully.
 * @retval E_FAULT                  if an IO-PMP fault was detected.
 * @retval E_SCP_GENERIC            if an unexpected error occurred.
 */
error_t scp_pvt_Store_Buffer(scp_register_index_t   reg_index,
                             uintptr_t              dest_pa);

/**
 * @brief Validates an input/output buffer for use by a higher-level interface.
 *
 * Verifies that the input or output buffer pointed to by base_pa is located at
 * a valid address within a supported memory region and is correctly aligned.
 * Also ensures that shortcut-DMA support has been properly enabled and
 * configured when b_allow_external is true.
 *
 * @param[in]  base_pa          The physical address of the buffer to validate.
 *
 * @param[in]  size             The size of the buffer to validate, in bytes.
 *
 * @param[in]  b_allow_external Whether to permit external memory locations.
 *
 * @retval E_SUCCESS                    if the buffer was validated
 *                                      successfully.
 *
 * @retval E_SCP_INVALID_POINTER        if base_pa is zero (NULL).
 *
 * @retval E_SCP_VINALID_ADDRESS        if base_pa is incorrectly aligned.
 *
 * @retval E_SCP_INVALID_BASE           if base_pa points to an unsupported
 *                                      memory region (e.g. EMEM).
 *
 * @retval E_SCP_FEATURE_NOT_ENABLED    if base_pa is located in external memory
 *                                      but the application does not have
 *                                      shortcut-DMA support enabled.
 *
 * @retval E_SCP_INVALID_DMA_SPECIFIER  if base_pa is located in external memory
 *                                      but the application has not called
 *                                      scp_Configure_Shortcut() to configure
 *                                      the shortcut-DMA path.
 *
 * @retval E_SCP_OUT_OF_RANGE           if the buffer referenced by base_pa
 *                                      overflows the memory region in which it
 *                                      resides.
 */
error_t scp_pvt_Validate_Buffer(uintptr_t    base_pa,
                                size_t       size,
                                bool         b_allow_external);

/**
 * @brief Executes the program currently loaded in SCP sequencer zero.
 *
 * Repeatedly executes the sequencer program currently loaded into SCP
 * sequencer zero until size bytes of data have been read from source_pa and/or
 * written to dest_pa. Automatically breaks the input and/or output data into
 * optimally-sized batches while processing.
 *
 * Note that the caller is assumed to have performed all necessary validation
 * on source_pa, dest_pa, and size before passing them to this function. This
 * includes verifying proper enablement and configuration of the shortcut-DMA
 * path in the event that dest_pa refers to an external memory location.
 *
 * It is also assumed that the number of push/fetch instructions issued per
 * iteration of the currently-loaded sequencer program exactly matches the
 * values in the table below.
 *
 *       source_pa | dest_pa | pushes | fetches
 *      -----------|---------|--------|---------
 *        Present  | Present |   1    |    1
 *        Present  | Absent  |   1    |    0
 *        Absent   | Present |   0    |    1
 *        Absent   | Absent  |   0    |    0
 *
 * While it is permissible to call this function with neither an input buffer
 * nor an output buffer, doing so is likely to be less efficient than simply
 * invoking the required number of sequencer iterations directly.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre The current security context must match that which was used to load the
 *      sequencer program.
 *
 * @param[in]  source_pa    The physical address of the input buffer containing
 *                          the data to be fed into the sequencer program (zero
 *                          /NULL if none). Must be aligned to
 *                          SCP_REGISTER_SIZE and contain at least size bytes.
 *                          Supports IMEM and DMEM locations only.
 *
 * @param[out] dest_pa      The physical address of the output buffer to which
 *                          the data produced by the sequencer program is to be
 *                          written (zero/NULL if none). Must be aligned to
 *                          SCP_REGISTER_SIZE and have at least size bytes of
 *                          capacity. Supports IMEM, DMEM, and external
 *                          locations.
 *
 * @param[in]  size         The number of bytes to process from source_pa and/or
 *                          output to dest_pa. Must be a multiple of
 *                          SCP_REGISTER_SIZE.
 *
 * @retval E_SUCCESS                if data was processed successfully.
 * @retval E_FAULT                  if an IO-PMP fault was detected.
 * @retval E_SCP_GENERIC            if an unexpected error occurred.
 */
error_t scp_pvt_Run_Sequence(uintptr_t  source_pa,
                             uintptr_t  dest_pa,
                             size_t     size);

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

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

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

/**
 * @brief Checks whether SCP lockdown is enabled.
 *
 * @retval true     if SCP lockdown is enabled.
 * @retval false    if SCP lockdown is disabled.
 */
static inline bool
scp_pvt_Is_Lockdown_Enabled(void)
{
    return FLD_TEST_DRF(_PRGNLCL_SCP, _CTL_CFG, _LOCKDOWN_SCP, _ENABLE,
        local_read(NV_PRGNLCL_SCP_CTL_CFG));
}

/**
 * @brief Checks whether the host engine is operating in a secure context.
 *
 * @retval true     if the host engine is operating in a secure context.
 * @retval false    if the host engine is operating in an insecure context.
 */
static inline bool
scp_pvt_Is_Secure(void)
{
    return FLD_TEST_DRF64(_RISCV_CSR, _XRSP, _XRSEC, _SEC,
                          csr_read(NV_RISCV_CSR_XRSP));
}

/**
 * @brief Checks whether access to lockable SCP registers/fields is restricted.
 *
 * @retval true     if access is restricted.
 * @retval false    if access is unrestricted.
 */
static inline bool
scp_pvt_Is_Access_Restricted(void)
{
    return (scp_pvt_Is_Lockdown_Enabled() && !scp_pvt_Is_Secure());
}

/**
 * @brief Requests a pipe reset. Waits for completion before returning.
 *
 * @pre Must be called from a secure context if SCP lockdown is enabled.
 */
static inline void
scp_pvt_Reset_Pipeline(void)
{
    // Request a pipe reset.
    local_write(NV_PRGNLCL_SCP_CTL1,
        FLD_SET_DRF(_PRGNLCL_SCP, _CTL1, _PIPE_RESET, _TASK,
            local_read(NV_PRGNLCL_SCP_CTL1)));

    // Wait for the pipe reset to complete.
    while (FLD_TEST_DRF(_PRGNLCL_SCP, _CTL1, _PIPE_RESET, _PENDING,
        local_read(NV_PRGNLCL_SCP_CTL1)))
    {
    }
}

/**
 * @brief Clears both SCP sequencers.
 *
 * @pre Must be called from a secure context if SCP lockdown is enabled.
 */
static inline void
scp_pvt_Reset_Sequencers(void)
{
    // Request a reset of both sequencers.
    local_write(NV_PRGNLCL_SCP_CTL1,
        FLD_SET_DRF(_PRGNLCL_SCP, _CTL1, _SEQ_CLEAR, _TASK,
            local_read(NV_PRGNLCL_SCP_CTL1)));

    // Wait for the sequencer reset to complete.
    while (FLD_TEST_DRF(_PRGNLCL_SCP, _CTL1, _SEQ_CLEAR, _PENDING,
        local_read(NV_PRGNLCL_SCP_CTL1)))
    {
    }
}

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

/**
 * @brief Global internal state variable.
 */
extern scp_state_t g_scp_state;

#endif // SCP__SCP_PRIVATE_H
