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

#ifndef SCP__SCP_DIRECT_H
#define SCP__SCP_DIRECT_H
#define FSP__SCP__SCP_DIRECT_H 1

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

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

/* Hardware headers */

/* Late FSP headers */
#include <cpu/io.h>                     // for local_read, local_write
#include <error/common-errors.h>        // for error_t
// Clients must provide their own DRF headers

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

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


/**
 * @brief Issues a single CCI instruction to the SCP unit.
 *
 * @pre The SCP hardware must have been properly initialized.
 *
 * @param[in] opcode    The CCI instruction to issue (e.g. XOR).
 * @param[in] rx_or_imm A register index or 6-bit immediate value.
 * @param[in] ry        A register index.
 *
 * @note The interpretation of the rx_or_imm and ry parameters is instruction-
 *       dependent. They can be set to zero when not needed.
 */
#define SCP_INSTRUCTION(opcode, rx_or_imm, ry) (                        \
    local_write(NV_PRGNLCL_RISCV_SCPDMATRFCMD,                          \
        DRF_DEF(_PRGNLCL_RISCV, _SCPDMATRFCMD, _CCI_EX,    _CCI     ) | \
        DRF_NUM(_PRGNLCL_RISCV, _SCPDMATRFCMD, _RY,        ry       ) | \
        DRF_NUM(_PRGNLCL_RISCV, _SCPDMATRFCMD, _RX_OR_IMM, rx_or_imm) | \
        DRF_DEF(_PRGNLCL_RISCV, _SCPDMATRFCMD, _OPCODE,    _##opcode)   \
    )                                                                   \
)

/**
 * @brief The size, in bytes, of each SCP GPR.
 */
#define SCP_REGISTER_SIZE 16U


/**
 * @brief A collection of supported transfer sizes for queued reads/writes.
 */
typedef enum scp_transfer_size_t
{
    SCP_TRANSFER_SIZE_1R  = NV_PRGNLCL_RISCV_SCPDMATRFCMD_SIZE_16B,
    SCP_TRANSFER_SIZE_2R  = NV_PRGNLCL_RISCV_SCPDMATRFCMD_SIZE_32B,
    SCP_TRANSFER_SIZE_4R  = NV_PRGNLCL_RISCV_SCPDMATRFCMD_SIZE_64B,
    SCP_TRANSFER_SIZE_8R  = NV_PRGNLCL_RISCV_SCPDMATRFCMD_SIZE_128B,
    SCP_TRANSFER_SIZE_16R = NV_PRGNLCL_RISCV_SCPDMATRFCMD_SIZE_256B,
} scp_transfer_size_t;


/**
 * @brief Prepares the SCP driver for low-level operations.
 *
 * Prepares the SCP driver for low-level operations by entering direct mode.
 * Applications must call scp_Start_Direct() before invoking any low-level SCP
 * interfaces.
 *
 * @retval E_SUCCESS                if direct mode was started successfully.
 *
 * @retval E_SCP_NOT_READY          if the SCP driver has not been initialized.
 *
 * @retval E_SCP_NOTHING_TO_DO      if the SCP driver is already operating in
 *                                  direct mode (no-op).
 */
error_t scp_Start_Direct(void);

/**
 * @brief Prepares the SCP driver for high-level operations.
 *
 * Prepares the SCP driver for high-level operations by exiting direct mode.
 * Applications must call scp_Stop_Direct() before invoking any high-level SCP
 * interfaces, unless otherwise noted.
 *
 * @pre The SCP GPRs must be fetchable and writable or an interrupt may occur.
 *
 * @retval E_SUCCESS                    if direct mode was stopped successfully.
 *
 * @retval E_SCP_NOT_READY              if the SCP driver has not been
 *                                      initialized.
 *
 * @retval E_SCP_INSUFFICIENT_RESOURCES if the application lacks access to
 *                                      secret-index zero (required in secure
 *                                      contexts).
 *
 * @retval E_SCP_INVALID_LOCK_STATE     if access to certain necessary registers
 *                                      is being blocked by SCP lockdown.
 *
 * @retval E_SCP_GENERIC                if an unexpected error occurred.
 *
 * @retval E_SCP_NOTHING_TO_DO          if the SCP driver is not operating in
 *                                      direct mode (no-op).
 */
error_t scp_Stop_Direct(void);

