/**
 * @file        scp_general.c
 * @brief       General SCP operations.
 *
 * 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_general.c but the SCP driver is disabled!"
#endif // NVRISCV_FEATURE_SCP


/* Compiler headers */
#include <stdbool.h>                    // for bool, true, false
#include <stdint.h>                     // for uint8_t, uint32_t, etc.

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

/* Hardware headers */

/* Late FSP headers */
#include <cpu/devicemap.h>              // for DEVICEMAP_HAS_ACCESS
#include <cpu/io.h>                     // for local_read, local_write
#include <cpu/iopmp.h>                  // for IOPMP_IS_CAPTURE_ENABLED
#include <error/common-errors.h>        // for error_t, common error codes
#include <misc/macros.h>                // for INLINE_RFD

/* 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_init_flag_t
#include <scp/scp_private.h>            // for scp_pvt_*

/*
 * 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__MISC__NVMISC_DRF_H)
HEADER_CHECK(FSP__CPU__DEVICEMAP_H)
HEADER_CHECK(FSP__CPU__IO_H)
HEADER_CHECK(FSP__CPU__IOPMP_H)
HEADER_CHECK(FSP__ERROR__COMMON_ERRORS_H)
HEADER_CHECK(FSP__MISC__MACROS_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)


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

static bool        s_scp_Has_Interrupt(void);

static inline bool s_scp_Is_Active(void);
static inline bool s_scp_Is_Present(void);
static inline bool s_scp_Is_Secure_Capable(void);

static inline void s_scp_Clear_Interrupts(void);
static inline void s_scp_Disable_Interrupts(void);
static inline void s_scp_Enable_Interrupts(void);

static inline void s_scp_Disable_Processing(void);
static inline void s_scp_Enable_Processing(void);

static inline void s_scp_Reset_Configuration(void);
static inline void s_scp_Reset_Controls(void);
static inline void s_scp_Reset_Secret_63(void);
static inline void s_scp_Reset_State(void);


/**
 * @copydoc scp_Init
 */
