/**
 * @file        scp_crypt.c
 * @brief       Cryptographic SCP features.
 *
 * 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_crypt.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 uint8_t, uintptr_t, etc.

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

/* Hardware headers */

/* Late FSP headers */
#include <error/common-errors.h>        // for error_t, common error codes
#include <fbdma/memutils.h>             // for memutils_mem_has_overlap
#include <misc/nvmisc_drf.h>            // for DRF macros (for scp_direct.h)

/* 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_crypt.h>              // for scp_key_desc_t, etc.
#include <scp/scp_direct.h>             // for SCP primitives
#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__ERROR__COMMON_ERRORS_H)
HEADER_CHECK(FSP__FBDMA__MEMUTILS_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_CRYPT_H)
HEADER_CHECK(FSP__SCP__SCP_DIRECT_H)
HEADER_CHECK(FSP__SCP__SCP_PRIVATE_H)


/**
 * @brief The chmod value for a secure-keyable-only register ACL.
 *
 * Intended to protect encryption/decryption keys in secure contexts.
 */
#define SCP_REGISTER_ACL_KEY 0x1U


//
// Prototypes for local helper functions.
// See definitions for details.
//
static error_t s_scp_Crypt(const scp_key_desc_t * p_key_desc,
                           scp_cipher_mode_t      cipher_mode,
                           uintptr_t              source_pa,
                           uintptr_t              dest_pa,
                           size_t                 size,
                           uintptr_t              context_pa,
                           bool                   b_decrypt);

static error_t s_scp_Cmac(const scp_key_desc_t * p_key_desc,
                          scp_cmac_mode_t        cmac_mode,
                          uintptr_t              source_pa,
                          size_t                 size,
                          uintptr_t              context_pa);

static error_t s_scp_Load_Key(const scp_key_desc_t * p_key_desc);

static error_t s_scp_Load_Buffer_Key(uintptr_t key_pa);

static error_t s_scp_Load_Register_Key(scp_register_index_t reg_index);

static error_t s_scp_Load_Ecb_Key(uint8_t secret_index, uintptr_t salt_pa);

static error_t s_scp_Load_Cbc_Key(uint8_t secret_index, uintptr_t salt_pa,
                                  uintptr_t iv_pa);

static error_t s_scp_Load_Cmac_Key(uint8_t      secret_index,
                                   uintptr_t    message_pa,
                                   size_t       size,
                                   bool         b_padded);

static error_t s_scp_Load_Crypt_Sequence(scp_cipher_mode_t  cipher_mode,
                                         bool               b_decrypt);

static error_t s_scp_Load_Ecb_Sequence(bool b_decrypt);

static error_t s_scp_Load_Cbc_Sequence(bool b_decrypt);

static error_t s_scp_Load_Ctr_Sequence(void);

static error_t s_scp_Load_Hash_Sequence(void);

static error_t s_scp_Load_Cmac_Sequence(void);

static error_t s_scp_Complete_Cmac(uintptr_t final_pa, bool b_padded);


/**
 * @copydoc scp_Encrypt
 */
error_t
scp_Encrypt
(
    const scp_key_desc_t *  p_key_desc,
    scp_cipher_mode_t       cipher_mode,
    uintptr_t               source_pa,
    uintptr_t               dest_pa,
    size_t                  size,
    uintptr_t               context_pa
)
{
    return s_scp_Crypt(p_key_desc, cipher_mode, source_pa, dest_pa, size,
                       context_pa, false);
}

/**
 * @copydoc scp_Decrypt
 */
error_t
scp_Decrypt
(
    const scp_key_desc_t *  p_key_desc,
    scp_cipher_mode_t       cipher_mode,
    uintptr_t               source_pa,
    uintptr_t               dest_pa,
    size_t                  size,
    uintptr_t               context_pa
)
{
    return s_scp_Crypt(p_key_desc, cipher_mode, source_pa, dest_pa, size,
                       context_pa, true);
}

/**
 * @copydoc scp_Dmhash
 */
error_t
scp_Dmhash
(
    uintptr_t   source_pa,
    size_t      size,
    uintptr_t   dest_pa,
    uintptr_t   init_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;
    }

    // Verify source buffer.
    status = scp_pvt_Validate_Buffer(source_pa, size, false);
    if (status != E_SUCCESS)
    {
        goto cleanup;
    }

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

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

    // Verify initial hash buffer.
    status = scp_pvt_Validate_Buffer(init_pa, SCP_BLOCK_SIZE, false);
    if (status != E_SUCCESS)
    {
        goto cleanup;
    }

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

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

    // Run the input data through the sequence we loaded above.
    status = scp_pvt_Run_Sequence(source_pa, (uintptr_t)NULL, size);
    if (status != E_SUCCESS)
    {
        goto cleanup;
    }

    // Read the final hash value out of register-index one.
    status = scp_pvt_Store_Buffer(SCP_REGISTER_INDEX_1, dest_pa);
    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;
}

/**
 * @copydoc scp_Cmac
 */