/**
 * @brief Loads data from a buffer into a general-purpose SCP register.
 *
 * Loads the first SCP_REGISTER_SIZE bytes from source_pa into the register
 * specified by reg_index. Assumes that the destination register is writable.
 *
 * Applications can check the status of the underlying SCPDMA transfer using
 * the provided scp_*_dma() interfaces.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] source_pa The physical address of the input buffer containing the
 *                      data to be loaded. Must be aligned to and contain at
 *                      least SCP_REGISTER_SIZE bytes. Supports IMEM and DMEM
 *                      locations only.
 *
 * @param[in] reg_index The index of the SCP register to which the data is to
 *                      be written.
 */
void scp_load(uintptr_t source_pa, scp_register_index_t reg_index);

/**
 * @brief Stores data from a general-purpose SCP register into a buffer.
 *
 * Writes the contents of the register specified by reg_index to the first
 * SCP_REGISTER_SIZE bytes of dest_pa. Assumes that the source register is
 * fetchable.
 *
 * Applications can check the status of the underlying SCPDMA transfer using
 * the provided scp_*_dma() interfaces.
 *
 * In the event that dest_pa refers to an external memory location, the
 * application is responsible for completing the corresponding shortcut-DMA
 * transfer accordingly.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in]  reg_index The index of the SCP register containing the data to
 *                       be stored.
 *
 * @param[out] dest_pa   The physical address of the output buffer to which the
 *                       data is to be written. Must be aligned to and have
 *                       capacity for at least SCP_REGISTER_SIZE bytes. Supports
 *                       IMEM, DMEM, and external locations.
 */
void scp_store(scp_register_index_t reg_index, uintptr_t dest_pa);

/**
 * @brief Queues an SCPDMA transfer without issuing any push instructions.
 *
 * Queues a transfer of size bytes from source_pa but does not read any data
 * until one or more subsequent push instructions are received. Generally used
 * to feed sequencer programs.
 *
 * Applications can check the status of the underlying suppressed SCPDMA
 * transfer using the provided scp_*_dma() interfaces.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] source_pa The physical address of the input buffer containing the
 *                      data to be read. Must be aligned to and contain at
 *                      least size bytes. Supports IMEM and DMEM locations
 *                      only.
 *
 * @param[in] size      The number of bytes to read from source_pa. See the
 *                      documentation for scp_transfer_size_t for supported
 *                      values.
 */
void scp_queue_read(uintptr_t source_pa, scp_transfer_size_t size);

/**
 * @brief Queues an SCPDMA transfer without issuing any fetch instructions.
 *
 * Queues a transfer of size bytes to dest_pa but does not write any data until
 * one or more subsequent fetch instructions are received. Generally used to
 * feed sequencer programs.
 *
 * Applications can check the status of the underlying suppressed SCPDMA
 * transfer using the provided scp_*_dma() interfaces.
 *
 * In the event that dest_pa refers to an external memory location, the
 * application is responsible for queuing the corresponding shortcut-DMA
 * transfer accordingly.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[out] dest_pa  The physical address of the output buffer to which the
 *                      data is to be written. Must be aligned to and have
 *                      capacity for at least size bytes. Supports IMEM, DMEM,
 *                      and external locations.
 *
 * @param[in] size      The number of bytes to write to dest_pa. See the
 *                      documentation for scp_transfer_size_t for supported
 *                      values.
 */
void scp_queue_write(uintptr_t dest_pa, scp_transfer_size_t size);

/**
 * @brief Returns the active status of the SCPDMA unit.
 *
 * Applications can use scp_poll_dma() to determine whether a load, store,
 * or CCI transfer has finished.
 *
 * @retval true  if the SCPDMA unit is active.
 * @retval false if the SCPDMA unit is idle.
 */
static inline bool
scp_poll_dma(void)
{
    return FLD_TEST_DRF(_PRGNLCL_RISCV, _SCPDMAPOLL, _DMA_ACTIVE, _ACTIVE,
        local_read(NV_PRGNLCL_RISCV_SCPDMAPOLL));
}

/**
 * @brief Polls the active status of the SCPDMA unit and returns when idle.
 *
 * Applications can use scp_wait_dma() to ensure that a prior load, store,
 * or CCI transfer has finished.
 */
static inline void
scp_wait_dma(void)
{
    while (scp_poll_dma())
    {
    }
}