error_t
scp_Init
(
    scp_init_flag_t flags
)
{
    error_t status;

    //
    // Reset the internal driver state before doing anything else. This
    // prevents, e.g., stale state from some other partition carrying over into
    // this one, even in the event of an early-exit later in this function.
    //
    s_scp_Reset_State();

    // Verify that the host Peregrine actually contains an SCP unit.
    if (!s_scp_Is_Present())
    {
        return E_NOTSUPPORTED;
    }

    //
    // Check that we have sufficient access rights to the necessary device-map
    // groups. We do this just as a courtesy check at init and not in every
    // interface.
    //
    if (!DEVICEMAP_HAS_ACCESS(MMODE, SCP,   READ)  ||
        !DEVICEMAP_HAS_ACCESS(MMODE, SCP,   WRITE) ||
        !DEVICEMAP_HAS_ACCESS(MMODE, IOPMP, READ))
    {
        return E_PERM;
    }

#if NVRISCV_CONFIG_CPU_MODE != 3
    // Not operating in M-mode so check S/U as well.
    if (!DEVICEMAP_HAS_ACCESS(SUBMMODE, SCP,   READ)  ||
        !DEVICEMAP_HAS_ACCESS(SUBMMODE, SCP,   WRITE) ||
        !DEVICEMAP_HAS_ACCESS(SUBMMODE, IOPMP, READ))
    {
        return E_PERM;
    }
#endif // NVRISCV_CONFIG_CPU_MODE != 3

    // Verify that IO-PMP error-capture is enabled for CPDMA transfers.
    if (!IOPMP_IS_CAPTURE_ENABLED(CPDMA))
    {
        return E_INVALID_STATE;
    }

    // Verify that SCP lockdown is correctly configured.
    if (s_scp_Is_Secure_Capable() != scp_pvt_Is_Lockdown_Enabled())
    {
        return E_SCP_INVALID_LOCK_STATE;
    }

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

    //
    // Ensure the SCP unit is idle before trying to reset it. Otherwise, we
    // may corrupt the result of some prior operation that's still pending.
    //
    if (scp_poll_dma())
    {
        return E_BUSY;
    }

    //
    // Hardware state has been verified. Check that the configuration flags
    // are valid as well before initiating any changes.
    //
    if (flags != SCP_INIT_FLAG_DEFAULT)
    {
        // No other flags are supported currently.
        return E_SCP_INVALID_FLAGS;
    }

    //
    // Concurrent SCP usage is not supported, so the unit is expected to remain
    // idle throughout initialization regardless, but disable processing anyway
    // as a precautionary measure.
    //
    s_scp_Disable_Processing();

    // Disable any interrupts that could cause us to stall later.
    s_scp_Disable_Interrupts();

    //
    // Clear any already-pending interrupts or SCPDMA errors so we can check
    // later that we didn't unexpectedly trigger any new ones ourselves.
    //
    s_scp_Clear_Interrupts();
    (void) scp_check_dma(true);

    //
    // Perform a pipe reset and clear the sequencers. We also disable RNG at
    // this point (applications can explicitly re-enable later if needed).
    //
    s_scp_Reset_Controls();

    //
    // Reset the carry-chain, endianness, stall-timeout, etc. to their
    // default values.
    //
    s_scp_Reset_Configuration();

    //
    // Re-enable processing now that configuration is complete. Needs to be
    // done before attempting to clear registers or we'll hang.
    //
    s_scp_Enable_Processing();

    if (!scp_pvt_Is_Secure())
    {
        //
        // Ensure we have fetchable access to the general-purpose registers
        // before trying to clear them. Secure code can do this via the
        // scp_secret() primitive (which scp_pvt_Zero_All() issues automatically
        // if needed) but insecure code requires the alternative approach
        // invoked here.
        //
        // Note that any subsequent driver code that causes these permissions
        // to be revoked again will also make sure to restore them before
        // returning, thus bypassing the need for this special procedure
        // beyond first-time initialization.
        //
        // In the event that one or more of the general-purpose registers lack
        // insecure-writable access (Wi), we will have no means of recovering
        // access whatsoever and will simply error out when we catch the
        // ensuing write-violation interrupt below.
        //
        status = s_scp_Recover_Insecure();
        if (status != E_SUCCESS)
        {
            return status;
        }
    }

    // Clear the contents of all general-purpose registers.
    status = scp_pvt_Zero_All();
    if (status != E_SUCCESS)
    {
        return status;
    }

    if (scp_pvt_Is_Secure())
    {
        //
        // Relax the ACLs of the general-purpose registers so insecure code is
        // not locked out later on. It is critical that we only do this if
        // scp_pvt_Zero_All() succeeded entirely, hence the interrupt check.
        //
        if (!s_scp_Has_Interrupt())
        {
            status = scp_pvt_Unlock_All();
            if (status != E_SUCCESS)
            {
                return status;
            }
        }

        //
        // Invalidate the engine's signature/hash. This will generate an
        // interrupt if the signature register is already invalid, hence doing
        // it while they're still disabled.
        //
        // Note that the RISC-V BootROM normally performs this step itself but
        // we repeat it here anyway as a defense-in-depth measure.
        //
        status = s_scp_Forget_Signature();
        if (status != E_SUCCESS)
        {
            return status;
        }
    }

    //
    // Check for any unexpected interrupts. There are very few realistic
    // scenarios in which we would ever hit this condition, so we just return
    // a generic error code here without investigating further.
    //
    if (s_scp_Has_Interrupt())
    {
        return E_SCP_GENERIC;
    }

    //
    // Clear any non-fatal interrupts we may have triggered and then enable a
    // reasonable subset of them for the application.
    //
    s_scp_Clear_Interrupts();
    s_scp_Enable_Interrupts();

    // Update internal state to reflect completed initialization.
    g_scp_state.b_initialized = true;

    // Initialization was successful.
    return E_SUCCESS;
}

/**
 * @copydoc scp_Shutdown
 */