error_t
scp_Cmac
(
    const scp_key_desc_t *  p_key_desc,
    scp_cmac_mode_t         cmac_mode,
    uintptr_t               source_pa,
    size_t                  size,
    uintptr_t               dest_pa,
    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;
    }

    // Verify destination buffer (other parameters validated by s_scp_Cmac()).
    status = scp_pvt_Validate_Buffer(dest_pa, SCP_BLOCK_SIZE, true);
    if (status != E_SUCCESS)
    {
        goto cleanup;
    }

    // Perform CMAC computation.
    status = s_scp_Cmac(p_key_desc, cmac_mode, source_pa, size, context_pa);
    if (status != E_SUCCESS)
    {
        goto cleanup;
    }

    // Read the final CMAC value out of register-index one.
    status = scp_pvt_Store_Buffer(SCP_REGISTER_INDEX_1, dest_pa);
    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 Encrypts or decrypts an arbitary buffer of data using AES-128.
 *
 * Encrypts or decrypts the first size bytes of data from source_pa using the
 * key described by p_key_desc and writes the result to dest_pa. Uses an AES-128
 * block cipher operating in the mode indicated by cipher_mode as the encryption
 * or decryption algorithm. If applicable, SCP_BLOCK_SIZE bytes from context_pa
 * are used as the initial contextual input to the cipher. Performs a
 * decryption operation if b_decrypt is true and an encryption operation
 * otherwise.
 *
 * Should p_key_desc describe a derived key, the s_scp_Crypt() function will
 * perform the indicated key-derivation operation automatically. See the
 * relevant SCP_KEY_* macros for details.
 *
 * Note that it is permissible for source_pa and dest_pa to reference the same
 * memory locations (i.e. to facilitate encrypting or decrypting data in-
 * place). However, the use of buffers that only partially overlap is
 * prohibited and will result in an error.
 *
 * In the event that dest_pa refers to an external memory location, the
 * application must ensure that it has first called scp_Configure_Shortcut() (or
 * used the SCP_EXTERNAL_DEST() helper macro) to configure the shortcut-DMA
 * path accordingly.
 *
 * @param[in]  p_key_desc   A pointer to the key-descriptor to be used for the
 *                          encryption or decryption operation. Applications
 *                          should acquire this parameter through one of the
 *                          provided SCP_KEY_* macros.
 *
 * @param[in]  cipher_mode  The block-cipher mode of operation to use.
 *
 * @param[in]  source_pa    The physical address of the input buffer to be
 *                          encrypted or decrypted. Must be aligned to
 *                          SCP_BUFFER_ALIGNMENT 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 encrypted or decrypted data is to be written.
 *                          Must be aligned to SCP_BUFFER_ALIGNMENT and have at
 *                          least size bytes of capacity. Permitted to overlap
 *                          with source_pa only if exactly equal. Supports IMEM,
 *                          DMEM, and external locations.
 *
 * @param[in]  size         The number of bytes to process from source_pa. Must
 *                          be a nonzero multiple of SCP_BLOCK_SIZE.
 *
 * @param[in]  context_pa   The physical address of the input buffer containing
 *                          the initialization vector (CBC) or initial counter
 *                          value with nonce (CTR) to be used for the
 *                          encryption or decryption operation. Must be aligned
 *                          to SCP_BUFFER_ALIGNMENT and contain at least
 *                          SCP_BLOCK_SIZE bytes. Required to be zero (NULL)
 *                          for ECB mode. Supports IMEM and DMEM locations
 *                          only.
 *
 * @param[in]  b_decrypt    A boolean value indicating whether to decrypt the
 *                          data from source_pa rather than encrypting it.
 *
 * @retval E_SUCCESS                    if data was encrypted or decrypted
 *                                      successfully.
 *
 * @retval E_SCP_NOT_READY              if the SCP driver has not been
 *                                      initialized.
 *
 * @retval E_SCP_INVALID_OBJECT         if p_key_desc does not point to a valid
 *                                      key-descriptor.
 *
 * @retval E_SCP_INVALID_OBJECT_BUFFER  if p_key_desc was constructed with a
 *                                      zero (NULL) or incorrectly-aligned
 *                                      address, or a pointer to an unsupported
 *                                      memory location (e.g. EMEM), or a buffer
 *                                      that overflows the memory region in
 *                                      which it resides, or a buffer of invalid
 *                                      size.
 *
 * @retval E_SCP_INVALID_INDEX          if p_key_desc was constructed with an
 *                                      invalid register or hardware-secret
 *                                      index, or references secret-index zero.
 *
 * @retval E_PERM                       if p_key_desc was constructed with a
 *                                      hardware-secret index that the
 *                                      application does not have permission to
 *                                      use.
 *
 * @retval E_SCP_INVALID_POINTER        if one or more of p_key_desc, source_pa,
 *                                      or dest_pa is zero/NULL, or context_pa
 *                                      is zero (NULL) and cipher_mode is not
 *                                      SCP_CIPHER_MODE_ECB.
 *
 * @retval E_SCP_INVALID_ADDRESS        if one or more of source_pa, dest_pa, or
 *                                      context_pa is incorrectly aligned, or
 *                                      the buffers referenced by source_pa and
 *                                      dest_pa overlap without being equal.
 *
 * @retval E_SCP_INVALID_BASE           if one or more of source_pa, dest_pa, or
 *                                      context_pa points to an unsupported
 *                                      memory region (e.g. EMEM).
 *
 * @retval E_SCP_INVALID_REQUEST        if size is zero or is not a multiple of
 *                                      SCP_BLOCK_SIZE.
 *
 * @retval E_SCP_INVALID_OPERATION      if cipher_mode is not a valid
 *                                      scp_cipher_mode_t value, or cipher_mode
 *                                      is SCP_CIPHER_MODE_ECB and context_pa is
 *                                      nonzero (non-NULL).
 *
 * @retval E_SCP_FEATURE_NOT_ENABLED    if dest_pa is located in external memory
 *                                      but the application does not have
 *                                      shortcut-DMA support enabled.
 *
 * @retval E_SCP_INVALID_DMA_SPECIFIER  if dest_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 one or more of the buffers referenced
 *                                      by source_pa, dest_pa, or context_pa
 *                                      overflows the memory region in which it
 *                                      resides.
 *
 * @retval E_SCP_INSUFFICIENT_RESOURCES if the application lacks access to
 *                                      secret-index zero (required in secure
 *                                      contexts).
 *
 * @retval E_INVALID_STATE              if calls to this function are prohibited
 *                                      because the SCP driver is operating in
 *                                      direct mode.
 *
 * @retval E_SCP_INVALID_LOCK_STATE     if access to certain necessary registers
 *                                      is being blocked by SCP lockdown.
 *
 * @retval E_FAULT                      if an IO-PMP fault was detected.
 *
 * @retval E_SCP_GENERIC                if an unexpected error occurred.
 */