/**
 * @brief Checks for pending SCPDMA errors and then clears them if requested.
 *
 * Checks for pending SCPDMA errors. Clears them after reading if b_clear is
 * true (IO-PMP errors are not cleared).
 *
 * Applications can use scp_check_dma() to determine whether a load, store, or
 * CCI transfer has encountered problems.
 *
 * @pre The SCPDMA hardware must be idle.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] b_clear   A boolean value indicating whether the SCPDMA error
 *                      status should be cleared before returning.
 *
 * @retval E_SUCCESS                if no errors were reported.
 *
 * @retval E_SCP_INVALID_ADDRESS    if an SCPDMA transfer was initiated with a
 *                                  physical address whose offset is not
 *                                  aligned to the transfer size.
 *
 * @retval E_SCP_INVALID_BASE       if an SCPDMA transfer was initiated with a
 *                                  physical address that points to an
 *                                  unsupported memory region (e.g. EMEM).
 *
 * @retval E_PERM                   if a CCI secret instruction was initiated
 *                                  with a hardware-secret index that the
 *                                  application does not have permission to
 *                                  use.
 *
 * @retval E_FAULT                  if an IO-PMP fault was detected.
 */
error_t scp_check_dma(bool b_clear);

///////////////////////////////////////////////////////////////////////////////
// Begin instruction primitives. Refer to the IAS for full descriptions.

/**
 * @brief Adds a 6-bit immediate value to register ry.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] imm The 6-bit immediate value to add.
 * @param[in] ry  The destination register to add to.
 *
 * @note The default carry-chain is 64 bits in length.
 */
static inline void
scp_add(uint8_t imm, scp_register_index_t ry)
{
    SCP_INSTRUCTION(ADD, imm, ry);
}

/**
 * @brief Performs a bitwise AND on the contents of registers rx and ry.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] rx The first operand register.
 * @param[in] ry The second operand register and destination for the result.
 */
static inline void
scp_and(scp_register_index_t rx, scp_register_index_t ry)
{
    SCP_INSTRUCTION(AND, rx, ry);
}

/**
 * @brief Swaps the byte-order of the value in register rx.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] rx The source register containing the value to byte-swap.
 * @param[in] ry The destination register to receive the byte-swapped result.
 */
static inline void
scp_bswap(scp_register_index_t rx, scp_register_index_t ry)
{
    SCP_INSTRUCTION(BSWAP, rx, ry);
}

/**
 * @brief Modifies the ACL of register ry.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] imm The new ACL value, encoded as {Wi,Fi,Ki,Fs,Ks} = imm[4:0].
 * @param[in] ry  The register whose ACL will be updated.
 *
 * @note Only valid in secure contexts. Cannot grant secure permissions.
 */
static inline void
scp_chmod(uint8_t imm, scp_register_index_t ry)
{
    SCP_INSTRUCTION(CHMOD, imm, ry);
}

/**
 * @brief Generates the first of the two subkeys required for CMAC computation.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] rx The source register containing an encrypted zero value.
 * @param[in] ry The destination register to receive the generated subkey.
 *
 * @note The initial step of encrypting zero must be completed separately.
 */
static inline void
scp_cmac_sk(scp_register_index_t rx, scp_register_index_t ry)
{
    SCP_INSTRUCTION(CMAC_SK, rx, ry);
}

/**
 * @brief Perform AES-128 decryption on the contents of register rx.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] rx The source register containing the ciphertext to decrypt.
 * @param[in] ry The destination register to receive the decrypted plaintext.
 *
 * @note Obtains the key from the register specified in ku.
 */
static inline void
scp_decrypt(scp_register_index_t rx, scp_register_index_t ry)
{
    SCP_INSTRUCTION(DECRYPT, rx, ry);
}

/**
 * @brief Perform AES-128 encryption on the contents of register rx.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] rx The source register containing the plaintext to encrypt.
 * @param[in] ry The destination register to receive the encrypted ciphertext.
 *
 * @note Obtains the key from the register specified in ku.
 */
static inline void
scp_encrypt(scp_register_index_t rx, scp_register_index_t ry)
{
    SCP_INSTRUCTION(ENCRYPT, rx, ry);
}

/**
 * @brief Offers the contents of register ry to the SCPDMA interface.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] ry The source register whose data will be fetched.
 *
 * @note Fetches zeroes if register ry lacks sufficient access rights.
 */
static inline void
scp_fetch(scp_register_index_t ry)
{
    SCP_INSTRUCTION(FETCH, 0U, ry);
}

/**
 * @brief Invalidates the engine's signature/hash.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @note Only valid in secure contexts.
 */
static inline void
scp_forget_sig(void)
{
    SCP_INSTRUCTION(FORGET_SIG, 0U, 0U);
}