error_t
scp_Shutdown(void)
{
    error_t current_status, final_status = E_SUCCESS;

    // Skip shutdown if already done.
    if (g_scp_state.b_shutdown)
    {
        return E_SCP_NOTHING_TO_DO;
    }

    // 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;
    }

    //
    // Ensure the SCP unit is idle before trying to shut it down. Otherwise,
    // we may corrupt the result of some prior operation that's still pending.
    //
    if (s_scp_Is_Active())
    {
        return E_BUSY;
    }

    // Disable any interrupts that could cause us to stall later.
    s_scp_Disable_Interrupts();

    //
    // Clear any already-pending interrupts or SCPDMA errors so we can check
    // later that we didn't unexpectedly trigger any new ones.
    //
    s_scp_Clear_Interrupts();
    (void) scp_check_dma(true);

    //
    // Perform a pipe reset and clear the sequencers. It's important to do this
    // before attempting to clear the registers in case the application forgot
    // to properly exit direct mode, or encountered a stall. We also disable
    // RNG at this point.
    //
    s_scp_Reset_Controls();

    //
    // Ensure processing is enabled in case, e.g., the application is calling
    // this function in response to a fatal error.
    //
    s_scp_Enable_Processing();

    if (!scp_pvt_Is_Secure())
    {
        //
        // Try to ensure that we have fetchable access to the general-purpose
        // registers before attempting to clear them. scp_pvt_Zero_All() will
        // handle this automatically for secure contexts, but insecure contexts
        // require the alternative procedure used here.
        //
        // Realistically, this should only be necessary if the application has
        // been misusing direct mode, but we do it anyway to maximize our
        // chances of a clean shutdown.
        //
        current_status = s_scp_Recover_Insecure();
        if (final_status == E_SUCCESS)
        {
            final_status = current_status;
        }
    }

    // Clear the contents of all general-purpose registers.
    current_status = scp_pvt_Zero_All();
    if (final_status == E_SUCCESS)
    {
        final_status = current_status;
    }

    //
    // Relax the ACLs of the general-purpose registers so insecure code can
    // freely use them afterwards. It is critical that we only do this if
    // scp_pvt_Zero_All() succeeded entirely, hence the extra check.
    //
    if (scp_pvt_Is_Secure() && !s_scp_Has_Interrupt() &&
        (final_status == E_SUCCESS))
    {
        final_status = scp_pvt_Unlock_All();
    }

    //
    // Check for any unexpected interrupts. There are very few realistic
    // scenarios in which we would ever hit this condition, so we just return
    // a generic error code here without investigating further.
    //
    if (s_scp_Has_Interrupt() && (final_status == E_SUCCESS))
    {
        final_status = E_SCP_GENERIC;
    }

    // Disable further processing until the SCP unit is needed again.
    s_scp_Disable_Processing();

    // Clear any interrupts we may have triggered.
    s_scp_Clear_Interrupts();

    // Reset the source for secret-index 63 (hardware recommendation).
    s_scp_Reset_Secret_63();

    // Clear internal driver state.
    s_scp_Reset_State();

    //
    // Update internal state to reflect completed shutdown. The driver will no
    // longer be in a usable state by this point, even if some prior operation
    // failed, so we do this unconditionally.
    //
    g_scp_state.b_shutdown = true;

    // Shutdown complete.
    return final_status;
}

/**
 * @copydoc scp_Is_Debug
 */
bool
scp_Is_Debug(void)
{
    return !FLD_TEST_DRF(_PRGNLCL_SCP, _CTL_STAT, _DEBUG_MODE, _DISABLED,
                         local_read(NV_PRGNLCL_SCP_CTL_STAT));
}

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

    // Ensure the provided register index is valid.
    if (reg_index > SCP_REGISTER_INDEX_7)
    {
        return E_SCP_INVALID_INDEX;
    }

    // Clear the selected register and unlock its associated ACL if needed.
    return scp_pvt_Clear_Reg(reg_index);
}

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

    // Clear all GPRs and unlock their associated ACLs if needed.
    return scp_pvt_Clear_All();
}

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

    // Invalidate previous setting, if any.
    g_scp_state.b_shortcut_configured = false;

    // Check that shortcut-DMA support is enabled.
    if (!NVRISCV_DRIVER_SCP_SHORTCUT_ENABLED)
    {
        return E_SCP_FEATURE_NOT_ENABLED;
    }

    // Ensure the provided DMA index is valid.
    if (dma_index > DRF_MASK(NV_PRGNLCL_FALCON_DMATRFCMD_CTXDMA))
    {
        return E_SCP_INVALID_INDEX;
    }

    // Update internal state.
    g_scp_state.b_shortcut_configured = true;
    g_scp_state.dma_index             = dma_index;

    // Shortcut-DMA path was configured successfully.
    return E_SUCCESS;
}

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