static error_t
s_scp_Crypt
(
    const scp_key_desc_t *  p_key_desc,
    scp_cipher_mode_t       cipher_mode,
    uintptr_t               source_pa,
    uintptr_t               dest_pa,
    size_t                  size,
    uintptr_t               context_pa,
    bool                    b_decrypt
)
{
    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;
    }

    // Verify source buffer.
    status = scp_pvt_Validate_Buffer(source_pa, size, false);
    if (status != E_SUCCESS)
    {
        goto cleanup;
    }

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

    // Check for partial overlap (exact aliasing is fine).
    if ((source_pa != dest_pa) &&
        memutils_mem_has_overlap(source_pa, size, dest_pa, size))
    {
        status = E_SCP_INVALID_ADDRESS;
        goto cleanup;
    }

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

    // Verify mode of operation.
    switch (cipher_mode)
    {
        case SCP_CIPHER_MODE_ECB:
            // No context in ECB mode.
            if (context_pa != (uintptr_t)NULL)
            {
                status = E_SCP_INVALID_OPERATION;
                goto cleanup;
            }
            break;

        case SCP_CIPHER_MODE_CBC:
        case SCP_CIPHER_MODE_CTR:
            // Verify context buffer for other modes.
            status = scp_pvt_Validate_Buffer(context_pa, SCP_BLOCK_SIZE, false);
            if (status != E_SUCCESS)
            {
                goto cleanup;
            }
            break;

        default:
            // Not a recognized block-cipher mode.
            status = E_SCP_INVALID_OPERATION;
            goto cleanup;
    }

    // Verify key-descriptor, load key into register-index zero, and update Ku.
    status = s_scp_Load_Key(p_key_desc);
    if (status != E_SUCCESS)
    {
        goto cleanup;
    }

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

    // Load sequencer zero with the instructions for the selected cipher mode.
    status = s_scp_Load_Crypt_Sequence(cipher_mode, b_decrypt);
    if (status != E_SUCCESS)
    {
        goto cleanup;
    }

    // Run the input data through the sequence we loaded above.
    status = scp_pvt_Run_Sequence(source_pa, 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 Computes the CMAC of an arbitrary buffer of data.
 *
 * Computes the CMAC of the first size bytes of data from source_pa using the
 * key described by p_key_desc and writes the result to SCP register-index one.
 * Uses an AES-128 block cipher for the operation. If provided, SCP_BLOCK_SIZE
 * bytes from context_pa are used as the initial contextual input (IV) for the
 * computation.
 *
 * When cmac_mode is SCP_CMAC_MODE_PARTIAL, s_scp_Cmac() skips the final step of
 * the CMAC computation, thus allowing the result to be chained into a future
 * call via context_pa. The full CMAC result can then be obtained by passing
 * SCP_CMAC_MODE_COMPLETE, as is done for singular, unchained calls.
 *
 * Notice that it is the application's responsibility to pad the source message
 * if needed (by appending 0b10...0 per the CMAC specification) and to signal
 * that padding has occurred by setting cmac_mode to SCP_CMAC_MODE_PADDED in
 * place of SCP_CMAC_MODE_COMPLETE.
 *
 * Should p_key_desc describe a derived key, the s_scp_Cmac() function will
 * perform the indicated key-derivation operation automatically. See the
 * relevant SCP_KEY_* macros for details.
 *
 * Note that this function uses register-indices zero through three for
 * intermediate storage and also loads instructions into sequencer zero. The
 * caller is responsible for clearing these resources before returning to the
 * client application.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre The SCP driver must not be operating in direct mode.
 * @pre Must be called from a secure context if SCP lockdown is enabled.
 *
 * @param[in]  p_key_desc   A pointer to the key-descriptor to be used for the
 *                          CMAC computation. Applications should acquire this
 *                          parameter through one of the provided SCP_KEY_*
 *                          macros.
 *
 * @param[in]  cmac_mode    The CMAC mode of operation to use. See the
 *                          documentation for scp_cmac_mode_t for a list of
 *                          supported values.
 *
 * @param[in]  source_pa    The physical address of the input buffer for which
 *                          the CMAC will be computed. Must be aligned to
 *                          SCP_BUFFER_ALIGNMENT and contain at least size
 *                          bytes. Supports IMEM and DMEM locations only.
 *
 * @param[in]  size         The number of bytes to process from source_pa. Must
 *                          be a nonzero multiple of SCP_BLOCK_SIZE.
 *
 * @param[in]  context_pa   The physical address of the input buffer containing
 *                          the result of a prior partial CMAC computation, if
 *                          applicable. Must be aligned to SCP_BUFFER_ALIGNMENT
 *                          and contain at least SCP_BLOCK_SIZE bytes. Should
 *                          be zero (NULL) or should point to an all-zero
 *                          buffer if not continuing a prior computation.
 *                          Supports IMEM and DMEM locations only.
 *
 * @retval E_SUCCESS                    if CMAC was computed successfully.
 *
 * @retval E_SCP_INVALID_OBJECT         if p_key_desc does not point to a valid
 *                                      key-descriptor.
 *
 * @retval E_SCP_INVALID_OBJECT_BUFFER  if p_key_desc was constructed with a
 *                                      zero (NULL) or incorrectly-aligned
 *                                      address, or a pointer to an unsupported
 *                                      memory location (e.g. EMEM), or a buffer
 *                                      that overflows the memory region in
 *                                      which it resides, or a buffer of invalid
 *                                      size.
 *
 * @retval E_SCP_INVALID_INDEX          if p_key_desc was constructed with an
 *                                      invalid register or hardware-secret
 *                                      index, or references secret-index zero.
 *
 * @retval E_PERM                       if p_key_desc was constructed with a
 *                                      hardware-secret index that the
 *                                      application does not have permission to
 *                                      use.
 *
 * @retval E_SCP_INVALID_POINTER        if one or both of p_key_desc or
 *                                      source_pa is zero/NULL.
 *
 * @retval E_SCP_INVALID_ADDRESS        if one or both of source_pa or
 *                                      context_pa is incorrectly aligned, or
 *                                      the buffers referenced by source_pa and
 *                                      context_pa overlap.
 *
 * @retval E_SCP_INVALID_BASE           if one or both of source_pa or
 *                                      context_pa points to an unsupported
 *                                      memory region (e.g. EMEM).
 *
 * @retval E_SCP_INVALID_REQUEST        if size is zero or is not a multiple of
 *                                      SCP_BLOCK_SIZE.
 *
 * @retval E_SCP_INVALID_OPERATION      if cmac_mode is not a valid
 *                                      scp_cmac_mode_t value.
 *
 * @retval E_SCP_OUT_OF_RANGE           if one or both of the buffers referenced
 *                                      by source_pa or context_pa overflows the
 *                                      memory region in which it resides.
 *
 * @retval E_SCP_INSUFFICIENT_RESOURCES if the application lacks access to
 *                                      secret-index zero (required in secure
 *                                      contexts).
 *
 * @retval E_FAULT                      if an IO-PMP fault was detected.
 *
 * @retval E_SCP_GENERIC                if an unexpected error occurred.
 */
static error_t
s_scp_Cmac
(
    const scp_key_desc_t *  p_key_desc,
    scp_cmac_mode_t         cmac_mode,
    uintptr_t               source_pa,
    size_t                  size,
    uintptr_t               context_pa
)
{
    error_t     status      = E_SUCCESS;
    size_t      batch_size  = size;
    bool        b_padded    = false;

    // Verify source buffer.
    status = scp_pvt_Validate_Buffer(source_pa, size, false);
    if (status != E_SUCCESS)
    {
        return status;
    }

    // Verify byte count.
    if ((size == 0U) || (size % SCP_BLOCK_SIZE != 0U))
    {
        return E_SCP_INVALID_REQUEST;
    }

    // Verify context buffer, if provided.
    if (context_pa != (uintptr_t)NULL)
    {
        status = scp_pvt_Validate_Buffer(context_pa, SCP_BLOCK_SIZE, false);
        if (status != E_SUCCESS)
        {
            return status;
        }

        // Check for overlap with source buffer.
        if (memutils_mem_has_overlap(source_pa, size, context_pa,
                SCP_BLOCK_SIZE))
        {
            return E_SCP_INVALID_ADDRESS;
        }
    }

    // Verify mode of operation.
    switch (cmac_mode)
    {
        case SCP_CMAC_MODE_PADDED:
            b_padded = true;
            // Fall-through.

        case SCP_CMAC_MODE_COMPLETE:
            //
            // The final block in non-partial messages requires special
            // handling, so exclude it from the inital batch.
            //
            batch_size -= SCP_BLOCK_SIZE;
            break;

        case SCP_CMAC_MODE_PARTIAL:
            // Nothing to do.
            break;

        default:
            // Not a recognized operating mode.
            return E_SCP_INVALID_OPERATION;
    }

    // Verify key-descriptor, load key into register-index zero, and update Ku.
    status = s_scp_Load_Key(p_key_desc);
    if (status != E_SUCCESS)
    {
        return status;
    }

    // Load initial context information if provided.
    if (context_pa != (uintptr_t)NULL)
    {
        status = scp_pvt_Load_Buffer(context_pa, SCP_REGISTER_INDEX_1);
        if (status != E_SUCCESS)
        {
            return status;
        }
    }
    // Otherwise, use zero.
    else
    {
        status = scp_pvt_Clear_Reg(SCP_REGISTER_INDEX_1);
        if (status != E_SUCCESS)
        {
            return status;
        }
    }

    // Load sequencer zero with the instructions for CMAC.
    status = s_scp_Load_Cmac_Sequence();
    if (status != E_SUCCESS)
    {
        return status;
    }

    // Run the input data through the sequence we loaded above.
    status = scp_pvt_Run_Sequence(source_pa, (uintptr_t)NULL, batch_size);
    if (status != E_SUCCESS)
    {
        return status;
    }

    // Handle the final block if needed.
    if (batch_size != size)
    {
        status = s_scp_Complete_Cmac((source_pa + batch_size), b_padded);
        if (status != E_SUCCESS)
        {
            return status;
        }
    }

    // Done.
    return status;
}

/**
 * @brief Validates a key-descriptor and then loads the key it describes to R0.
 *
 * Checks the provided key-descriptor for errors and then loads the key it
 * describes into register-index zero. Automatically performs any key-
 * derivation operations as required and updates the key register (Ku) to
 * point to the newly-loaded key.
 *
 * Note that this function may use register-indices one through three for
 * intermediate storage and may load instructions into sequencer zero.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre The SCP driver must not be operating in direct mode.
 * @pre For register-type keys, the source register must be writable and
 *      fetchable if called from an insecure context.
 *
 * @param[in]  p_key_desc   A pointer to the key-descriptor to be processed.
 *
 * @retval E_SUCCESS                    if key was loaded successfully.
 *
 * @retval E_SCP_INVALID_OBJECT         if p_key_desc does not point to a valid
 *                                      key-descriptor.
 *
 * @retval E_SCP_INVALID_OBJECT_BUFFER  if p_key_desc was constructed with a
 *                                      zero (NULL) or incorrectly-aligned
 *                                      address, or a pointer to an unsupported
 *                                      memory location (e.g. EMEM), or a buffer
 *                                      that overflows the memory region in
 *                                      which it resides, or a buffer of invalid
 *                                      size.
 *
 * @retval E_SCP_INVALID_INDEX          if p_key_desc was constructed with an
 *                                      invalid register or hardware-secret
 *                                      index, or references secret-index zero.
 *
 * @retval E_PERM                       if p_key_desc was constructed with a
 *                                      hardware-secret index that the
 *                                      application does not have permission to
 *                                      use.
 *
 * @retval E_SCP_INVALID_POINTER        if p_key_desc is NULL.
 *
 * @retval E_SCP_INSUFFICIENT_RESOURCES if the application lacks access to
 *                                      secret-index zero (required in secure
 *                                      contexts).
 *
 * @retval E_FAULT                      if an IO-PMP fault was detected.
 *
 * @retval E_SCP_GENERIC                if an unexpected error occurred.
 */
static error_t
s_scp_Load_Key
(
    const scp_key_desc_t * p_key_desc
)
{
    error_t status;

    // Check for valid pointer.
    if (p_key_desc == NULL)
    {
        return E_SCP_INVALID_POINTER;
    }

    // Load the requested key based on its type.
    switch (p_key_desc->key_type)
    {
        case SCP_KEY_TYPE_RAW_BUFFER:
            status = s_scp_Load_Buffer_Key(p_key_desc->raw_buffer_pa);
            break;

        case SCP_KEY_TYPE_RAW_REGISTER:
            status = s_scp_Load_Register_Key(p_key_desc->raw_register_index);
            break;

        case SCP_KEY_TYPE_DERIVED_ECB:
            status = s_scp_Load_Ecb_Key(p_key_desc->ecb_secret_index,
                                        p_key_desc->ecb_salt_pa);
            break;

        case SCP_KEY_TYPE_DERIVED_CBC:
            status = s_scp_Load_Cbc_Key(p_key_desc->cbc_secret_index,
                                        p_key_desc->cbc_salt_pa,
                                        p_key_desc->cbc_iv_pa);
            break;

        case SCP_KEY_TYPE_DERIVED_CMAC:
            status = s_scp_Load_Cmac_Key(p_key_desc->cmac_secret_index,
                                         p_key_desc->cmac_message_pa,
                                         p_key_desc->cmac_size,
                                         p_key_desc->cmac_padded);
            break;

        default:
            // Not a recognized key type.
            return E_SCP_INVALID_OBJECT;
    }

    // Report any errors to the caller.
    if (status != E_SUCCESS)
    {
        return status;
    }

    //
    // Restrict the key's associated ACL to the bare minimum required for
    // encryption and decryption operations.
    //
    if (scp_pvt_Is_Secure())
    {
        scp_chmod(SCP_REGISTER_ACL_KEY, SCP_REGISTER_INDEX_0);
    }

    // Update the key register to point to the newly-loaded key.
    scp_key(SCP_REGISTER_INDEX_0);

    // Wait for the above operations to complete.
    scp_wait_dma();

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

    // Key was loaded successfully.
    return E_SUCCESS;
}

/**
 * @brief Loads a raw key from the provided buffer into R0.
 *
 * Validates the buffer referenced by key_pa and then loads the first
 * SCP_KEY_SIZE bytes from it into register-index zero.
 *
 * @pre The SCP hardware must have been properly initialized.
 *
 * @param[in]  key_pa   The physical address of the input buffer containing the
 *                      raw key to be loaded.
 *
 * @retval E_SUCCESS                    if key was loaded successfully.
 *
 * @retval E_SCP_INVALID_OBJECT_BUFFER  if key_pa is zero (NULL) or incorrectly-
 *                                      aligned, or points to an unsupported
 *                                      memory location (e.g. EMEM), or
 *                                      references a buffer that overflows the
 *                                      memory region in which it resides.
 *
 * @retval E_FAULT                      if an IO-PMP fault was detected.
 *
 * @retval E_SCP_GENERIC                if an unexpected error occurred.
 */
static error_t
s_scp_Load_Buffer_Key
(
    uintptr_t key_pa
)
{
    // Validate the source buffer.
    if (scp_pvt_Validate_Buffer(key_pa, SCP_KEY_SIZE, false) != E_SUCCESS)
    {
        return E_SCP_INVALID_OBJECT_BUFFER;
    }

    // Load into register-index zero.
    return scp_pvt_Load_Buffer(key_pa, SCP_REGISTER_INDEX_0);
}

/**
 * @brief Loads a raw key from the indicated SCP GPR into R0.
 *
 * Copies the contents of the register specified by reg_index into register-
 * index zero. Clears the source register after copying.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre The source register must be writable and fetchable if called from an
 *      insecure context.
 *
 * @param[in]  reg_index    The index of the SCP register containing the raw
 *                          key to be loaded.
 *
 * @retval E_SUCCESS                    if key was loaded successfully.
 *
 * @retval E_SCP_INVALID_INDEX          if reg_index is not a valid
 *                                      scp_register_index_t value.
 *
 * @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.
 */
static error_t
s_scp_Load_Register_Key
(
    scp_register_index_t reg_index
)
{
    // Validate the register index.
    if (reg_index > SCP_REGISTER_INDEX_7)
    {
        return E_SCP_INVALID_INDEX;
    }

    // No-op if already in R0.
    if (reg_index == SCP_REGISTER_INDEX_0)
    {
        return E_SUCCESS;
    }

    // Copy into register-index zero.
    scp_mov(reg_index, SCP_REGISTER_INDEX_0);
    scp_wait_dma();

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

    // Clear the source register.
    return scp_pvt_Clear_Reg(reg_index);
}

/**
 * @brief Loads a key derived via AES-ECB of a provided salt value into R0.
 *
 * Performs AES-ECB encryption on the first SCP_BLOCK_SIZE bytes from salt_pa,
 * using the hardware-secret indicated by secret_index as the key, and stores
 * the result in register-index zero.
 *
 * Note that this function uses register-index one for intermediate values.
 *
 * @pre The SCP hardware must have been properly initialized.
 *
 * @param[in]  secret_index The index of the hardware-secret to be used in the
 *                          key-derivation process.
 *
 * @param[in]  salt_pa      The physical address of the input buffer containing
 *                          the salt value to be used in the key-derivation
 *                          process.
 *
 * @retval E_SUCCESS                    if key was loaded successfully.
 *
 * @retval E_SCP_INVALID_OBJECT_BUFFER  if salt_pa is zero (NULL) or
 *                                      incorrectly-aligned, or points to an
 *                                      unsupported memory location (e.g. EMEM),
 *                                      or references a buffer that overflows
 *                                      the memory region in which it resides.
 *
 * @retval E_SCP_INVALID_INDEX          if secret_index is not a valid SCP
 *                                      secret-index, or references secret-index
 *                                      zero.
 *
 * @retval E_PERM                       if secret_index references a hardware-
 *                                      secret that the application does not
 *                                      have permission to use.
 *
 * @retval E_SCP_INSUFFICIENT_RESOURCES if the application lacks access to
 *                                      secret-index zero (required in secure
 *                                      contexts).
 *
 * @retval E_FAULT                      if an IO-PMP fault was detected.
 *
 * @retval E_SCP_GENERIC                if an unexpected error occurred.
 */
static error_t
s_scp_Load_Ecb_Key
(
    uint8_t     secret_index,
    uintptr_t   salt_pa
)
{
    error_t status;

    // Verify context (hardware secrets cannot be used in insecure contexts).
    if (!scp_pvt_Is_Secure())
    {
        return E_PERM;
    }

    // Validate the secret index.
    // TODO(COREUCODES-1846): Reinstate check against SCP_SECRET_INDEX_CLEAR.
    if (secret_index >= SCP_SECRET_COUNT)
    {
        return E_SCP_INVALID_INDEX;
    }

    // Validate the salt buffer.
    if (scp_pvt_Validate_Buffer(salt_pa, SCP_BLOCK_SIZE, false) != E_SUCCESS)
    {
        return E_SCP_INVALID_OBJECT_BUFFER;
    }

    // Load the salt value into a temporary register.
    scp_load(salt_pa, SCP_REGISTER_INDEX_1);
    scp_wait_dma();

    // Load the hardware secret into the destination register.
    scp_secret(secret_index, SCP_REGISTER_INDEX_0);

    // Encrypt the salt with the hardware secret.
    scp_key(SCP_REGISTER_INDEX_0);
    scp_encrypt(SCP_REGISTER_INDEX_1, SCP_REGISTER_INDEX_0);

    // Wait for completion.
    scp_wait_dma();

    // Check for any failures.
    status = scp_check_dma(true);
    if ((status == E_PERM) || (status == E_FAULT))
    {
        // Bubble up permissions and IO-PMP errors.
        return status;
    }
    else if (status != E_SUCCESS)
    {
        // No other errors are expected.
        return E_SCP_GENERIC;
    }

    // Clear the temporary register.
    return scp_pvt_Clear_Reg(SCP_REGISTER_INDEX_1);
}

/**
 * @brief Loads a key derived via AES-CBC of a provided salt value into R0.
 *
 * Performs AES-CBC encryption on the first SCP_BLOCK_SIZE bytes from salt_pa,
 * using the hardware-secret indicated by secret_index as the key and the first
 * SCP_BLOCK_SIZE bytes from iv_pa as the initialization vector, and stores
 * the result in register-index zero.
 *
 * Note that this function uses register-index one for intermediate values.
 *
 * @pre The SCP hardware must have been properly initialized.
 *
 * @param[in]  secret_index The index of the hardware-secret to be used in the
 *                          key-derivation process.
 *
 * @param[in]  salt_pa      The physical address of the input buffer containing
 *                          the salt value to be used in the key-derivation
 *                          process.
 *
 * @param[in]  iv_pa        The physical address of the input buffer containing
 *                          the initialization vector to be used in the key-
 *                          derivation process.
 *
 * @retval E_SUCCESS                    if key was loaded successfully.
 *
 * @retval E_SCP_INVALID_OBJECT_BUFFER  if one or more of salt_pa or iv_pa is
 *                                      zero (NULL) or incorrectly-aligned, or
 *                                      points to an unsupported memory location
 *                                      (e.g. EMEM), or references a buffer that
 *                                      overflows the memory region in which it
 *                                      resides.
 *
 * @retval E_SCP_INVALID_INDEX          if secret_index is not a valid SCP
 *                                      secret-index, or references secret-index
 *                                      zero.
 *
 * @retval E_PERM                       if secret_index references a hardware-
 *                                      secret that the application does not
 *                                      have permission to use.
 *
 * @retval E_SCP_INSUFFICIENT_RESOURCES if the application lacks access to
 *                                      secret-index zero (required in secure
 *                                      contexts).
 *
 * @retval E_FAULT                      if an IO-PMP fault was detected.
 *
 * @retval E_SCP_GENERIC                if an unexpected error occurred.
 */
static error_t
s_scp_Load_Cbc_Key
(
    uint8_t     secret_index,
    uintptr_t   salt_pa,
    uintptr_t   iv_pa
)
{
    error_t status;

    // Verify context (hardware secrets cannot be used in insecure contexts).
    if (!scp_pvt_Is_Secure())
    {
        return E_PERM;
    }

    // Validate the secret index.
    // TODO(COREUCODES-1846): Reinstate check against SCP_SECRET_INDEX_CLEAR.
    if (secret_index >= SCP_SECRET_COUNT)
    {
        return E_SCP_INVALID_INDEX;
    }

    // Validate the salt buffer.
    if (scp_pvt_Validate_Buffer(salt_pa, SCP_BLOCK_SIZE, false) != E_SUCCESS)
    {
        return E_SCP_INVALID_OBJECT_BUFFER;
    }

    // Validate the IV buffer.
    if (scp_pvt_Validate_Buffer(iv_pa, SCP_BLOCK_SIZE, false) != E_SUCCESS)
    {
        return E_SCP_INVALID_OBJECT_BUFFER;
    }

    // Load the IV and salt values into registers.
    scp_load(iv_pa, SCP_REGISTER_INDEX_0);
    scp_load(salt_pa, SCP_REGISTER_INDEX_1);
    scp_wait_dma();

    // XOR the IV and salt as the first step of CBC encryption.
    scp_xor(SCP_REGISTER_INDEX_0, SCP_REGISTER_INDEX_1);

    // Load the hardware secret into the destination register.
    scp_secret(secret_index, SCP_REGISTER_INDEX_0);

    // Encrypt the XORed salt with the hardware secret.
    scp_key(SCP_REGISTER_INDEX_0);
    scp_encrypt(SCP_REGISTER_INDEX_1, SCP_REGISTER_INDEX_0);

    // Wait for completion.
    scp_wait_dma();

    // Check for any failures.
    status = scp_check_dma(true);
    if ((status == E_PERM) || (status == E_FAULT))
    {
        // Bubble up permissions and IO-PMP errors.
        return status;
    }
    else if (status != E_SUCCESS)
    {
        // No other errors are expected.
        return E_SCP_GENERIC;
    }

    // Clear the temporary register.
    return scp_pvt_Clear_Reg(SCP_REGISTER_INDEX_1);
}

/**
 * @brief Loads a key derived via AES-CMAC of a provided message into R0.
 *
 * Computes the CMAC of the first size bytes from message_pa, using the
 * hardware-secret indicated by secret_index as the key, and stores the result
 * in register-index zero. Assumes that the input has been padded per the CMAC
 * specification if b_padded is true.
 *
 * Note that this function uses register-indices one through three for
 * intermediate values and also loads instructions into sequencer zero.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre The SCP driver must not be operating in direct mode.
 *
 * @param[in]  secret_index The index of the hardware-secret to be used in the
 *                          key-derivation process.
 *
 * @param[in]  message_pa   The physical address of the input buffer containing
 *                          the message to be used in the key-derivation
 *                          process.
 *
 * @param[in]  size         The number of bytes to process from message_pa.
 *
 * @param[in]  b_padded     A boolean value indicating whether the source
 *                          message contains padding.
 *
 * @retval E_SUCCESS                    if key was loaded successfully.
 *
 * @retval E_SCP_INVALID_OBJECT_BUFFER  if message_pa is zero (NULL) or
 *                                      incorrectly-aligned, or points to an
 *                                      unsupported memory location (e.g. EMEM),
 *                                      or references a buffer that overflows
 *                                      the memory region in which it resides,
 *                                      or references a buffer of invalid size.
 *
 * @retval E_SCP_INVALID_INDEX          if secret_index is not a valid SCP
 *                                      secret-index, or references secret-index
 *                                      zero.
 *
 * @retval E_PERM                       if secret_index references a hardware-
 *                                      secret that the application does not
 *                                      have permission to use.
 *
 * @retval E_SCP_INSUFFICIENT_RESOURCES if the application lacks access to
 *                                      secret-index zero (required in secure
 *                                      contexts).
 *
 * @retval E_FAULT                      if an IO-PMP fault was detected.
 *
 * @retval E_SCP_GENERIC                if an unexpected error occurred.
 */
static error_t
s_scp_Load_Cmac_Key
(
    uint8_t     secret_index,
    uintptr_t   message_pa,
    size_t      size,
    bool        b_padded
)
{
    error_t status;

    // Verify context (hardware secrets cannot be used in insecure contexts).
    if (!scp_pvt_Is_Secure())
    {
        return E_PERM;
    }

    // Validate the secret index.
    // TODO(COREUCODES-1846): Reinstate check against SCP_SECRET_INDEX_CLEAR.
    if (secret_index >= SCP_SECRET_COUNT)
    {
        return E_SCP_INVALID_INDEX;
    }

    // Load the requested hardware secret.
    scp_secret(secret_index, SCP_REGISTER_INDEX_0);
    scp_wait_dma();

    // Check for any failures.
    status = scp_check_dma(true);
    if (status == E_PERM)
    {
        // Bubble up permissions errors.
        return status;
    }
    else if (status != E_SUCCESS)
    {
        // No other errors are expected.
        return E_SCP_GENERIC;
    }

    // Generate derived key in register-index one.
    status = s_scp_Cmac(SCP_KEY_RAW_REGISTER(SCP_REGISTER_INDEX_0),
        b_padded ? SCP_CMAC_MODE_PADDED : SCP_CMAC_MODE_COMPLETE,
        message_pa, size, (uintptr_t)NULL);
    switch (status)
    {
        // Continue on success.
        case E_SUCCESS:
            break;

        // Translate buffer-related errors.
        case E_SCP_INVALID_POINTER:
        case E_SCP_INVALID_ADDRESS:
        case E_SCP_INVALID_BASE:
        case E_SCP_INVALID_REQUEST:
        case E_SCP_OUT_OF_RANGE:
            return E_SCP_INVALID_OBJECT_BUFFER;

        // Bubble-up other plausible errors.
        case E_SCP_INSUFFICIENT_RESOURCES:
        case E_FAULT:
            return status;

        // Anything else is unexpected.
        default:
            return E_SCP_GENERIC;
    }

    // Move key to destination register.
    scp_mov(SCP_REGISTER_INDEX_1, SCP_REGISTER_INDEX_0);
    scp_wait_dma();

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

    // Clear the source register.
    return scp_pvt_Clear_Reg(SCP_REGISTER_INDEX_1);
}

/**
 * @brief Loads the requested cryptographic algorithm into sequencer zero.
 *
 * Loads sequencer zero with the set of SCP instructions corresponding to a
 * single iteration of the requested block-cipher algorithm. Uses the
 * decryption form if b_decrypt is true and the encryption form otherwise.
 *
 * The loaded algorithm assumes that key material has already been loaded to
 * register-index zero and has also been selected via the scp_key()
 * instruction. It further assumes that any context information 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.
 *
 * @param[in]  cipher_mode  The block-cipher algorithm to load.
 *
 * @param[in]  b_decrypt    A boolean value indicating whether to load the
 *                          decryption sequence for the selected algorithm
 *                          rather than the encryption sequence.
 *
 * @retval E_SUCCESS                if algorithm was loaded successfully.
 * @retval E_SCP_GENERIC            if an unexpected error occurred.
 */
static error_t
s_scp_Load_Crypt_Sequence
(
    scp_cipher_mode_t   cipher_mode,
    bool                b_decrypt
)
{
    // Load the correct sequence for the requested block-cipher mode.
    switch (cipher_mode)
    {
        case SCP_CIPHER_MODE_ECB:
            return s_scp_Load_Ecb_Sequence(b_decrypt);

        case SCP_CIPHER_MODE_CBC:
            return s_scp_Load_Cbc_Sequence(b_decrypt);

        case SCP_CIPHER_MODE_CTR:
            return s_scp_Load_Ctr_Sequence();

        default:
            //
            // Not a recognized block-cipher mode. This should have been
            // validated already by the caller so treat as unexpected.
            //
            return E_SCP_GENERIC;
    }
}

/**
 * @brief Loads the SCP instructions for AES-ECB into sequencer zero.
 *
 * Loads sequencer zero with the set of SCP instructions corresponding to a
 * single iteration of AES-ECB encryption/decryption. Uses the decryption
 * algorithm if b_decrypt is true and the encryption algorithm otherwise.
 *
 * The loaded algorithm assumes that key material has already been loaded to
 * register-index zero and has also been selected via the scp_key()
 * instruction (but not yet converted via scp_rkey10(), if applicable). It
 * further assumes that register-index two is available for use as intermediate
 * storage.
 *
 * @pre The SCP hardware must have been properly initialized.
 *
 * @param[in]  b_decrypt    A boolean value indicating whether to load the
 *                          decryption sequence rather than the encryption
 *                          sequence.
 *
 * @retval E_SUCCESS                if instructions were loaded successfully.
 * @retval E_SCP_GENERIC            if an unexpected error occurred.
 */
static error_t
s_scp_Load_Ecb_Sequence
(
    bool b_decrypt
)
{
    // Prepare to write three instructions to sequencer zero.
    scp_load_trace0(3U);
    {
        // Load the next block of plaintext/ciphertext to process.
        scp_push(SCP_REGISTER_INDEX_2);

        if (b_decrypt)
        {
            // Decrypt the ciphertext in-place.
            scp_decrypt(SCP_REGISTER_INDEX_2, SCP_REGISTER_INDEX_2);
        }
        else
        {
            // Encrypt the plaintext in-place.
            scp_encrypt(SCP_REGISTER_INDEX_2, SCP_REGISTER_INDEX_2);
        }

        // Store the resulting ciphertext/plaintext.
        scp_fetch(SCP_REGISTER_INDEX_2);
    }

    //
    // Convert the key loaded in register-index zero to the form required for
    // decryption operations, if applicable. This only needs to be done once,
    // hence its exclusion from the above sequencer load.
    //
    if (b_decrypt)
    {
        scp_rkey10(SCP_REGISTER_INDEX_0, SCP_REGISTER_INDEX_0);
    }

    // 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 Loads the SCP instructions for AES-CBC into sequencer zero.
 *
 * Loads sequencer zero with the set of SCP instructions corresponding to a
 * single iteration of AES-CBC encryption/decryption. Uses the decryption
 * algorithm if b_decrypt is true and the encryption algorithm otherwise.
 *
 * The loaded algorithm assumes that key material has already been loaded to
 * register-index zero and has also been selected via the scp_key()
 * instruction (but not yet converted via scp_rkey10(), if applicable). It
 * further assumes that the initialization vector 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.
 *
 * @param[in]  b_decrypt    A boolean value indicating whether to load the
 *                          decryption sequence rather than the encryption
 *                          sequence.
 *
 * @retval E_SUCCESS                if instructions were loaded successfully.
 * @retval E_SCP_GENERIC            if an unexpected error occurred.
 */
static error_t
s_scp_Load_Cbc_Sequence
(
    bool b_decrypt
)
{
    // Split because decryption requires an extra instruction over encryption.
    if (b_decrypt)
    {
        // Prepare to write five instructions to sequencer zero.
        scp_load_trace0(5U);
        {
            // Load the next block of ciphertext to process.
            scp_push(SCP_REGISTER_INDEX_2);

            // Decrypt non-destructively.
            scp_decrypt(SCP_REGISTER_INDEX_2, SCP_REGISTER_INDEX_3);

            // XOR with the current "IV" to obtain the plaintext.
            scp_xor(SCP_REGISTER_INDEX_1, SCP_REGISTER_INDEX_3);

            // Carry the original ciphertext forward as the new "IV".
            scp_mov(SCP_REGISTER_INDEX_2, SCP_REGISTER_INDEX_1);

            // Store the resulting plaintext.
            scp_fetch(SCP_REGISTER_INDEX_3);
        }

        //
        // Convert the key loaded in register-index zero to the form required
        // for decryption operations. This only needs to be done once, hence
        // its exclusion from the above sequencer load.
        //
        scp_rkey10(SCP_REGISTER_INDEX_0, SCP_REGISTER_INDEX_0);
    }
    else
    {
        // Prepare to write four instructions to sequencer zero.
        scp_load_trace0(4U);
        {
            // Load the next block of plaintext to process.
            scp_push(SCP_REGISTER_INDEX_2);

            // XOR with the current "IV".
            scp_xor(SCP_REGISTER_INDEX_1, SCP_REGISTER_INDEX_2);

            // Encrypt to obtain the ciphertext and save as the new "IV".
            scp_encrypt(SCP_REGISTER_INDEX_2, SCP_REGISTER_INDEX_1);

            // Store the resulting ciphertext.
            scp_fetch(SCP_REGISTER_INDEX_1);
        }
    }

    // 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 Loads the SCP instructions for AES-CTR into sequencer zero.
 *
 * Loads sequencer zero with the set of SCP instructions corresponding to a
 * single iteration of AES-CTR encryption/decryption.
 *
 * The loaded algorithm assumes that key material has already been loaded to
 * register-index zero and has also been selected via the scp_key()
 * instruction. It further assumes that the initial counter value (with nonce)
 * 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_Ctr_Sequence(void)
{
    // Prepare to write five instructions to sequencer zero.
    scp_load_trace0(5U);
    {
        // Load the next block of plaintext/ciphertext to process.
        scp_push(SCP_REGISTER_INDEX_3);

        // Encrypt the current counter value (with nonce) non-destructively.
        scp_encrypt(SCP_REGISTER_INDEX_1, SCP_REGISTER_INDEX_2);

        // XOR with the current block to obtain the ciphertext/plaintext.
        scp_xor(SCP_REGISTER_INDEX_2, SCP_REGISTER_INDEX_3);

        // Update the current counter value.
        scp_add(1U, SCP_REGISTER_INDEX_1);

        // Store the resulting ciphertext/plaintext.
        scp_fetch(SCP_REGISTER_INDEX_3);
    }

    // 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 Loads the SCP instructions for DmHash into sequencer zero.
 *
 * Loads sequencer zero with the set of SCP instructions required to compute
 * the Davies-Meyer hash of a single block of data. Also updates the key
 * register (Ku).
 *
 * The loaded algorithm assumes that the initial hash value (H0) 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_Hash_Sequence(void)
{
    // Prepare to write three instructions to sequencer zero.
    scp_load_trace0(3U);
    {
        // Load the next block of the message to process.
        scp_push(SCP_REGISTER_INDEX_2);

        // Encrypt the previous hash value with the message.
        scp_encrypt(SCP_REGISTER_INDEX_1, SCP_REGISTER_INDEX_3);

        // XOR with the previous hash value to obtain the new hash value.
        scp_xor(SCP_REGISTER_INDEX_3, 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 Loads the SCP instructions for CMAC into sequencer zero.
 *
 * Loads sequencer zero with the set of SCP instructions required to compute
 * the CMAC of a single intermediate block of data.
 *
 * The loaded algorithm assumes that key material has already been loaded to
 * register-index zero and has also been selected via the scp_key()
 * instruction. It further assumes that the initialization vector, if any, has
 * been loaded to register-index one and that register-index two is 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_Cmac_Sequence(void)
{
    // Prepare to write three instructions to sequencer zero.
    scp_load_trace0(3U);
    {
        // Load the next block of the message to process.
        scp_push(SCP_REGISTER_INDEX_2);

        // XOR with the current MAC value (or IV).
        scp_xor(SCP_REGISTER_INDEX_1, SCP_REGISTER_INDEX_2);

        // Encrypt to obtain the new MAC value.
        scp_encrypt(SCP_REGISTER_INDEX_2, SCP_REGISTER_INDEX_1);
    }

    // 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 Completes a CMAC computation by handling a message's final block.
 *
 * Generates the correct CMAC subkey per the value of b_padded and uses it to
 * tweak the first SCP_BLOCK_SIZE bytes from final_pa before computing the final
 * CMAC result from it and the intermediate value in SCP register-index one.
 *
 * Assumes that key material has already been loaded to register-index zero and
 * has also been selected via the scp_key() instruction. Further assumes that
 * register-indices two and three are available for use as intermediate storage.
 *
 * Note that the caller is responsible for validating final_pa before passing it
 * to this function, and is also responsible for clearing the intermediate
 * registers when finished.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Register-index one must be fetchable.
 *
 * @param[in]  final_pa     The physical address of the final block of the
 *                          input message. Must be aligned to and contain at
 *                          least SCP_BLOCK_SIZE bytes. Supports IMEM and DMEM
 *                          locations only.
 *
 * @param[in]  b_padded     A boolean value indicating whether the input
 *                          message has been padded per the CMAC specification.
 *
 * @retval E_SUCCESS                    if CMAC was completed successfully.
 *
 * @retval E_SCP_INSUFFICIENT_RESOURCES if the application lacks access to
 *                                      secret-index zero (required in secure
 *                                      contexts).
 *
 * @retval E_FAULT                      if an IO-PMP fault was detected.
 *
 * @retval E_SCP_GENERIC                if an unexpected error occurred.
 */
static error_t
s_scp_Complete_Cmac
(
    uintptr_t   final_pa,
    bool        b_padded
)
{
    error_t status;

    // Load the final message block.
    status = scp_pvt_Load_Buffer(final_pa, SCP_REGISTER_INDEX_2);
    if (status != E_SUCCESS)
    {
        return status;
    }

    // Ensure that register-index three is zero.
    status = scp_pvt_Clear_Reg(SCP_REGISTER_INDEX_3);
    if (status != E_SUCCESS)
    {
        return status;
    }

    // Generate the first CMAC subkey.
    scp_encrypt(SCP_REGISTER_INDEX_3, SCP_REGISTER_INDEX_3);
    scp_cmac_sk(SCP_REGISTER_INDEX_3, SCP_REGISTER_INDEX_3);

    // Generate the second subkey if needed.
    if (b_padded)
    {
        scp_cmac_sk(SCP_REGISTER_INDEX_3, SCP_REGISTER_INDEX_3);
    }

    // Tweak the final message block using the relevant subkey.
    scp_xor(SCP_REGISTER_INDEX_3, SCP_REGISTER_INDEX_2);

    // XOR with the current MAC value.
    scp_xor(SCP_REGISTER_INDEX_1, SCP_REGISTER_INDEX_2);

    // Encrypt to obtain the final MAC value.
    scp_encrypt(SCP_REGISTER_INDEX_2, SCP_REGISTER_INDEX_1);

    // Wait for completion.
    scp_wait_dma();

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

    // Done.
    return status;
}