/**
 * @brief Marks register ry as the key source for cryptographic operations.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] ry The source register from which key material will be read.
 *
 * @note Updates ku with the index of ry only (contents are not copied).
 */
static inline void
scp_key(scp_register_index_t ry)
{
    SCP_INSTRUCTION(KEY, 0U, ry);
}

/**
 * @brief Loads the next imm instructions into sequencer zero.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] imm The number of instructions to load (0-16).
 *
 * @note Resets the sequencer if imm is zero.
 */
static inline void
scp_load_trace0(uint8_t imm)
{
    SCP_INSTRUCTION(LOAD_TRACE0, imm, 0U);
}

/**
 * @brief Loads the next imm instructions into sequencer one.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] imm The number of instructions to load (0-16).
 *
 * @note Resets the sequencer if imm is zero.
 */
static inline void
scp_load_trace1(uint8_t imm)
{
    SCP_INSTRUCTION(LOAD_TRACE1, imm, 0U);
}

/**
 * @brief Invokes imm iterations of sequencer zero.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] imm The number of iterations to execute (0-63).
 */
static inline void
scp_loop_trace0(uint8_t imm)
{
    SCP_INSTRUCTION(LOOP_TRACE0, imm, 0U);
}

/**
 * @brief Invokes imm iterations of sequencer one.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] imm The number of iterations to execute (0-63).
 */
static inline void
scp_loop_trace1(uint8_t imm)
{
    SCP_INSTRUCTION(LOOP_TRACE1, imm, 0U);
}

/**
 * @brief Copies the contents of register rx into register ry.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] rx The source register to copy from.
 * @param[in] ry The destination register to copy to.
 */
static inline void
scp_mov(scp_register_index_t rx, scp_register_index_t ry)
{
    SCP_INSTRUCTION(MOV, rx, ry);
}

/**
 * @brief No operation.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 */
static inline void
scp_nop(void)
{
    SCP_INSTRUCTION(NOP, 0U, 0U);
}

/**
 * @brief Accepts data from the SCPDMA interface and stores in register ry.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] ry The destination register to receive the push data.
 */
static inline void
scp_push(scp_register_index_t ry)
{
    SCP_INSTRUCTION(PUSH, 0U, ry);
}

/**
 * @brief Writes a true random number to register ry.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] ry The destination register to receive the random number.
 *
 * @note Requires prior configuration of the RNG hardware. Not NIST-compliant.
 */
static inline void
scp_rand(scp_register_index_t ry)
{
    SCP_INSTRUCTION(RAND, 0U, ry);
}

/**
 * @brief Converts a decryption key into an equivalent encryption key.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] rx The source register containing the decryption key to convert.
 * @param[in] ry The destination register to receive the encryption key.
 *
 * @note Inverse of the rkey10 instruction.
 */
static inline void
scp_rkey1(scp_register_index_t rx, scp_register_index_t ry)
{
    SCP_INSTRUCTION(RKEY1, rx, ry);
}

/**
 * @brief Converts an encryption key into an equivalent decryption key.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] rx The source register containing the encryption key to convert.
 * @param[in] ry The destination register to receive the decryption key.
 *
 * @note Inverse of the rkey1 instruction.
 */
static inline void
scp_rkey10(scp_register_index_t rx, scp_register_index_t ry)
{
    SCP_INSTRUCTION(RKEY10, rx, ry);
}

/**
 * @brief Loads one of 64 hardware secrets into register ry.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] imm The index of the hardware secret to load (0-63).
 * @param[in] ry  The destination register to receive the hardware secret.
 *
 * @note Only valid in secure contexts. ACL is updated accordingly.
 */
static inline void
scp_secret(uint8_t imm, scp_register_index_t ry)
{
    SCP_INSTRUCTION(SECRET, imm, ry);
}

/**
 * @brief Performs a bitwise XOR on the contents of registers rx and ry.
 *
 * @pre The SCP hardware must have been properly initialized.
 * @pre Clients must enter direct mode before calling this function.
 *
 * @param[in] rx The first operand register.
 * @param[in] ry The second operand register and destination for the result.
 */
static inline void
scp_xor(scp_register_index_t rx, scp_register_index_t ry)
{
    SCP_INSTRUCTION(XOR, rx, ry);
}

// End instruction primitives.
///////////////////////////////////////////////////////////////////////////////

// Prevent external use of this macro.
#undef SCP_INSTRUCTION

#endif // SCP__SCP_DIRECT_H