/**
 * @brief Invalidates the engine's signature/hash.
 *
 * Intended as an internal-only convenience wrapper for scp_forget_sig().
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre This function must only be called from secure contexts.
 *
 * @retval E_SUCCESS        if signature/hash was invalidated successfully.
 * @retval E_SCP_GENERIC    if an unexpected error occurred.
 */
static error_t
s_scp_Forget_Signature(void)
{
    // Invalidate the engine's signature/hash.
    scp_forget_sig();

    // Wait for completion.
    scp_wait_dma();

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

    // Signature/hash invalidated successfully.
    return E_SUCCESS;
}

/**
 * @brief Recovers insecure access to the SCP GPRs.
 *
 * Recovers insecure-fetchable and insecure-keyable access to the SCP GPRs.
 *
 * Requires that insecure-writable access (Wi) is already present for all
 * registers. Only intended to be called from insecure contexts.
 *
 * Note that this function makes use of the SCP unit's RNG hardware and does
 * NOT preserve its configuration settings, or the contents of the affected
 * GPRs.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre SCP lockdown must be disabled.
 *
 * @retval E_SUCCESS        if access was recovered successfully.
 * @retval E_SCP_GENERIC    if an unexpected error occurred.
 */
static error_t
s_scp_Recover_Insecure(void)
{
    //
    // The only way an insecure context can recover access to an SCP GPR
    // previously locked by a secure context is by loading data of some kind
    // into it, which will overwrite the GPR's previous contents and reset its
    // associated ACL to a fully-open state.
    //
    // Unfortunately, declaring a temporary buffer to load data from is out of
    // the question here as we have no means of obtaining said buffer's
    // physical address in order to initiate an SCPDMA transfer. That leaves
    // the "rand" instruction as the only other practical means of getting
    // insecure data into the GPRs.
    //
    // Of course, for even this to work, we need to already have insecure-
    // writable (Wi) access to the target GPRs or we'll trigger a write-
    // violation interrupt, hence the pre-condition listed in the function
    // description.
    //
    // As of SCP RNG v2.0, the RNG hardware should be usable "out of the box",
    // so we don't bother with any configuration here (since the actual entropy
    // of the values we produce is irrelevant).
    //

    // Ensure that the RNG hardware is enabled.
    scp_pvt_Enable_Rand();

    //
    // Load a true-random number into register-index zero. This will
    // overwrite any (potentially sensitive) prior contents and unlock the
    // register's associated ACL to a fully-open state.
    //
    scp_rand(SCP_REGISTER_INDEX_0);

    //
    // Copy the true-random number from register-index zero into the remaining
    // GPRs, thus effectively copying its fully-open ACL to each of them as
    // well.
    //
    scp_mov(SCP_REGISTER_INDEX_0, SCP_REGISTER_INDEX_1);
    scp_mov(SCP_REGISTER_INDEX_0, SCP_REGISTER_INDEX_2);
    scp_mov(SCP_REGISTER_INDEX_0, SCP_REGISTER_INDEX_3);
    scp_mov(SCP_REGISTER_INDEX_0, SCP_REGISTER_INDEX_4);
    scp_mov(SCP_REGISTER_INDEX_0, SCP_REGISTER_INDEX_5);
    scp_mov(SCP_REGISTER_INDEX_0, SCP_REGISTER_INDEX_6);
    scp_mov(SCP_REGISTER_INDEX_0, SCP_REGISTER_INDEX_7);

    // Wait for completion.
    scp_wait_dma();

    // Disable the RNG hardware.
    scp_pvt_Disable_Rand();

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

    // Registers unlocked successfully.
    return E_SUCCESS;
}

/**
 * @brief Checks for pending SCP interrupts.
 *
 * @retval true  if there are interrupts pending.
 * @retval false if there are no interrupts pending.
 *
 * @note This function only checks for interrupts that would represent
 *       genuine error conditions in the context of initialization or
 *       shutdown and ignores all others.
 */
static bool
s_scp_Has_Interrupt(void)
{
    uint32_t reg;

    // Read interrupt status.
    reg = local_read(NV_PRGNLCL_SCP_INTR);

    // ACL violations and security violations are always relevant.
    if (FLD_TEST_DRF(_PRGNLCL_SCP, _INTR, _ACL_VIO,      _PENDING, reg) ||
        FLD_TEST_DRF(_PRGNLCL_SCP, _INTR, _SECURITY_VIO, _PENDING, reg))
    {
        return true;
    }

    // Only certain command errors are relevant so investigate further.
    if (FLD_TEST_DRF(_PRGNLCL_SCP, _INTR, _CMD_ERROR, _PENDING, reg))
    {
        // Obtain error details.
        reg = local_read(NV_PRGNLCL_SCP_CMD_ERROR);

        // Check for relevant errors.
        if (FLD_TEST_DRF(_PRGNLCL_SCP, _CMD_ERROR, _UNDEFINED,
                _TRUE, reg) ||
            FLD_TEST_DRF(_PRGNLCL_SCP, _CMD_ERROR, _SEQ_EMPTY,
                _TRUE, reg) ||
            FLD_TEST_DRF(_PRGNLCL_SCP, _CMD_ERROR, _SEQ_OVERFLOW,
                _TRUE, reg) ||
            FLD_TEST_DRF(_PRGNLCL_SCP, _CMD_ERROR, _SIG_INSECURE,
                _TRUE, reg) ||
            FLD_TEST_DRF(_PRGNLCL_SCP, _CMD_ERROR, _CHMOD_INSECURE,
                _TRUE, reg) ||
            FLD_TEST_DRF(_PRGNLCL_SCP, _CMD_ERROR, _SECRET_INSECURE,
                _TRUE, reg) ||
            FLD_TEST_DRF(_PRGNLCL_SCP, _CMD_ERROR, _SEQ_INST_WHILE_LOADING,
                _TRUE, reg))
        {
            return true;
        }
    }

    // No relevant pending interrupts.
    return false;
}

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

/**
 * @brief Checks the SCP unit for activity.
 *
 * Checks whether the SCP unit is currently active. Does not check for pending
 * activity (i.e. CCI transfers queued while processing is disabled).
 *
 * @retval true  if the SCP unit is busy.
 * @retval false if the SCP unit is idle.
 */
static inline bool
s_scp_Is_Active(void)
{
    return FLD_TEST_DRF(_PRGNLCL_SCP, _STATUS, _SCP_ACTIVE, _TRUE,
                        local_read(NV_PRGNLCL_SCP_STATUS));
}

/**
 * @brief Checks whether the host engine contains an SCP unit.
 *
 * @retval true  if the SCP unit is present.
 * @retval false if the SCP unit is absent.
 */
static inline bool
s_scp_Is_Present(void)
{
    return FLD_TEST_DRF(_PRGNLCL_FALCON, _HWCFG2, _SCP, _ENABLE,
                        local_read(NV_PRGNLCL_FALCON_HWCFG2));
}

/**
 * @brief Checks whether the host engine is able to operate in a secure context.
 *
 * @retval true  if the host engine is capable of operating in a secure context.
 * @retval false if the host engine is not capable of operating in a secure
 *               context.
 *
 * @note To check whether the host engine is currently operating in a secure
 *       context, use scp_pvt_Is_Secure() instead.
 */
static inline bool
s_scp_Is_Secure_Capable(void)
{
    return FLD_TEST_DRF64(_RISCV_CSR, _XSPM, _XSECM, _SEC,
                          csr_read(NV_RISCV_CSR_XSPM));
}

/**
 * @brief Clears any pending SCP interrupts.
 */
static inline void
s_scp_Clear_Interrupts(void)
{
    // Clear the main interrupt register first.
    local_write(NV_PRGNLCL_SCP_INTR,
        DRF_DEF(_PRGNLCL_SCP, _INTR, _RAND_READY,           _RESET) |
        DRF_DEF(_PRGNLCL_SCP, _INTR, _ACL_VIO,              _RESET) |
        DRF_DEF(_PRGNLCL_SCP, _INTR, _SECURITY_VIO,         _RESET) |
        DRF_DEF(_PRGNLCL_SCP, _INTR, _CMD_ERROR,            _RESET) |
        DRF_DEF(_PRGNLCL_SCP, _INTR, _STEP,                 _RESET) |
        DRF_DEF(_PRGNLCL_SCP, _INTR, _RNDHOLDOFF_REQUESTED, _RESET) |
        DRF_DEF(_PRGNLCL_SCP, _INTR, _STALL_TIMEOUT,        _RESET));

    // Clear any ACL violations.
    local_write(NV_PRGNLCL_SCP_ACL_VIO,
        DRF_DEF(_PRGNLCL_SCP, _ACL_VIO, _CHMOD_RELAX_VIO, _CLEAR) |
        DRF_DEF(_PRGNLCL_SCP, _ACL_VIO, _FETCH_VIO,       _CLEAR) |
        DRF_DEF(_PRGNLCL_SCP, _ACL_VIO, _WR_VIO,          _CLEAR));

    // Can't clear security violations.

    // Clear any command errors.
    local_write(NV_PRGNLCL_SCP_CMD_ERROR,
        DRF_DEF(_PRGNLCL_SCP, _CMD_ERROR, _UNDEFINED,              _CLEAR) |
        DRF_DEF(_PRGNLCL_SCP, _CMD_ERROR, _SEQ_EMPTY,              _CLEAR) |
        DRF_DEF(_PRGNLCL_SCP, _CMD_ERROR, _SEQ_OVERFLOW,           _CLEAR) |
        DRF_DEF(_PRGNLCL_SCP, _CMD_ERROR, _SEQ_INST_WHILE_LOADING, _CLEAR) |
        DRF_DEF(_PRGNLCL_SCP, _CMD_ERROR, _SIG_INSECURE,           _CLEAR) |
        DRF_DEF(_PRGNLCL_SCP, _CMD_ERROR, _SIG_INVALID,            _CLEAR) |
        DRF_DEF(_PRGNLCL_SCP, _CMD_ERROR, _CHMOD_INSECURE,         _CLEAR) |
        DRF_DEF(_PRGNLCL_SCP, _CMD_ERROR, _SECRET_INSECURE,        _CLEAR));
}

/**
 * @brief Disables all SCP interrupts.
 */
static inline void
s_scp_Disable_Interrupts(void)
{
    // Disable all interrupts.
    local_write(NV_PRGNLCL_SCP_INTR_EN,
        DRF_DEF(_PRGNLCL_SCP, _INTR_EN, _RAND_READY,            _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _INTR_EN, _ACL_VIO,               _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _INTR_EN, _SECURITY_VIO,          _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _INTR_EN, _CMD_ERROR,             _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _INTR_EN, _STEP,                  _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _INTR_EN, _RNDHOLDOFF_REQUESTED,  _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _INTR_EN, _STALL_TIMEOUT,         _DISABLED));
}

/**
 * @brief Enables a critical subset of SCP interrupts.
 */
static inline void
s_scp_Enable_Interrupts(void)
{
    // Enable only critical interrupts that shouldn't be ignored.
    local_write(NV_PRGNLCL_SCP_INTR_EN,
        DRF_DEF(_PRGNLCL_SCP, _INTR_EN, _RAND_READY,            _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _INTR_EN, _ACL_VIO,               _ENABLED)  |
        DRF_DEF(_PRGNLCL_SCP, _INTR_EN, _SECURITY_VIO,          _ENABLED)  |
        DRF_DEF(_PRGNLCL_SCP, _INTR_EN, _CMD_ERROR,             _ENABLED)  |
        DRF_DEF(_PRGNLCL_SCP, _INTR_EN, _STEP,                  _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _INTR_EN, _RNDHOLDOFF_REQUESTED,  _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _INTR_EN, _STALL_TIMEOUT,         _DISABLED));
}

/**
 * @brief Disables all processing of SCP commands.
 */
static inline void
s_scp_Disable_Processing(void)
{
    local_write(NV_PRGNLCL_SCP_CTL0,
        DRF_DEF(_PRGNLCL_SCP, _CTL0, _CTL_EN,            _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _CTL0, _SEQ_EN,            _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _CTL0, _SF_CMD_IFACE_EN,   _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _CTL0, _SF_PUSH_IFACE_EN,  _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _CTL0, _SF_FETCH_IFACE_EN, _DISABLED));
}

/**
 * @brief Enables processing of SCP commands.
 */
static inline void
s_scp_Enable_Processing(void)
{
    local_write(NV_PRGNLCL_SCP_CTL0,
        DRF_DEF(_PRGNLCL_SCP, _CTL0, _CTL_EN,            _ENABLED) |
        DRF_DEF(_PRGNLCL_SCP, _CTL0, _SEQ_EN,            _ENABLED) |
        DRF_DEF(_PRGNLCL_SCP, _CTL0, _SF_CMD_IFACE_EN,   _ENABLED) |
        DRF_DEF(_PRGNLCL_SCP, _CTL0, _SF_PUSH_IFACE_EN,  _ENABLED) |
        DRF_DEF(_PRGNLCL_SCP, _CTL0, _SF_FETCH_IFACE_EN, _ENABLED));
}

/**
 * @brief Resets the SCP hardware to its default configuration settings.
 *
 * These values have been confirmed by the hardware team.
 */
static inline void
s_scp_Reset_Configuration(void)
{
    //
    // Casting to uint32_t as these constants are technically signed and will
    // produce a warning otherwise. There should be no risk of overflow here
    // since these constants are reasonably assumed to fit within the 32-bit
    // CFG0 register they're defined for.
    //
    INLINE_RFD(CERTC, DEVIATE, INT31_C, "Bug: TODO, DR: TODO - perceived sign of bitmap is irrelevant for correctly initializing the register.")
    local_write(NV_PRGNLCL_SCP_CFG0, (uint32_t)
        DRF_DEF(_PRGNLCL_SCP, _CFG0, _STALL_TIMEOUT, _INIT)   |
        DRF_DEF(_PRGNLCL_SCP, _CFG0, _CARRY_CHAIN,   _64)     |
        DRF_DEF(_PRGNLCL_SCP, _CFG0, _CMD_FLUSH,     _FALSE)  |
        DRF_DEF(_PRGNLCL_SCP, _CFG0, _AES_ENDIAN,    _BIG)    |
        DRF_DEF(_PRGNLCL_SCP, _CFG0, _PUSH_ENDIAN,   _LITTLE) |
        DRF_DEF(_PRGNLCL_SCP, _CFG0, _FETCH_ENDIAN,  _LITTLE) |
        DRF_DEF(_PRGNLCL_SCP, _CFG0, _CMAC_ENDIAN,   _BIG)    |
        DRF_DEF(_PRGNLCL_SCP, _CFG0, _ADD_ENDIAN,    _BIG));
}

/**
 * @brief Resets the SCP pipeline and sequencers. Also disables RNG.
 *
 * @pre Must be called from a secure context if SCP lockdown is enabled.
 */
static inline void
s_scp_Reset_Controls(void)
{
    uint32_t reg;

    //
    // Disable RNG and request pipe and sequencer resets. We also disable some
    // debug-only features here that must not be active in production.
    //
    local_write(NV_PRGNLCL_SCP_CTL1,
        DRF_DEF(_PRGNLCL_SCP, _CTL1, _SF_PUSH_BYPASS,   _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _CTL1, _SF_FETCH_BYPASS,  _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _CTL1, _SF_FETCH_AS_ZERO, _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _CTL1, _RNG_EN,           _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _CTL1, _RNG_FAKE_MODE,    _DISABLED) |
        DRF_DEF(_PRGNLCL_SCP, _CTL1, _PIPE_RESET,       _TASK)     |
        DRF_DEF(_PRGNLCL_SCP, _CTL1, _SEQ_CLEAR,        _TASK));

    // Wait for the pipe and sequencer resets to complete.
    do
    {
        reg = local_read(NV_PRGNLCL_SCP_CTL1);
    } while (FLD_TEST_DRF(_PRGNLCL_SCP, _CTL1, _PIPE_RESET, _PENDING, reg) ||
             FLD_TEST_DRF(_PRGNLCL_SCP, _CTL1, _SEQ_CLEAR,  _PENDING, reg));
}

/**
 * @brief Resets the source of secret-index 63 per hardware recommendation.
 */
static inline void
s_scp_Reset_Secret_63(void)
{
    local_write(NV_PRGNLCL_SCP_SECRET63_CTL,
        DRF_DEF(_PRGNLCL_SCP, _SECRET63_CTL, _SEL, _PKEY));
}

/**
 * @brief Resets the driver's internal state.
 */
static inline void
s_scp_Reset_State(void)
{
    g_scp_state.b_initialized         = false;
    g_scp_state.b_shutdown            = false;
    g_scp_state.b_direct_mode_active  = false;
    g_scp_state.b_rand_configured     = false;
    g_scp_state.b_shortcut_configured = false;
    g_scp_state.dma_index             = UINT8_MAX;
}
