/***************************************************************************//**
 * @file
 * @brief Secure Element API
 *******************************************************************************
 * # License
 * <b>Copyright 2018 Silicon Laboratories Inc. www.silabs.com</b>
 *******************************************************************************
 *
 * SPDX-License-Identifier: Zlib
 *
 * The licensor of this software is Silicon Laboratories Inc.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 *
 ******************************************************************************/
#ifndef EM_SE_H
#define EM_SE_H

#include "em_device.h"

#if defined(SEMAILBOX_PRESENT) || defined(CRYPTOACC_PRESENT)

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

/***************************************************************************//**
 * @addtogroup emlib
 * @{
 ******************************************************************************/

/***************************************************************************//**
 * @addtogroup SE
 *
 * @brief Secure Element peripheral API
 *
 * @details
 *   Abstraction of the Secure Element's mailbox interface.
 *
 *   @note Although commands to interact with the mailbox directly are
 *   available, it is always recommended to use the higher level APIs available
 *   in em_se and through mbedTLS.
 *
 *   @note Using the SE's mailbox is not thread-safe in emlib, and accessing the
 *   SE's mailbox both in regular and IRQ context is not safe, either. If
 *   mbedTLS is compiled into the application, SE operations should be wrapped
 *   in se_management_acquire()/se_management_release() calls to synchronize
 *   access. If mbedTLS is not in use, it is the user's responsibility to not
 *   trigger simultaneous use of the SE mailbox.
 *
 * @{
 ******************************************************************************/

/*******************************************************************************
 ******************************   DEFINES    ***********************************
 ******************************************************************************/

#if defined(SEMAILBOX_PRESENT)

/* Command words for the Security Engine. */
#if (defined(_SILICON_LABS_SECURITY_FEATURE) \
  && (_SILICON_LABS_SECURITY_FEATURE == _SILICON_LABS_SECURITY_FEATURE_VAULT))
#define SE_COMMAND_WRAP_KEY                 0x01000000UL
#define SE_COMMAND_UNWRAP_KEY               0x01020000UL
#define SE_COMMAND_TRANSFER_KEY             0x01060000UL
#endif /* _SILICON_LABS_SECURITY_FEATURE_VAULT */

#define SE_COMMAND_CREATE_KEY               0x02000000UL
#define SE_COMMAND_READPUB_KEY              0x02010000UL

#if (defined(_SILICON_LABS_SECURITY_FEATURE) \
  && (_SILICON_LABS_SECURITY_FEATURE == _SILICON_LABS_SECURITY_FEATURE_VAULT))
#define SE_COMMAND_DERIVE_KEY_PBKDF2        0x02020002UL
#define SE_COMMAND_DERIVE_KEY_HKDF          0x02020003UL
#endif /* _SILICON_LABS_SECURITY_FEATURE_VAULT */

#define SE_COMMAND_HASH                     0x03000000UL
#define SE_COMMAND_HASHUPDATE               0x03010000UL
#define SE_COMMAND_HMAC                     0x03020000UL

#define SE_COMMAND_AES_ENCRYPT              0x04000000UL
#define SE_COMMAND_AES_DECRYPT              0x04010000UL
#define SE_COMMAND_AES_GCM_ENCRYPT          0x04020000UL
#define SE_COMMAND_AES_GCM_DECRYPT          0x04030000UL
#define SE_COMMAND_AES_CMAC                 0x04040000UL
#define SE_COMMAND_AES_CCM_ENCRYPT          0x04050000UL
#define SE_COMMAND_AES_CCM_DECRYPT          0x04060000UL

#define SE_COMMAND_SIGNATURE_SIGN           0x06000000UL
#define SE_COMMAND_SIGNATURE_VERIFY         0x06010000UL
#if (defined(_SILICON_LABS_SECURITY_FEATURE) \
  && (_SILICON_LABS_SECURITY_FEATURE == _SILICON_LABS_SECURITY_FEATURE_VAULT))
#define SE_COMMAND_EDDSA_SIGN               0x06020000UL
#define SE_COMMAND_EDDSA_VERIFY             0x06030000UL
#endif /* _SILICON_LABS_SECURITY_FEATURE_VAULT */

#define SE_COMMAND_TRNG_GET_RANDOM          0x07000000UL
#define SE_COMMAND_READ_CLOCK               0x07020000UL

#if (defined(_SILICON_LABS_SECURITY_FEATURE) \
  && (_SILICON_LABS_SECURITY_FEATURE == _SILICON_LABS_SECURITY_FEATURE_VAULT))
#define SE_COMMAND_ATTEST_CERTIFY           0x0A000000UL
#define SE_COMMAND_ATTEST_TIME              0x0A010000UL
#define SE_COMMAND_ATTEST_PUBKEY            0x0A020000UL
#endif /* _SILICON_LABS_SECURITY_FEATURE_VAULT */

#define SE_COMMAND_JPAKE_R1_GENERATE        0x0B000000UL
#define SE_COMMAND_JPAKE_R1_VERIFY          0x0B000100UL
#define SE_COMMAND_JPAKE_R2_GENERATE        0x0B010000UL
#define SE_COMMAND_JPAKE_R2_VERIFY          0x0B010100UL
#define SE_COMMAND_JPAKE_GEN_SESSIONKEY     0x0B020000UL

#if (defined(_SILICON_LABS_SECURITY_FEATURE) \
  && (_SILICON_LABS_SECURITY_FEATURE == _SILICON_LABS_SECURITY_FEATURE_VAULT))
#define SE_COMMAND_AEAD_ENCRYPT             0x0C000000UL
#define SE_COMMAND_AEAD_DECRYPT             0x0C010000UL
#define SE_COMMAND_CHACHA20_ENCRYPT         0x0C020000UL
#define SE_COMMAND_CHACHA20_DECRYPT         0x0C030000UL
#define SE_COMMAND_POLY1305_KEY_MAC         0x0C040000UL
#endif /* _SILICON_LABS_SECURITY_FEATURE_VAULT */

#define SE_COMMAND_DH                       0x0E000000UL

#endif // #if defined(SEMAILBOX_PRESENT)

#define SE_COMMAND_CHECK_SE_IMAGE           0x43020000UL
#define SE_COMMAND_APPLY_SE_IMAGE           0x43030000UL
#define SE_COMMAND_STATUS_SE_IMAGE          0x43040000UL
#define SE_COMMAND_CHECK_HOST_IMAGE         0x43050001UL
#define SE_COMMAND_APPLY_HOST_IMAGE         0x43060001UL
#define SE_COMMAND_STATUS_HOST_IMAGE        0x43070000UL

#if defined(SEMAILBOX_PRESENT)

#define SE_COMMAND_STATUS_SE_VERSION        0x43080000UL
#define SE_COMMAND_STATUS_OTP_VERSION       0x43080100UL

#define SE_COMMAND_WRITE_USER_DATA          0x43090000UL
#define SE_COMMAND_ERASE_USER_DATA          0x430A0000UL

#define SE_COMMAND_DBG_LOCK_APPLY           0x430C0000
#define SE_COMMAND_DBG_LOCK_ENABLE_SECURE   0x430D0000
#define SE_COMMAND_DBG_LOCK_DISABLE_SECURE  0x430E0000
#define SE_COMMAND_DEVICE_ERASE             0x430F0000
#define SE_COMMAND_DEVICE_ERASE_DISABLE     0x43100000
#define SE_COMMAND_DBG_LOCK_STATUS          0x43110000

#define SE_COMMAND_PROTECTED_REGISTER       0x43210000

#define SE_COMMAND_GET_CHALLENGE            0xFD000000UL
#define SE_COMMAND_ROLL_CHALLENGE           0xFD000100UL
#define SE_COMMAND_OPEN_DEBUG               0xFD010001UL
#define SE_COMMAND_DISABLE_TAMPER           0xFD020001UL

#define SE_COMMAND_READ_SERIAL              0xFE000000UL
#define SE_COMMAND_GET_STATUS               0xFE010000UL
#define SE_COMMAND_READ_PUBKEYBOOT          0xFE020001UL

#define SE_COMMAND_SET_UPGRADEFLAG_SE       0xFE030000UL
#define SE_COMMAND_SET_UPGRADEFLAG_HOST     0xFE030001UL

#define SE_COMMAND_READ_PUBKEY              0xFF080001UL
#define SE_COMMAND_INIT_PUBKEY_SIGNATURE    0xFF090001UL
#define SE_COMMAND_READ_PUBKEY_SIGNATURE    0xFF0A0001UL

/* Command options for the Secure Element commands. */
/** Use MD5 as hash algorithm */
#define SE_COMMAND_OPTION_HASH_MD5          0x00000100UL
/** Use SHA1 as hash algorithm */
#define SE_COMMAND_OPTION_HASH_SHA1         0x00000200UL
/** Use SHA224 as hash algorithm */
#define SE_COMMAND_OPTION_HASH_SHA224       0x00000300UL
/** Use SHA256 as hash algorithm */
#define SE_COMMAND_OPTION_HASH_SHA256       0x00000400UL

#if (defined(_SILICON_LABS_SECURITY_FEATURE) \
  && (_SILICON_LABS_SECURITY_FEATURE == _SILICON_LABS_SECURITY_FEATURE_VAULT))
/** Use SHA384 as hash algorithm */
#define SE_COMMAND_OPTION_HASH_SHA384       0x00000500UL
/** Use SHA512 as hash algorithm */
#define SE_COMMAND_OPTION_HASH_SHA512       0x00000600UL
#endif /* _SILICON_LABS_SECURITY_FEATURE_VAULT */

/** Execute algorithm in ECB mode */
#define SE_COMMAND_OPTION_MODE_ECB          0x00000100UL
/** Execute algorithm in CBC mode */
#define SE_COMMAND_OPTION_MODE_CBC          0x00000200UL
/** Execute algorithm in CTR mode */
#define SE_COMMAND_OPTION_MODE_CTR          0x00000300UL
/** Execute algorithm in CFB mode */
#define SE_COMMAND_OPTION_MODE_CFB          0x00000400UL
/** Execute algorithm in OFB mode */
#define SE_COMMAND_OPTION_MODE_OFB          0x00000500UL
/** Execute algorithm in XTS mode */
#define SE_COMMAND_OPTION_MODE_XTS          0x00000800UL

#define SE_COMMAND_OPTION_CERT_DEVICE       0x00000100UL
#define SE_COMMAND_OPTION_CERT_BATCH        0x00000200UL
#define SE_COMMAND_OPTION_CERT_FACTORY      0x00000300UL

/** Pubkey type */
#define SE_KEY_TYPE_ROOT                    0x00000300UL

/** Run the whole algorithm, all data present */
#define SE_COMMAND_OPTION_CONTEXT_WHOLE     0x00000000UL
/** Start the algorithm, but get a context to later add more data */
#define SE_COMMAND_OPTION_CONTEXT_START     0x00000001UL
/** End the algorithm, get the result */
#define SE_COMMAND_OPTION_CONTEXT_END       0x00000002UL
/** Add more data input to the algorithm. Need to supply previous context,
 *  and get a context back */
#define SE_COMMAND_OPTION_CONTEXT_ADD       0x00000003UL

/** Padding options for signature functionality. */
#define SE_COMMAND_OPTION_PADDING_NONE      0x00000000UL
#define SE_COMMAND_OPTION_PADDING_EMSA_PKCS 0x00000003UL
#define SE_COMMAND_OPTION_PADDING_PSS       0x00000004UL

/* Special parameters for the Secure Element commands. */
#define SE_COMMAND_OPTION_READ              0x00000000UL
#define SE_COMMAND_OPTION_WRITE             0x00000100UL

/** Magic paramater for deleting user data */
#define SE_COMMAND_OPTION_ERASE_UD          0xDE1E7EADUL

#elif defined(CRYPTOACC_PRESENT)
/* Root Code Mailbox is invalid. */
#define SE_RESPONSE_MAILBOX_INVALID         0x00FE0000UL
/* Root Code Mailbox magic word */
#define SE_RESPONSE_MAILBOX_VALID           0xE5ECC0DEUL
#endif

/* Response status codes for the Secure Element */
#define SE_RESPONSE_MASK                    0x000F0000UL
/** Command executed successfully or signature was successfully validated. */
#define SE_RESPONSE_OK                      0x00000000UL

/** Pubkey types */
#define SE_KEY_TYPE_BOOT                    0x00000100UL
#define SE_KEY_TYPE_AUTH                    0x00000200UL

#define SE_COMMAND_INIT_OTP                 0xFF000001UL
#define SE_COMMAND_INIT_PUBKEY              0xFF070001UL

/**
 * Command was not recognized as a valid command, or is not allowed in the
 * current context.
 */
#define SE_RESPONSE_INVALID_COMMAND         0x00010000UL
/**
 * User did not provide the required credentials to be allowed to execute the
 * command.
 */
#define SE_RESPONSE_AUTHORIZATION_ERROR     0x00020000UL
/**
 * Signature validation command (e.g. SE_COMMAND_SIGNATURE_VERIFY) failed to
 * verify the given signature as being correct.
 */
#define SE_RESPONSE_INVALID_SIGNATURE       0x00030000UL
/** A command started in non-secure mode is trying to access secure memory. */
#define SE_RESPONSE_BUS_ERROR               0x00040000UL
/** Internal error */
#define SE_RESPONSE_INTERNAL_ERROR          0x00050000UL
/** An internal error was raised and the command did not execute. */
#define SE_RESPONSE_CRYPTO_ERROR            0x00060000UL
/** One of the passed parameters is deemed invalid (e.g. out of bounds). */
#define SE_RESPONSE_INVALID_PARAMETER       0x00070000UL
/** Failure while checking the host for secure boot */
#define SE_RESPONSE_SECUREBOOT_ERROR        0x00090000UL
/** Failure during selftest */
#define SE_RESPONSE_SELFTEST_ERROR          0x000A0000UL
/** Feature/item not initialized or not present */
#define SE_RESPONSE_NOT_INITIALIZED         0x000B0000UL
/* Abort status code is given when no operation is attempted. */
#define SE_RESPONSE_ABORT                   0x00FF0000UL

/** Maximum amount of parameters supported by the hardware FIFO */
#define SE_FIFO_MAX_PARAMETERS              13U

#define SE_DATATRANSFER_STOP                0x00000001UL
#define SE_DATATRANSFER_DISCARD             0x40000000UL
#define SE_DATATRANSFER_REALIGN             0x20000000UL
#define SE_DATATRANSFER_CONSTADDRESS        0x10000000UL
#define SE_DATATRANSFER_LENGTH_MASK         0x0FFFFFFFUL

/** Maximum amount of parameters for largest command in defined command set */
#ifndef SE_MAX_PARAMETERS
#define SE_MAX_PARAMETERS                   4U
#endif

/* Sanity-check defines */
#if SE_MAX_PARAMETERS > SE_FIFO_MAX_PARAMETERS
#error "Trying to configure more parameters than supported by the hardware"
#endif

/*******************************************************************************
 ******************************   TYPEDEFS   ***********************************
 ******************************************************************************/

/**
 * SE DMA transfer descriptor. Can be linked to each other to provide
 * scatter-gather behavior.
 */
typedef struct {
  volatile void* volatile data;
  void* volatile next;
  volatile uint32_t length;
} SE_DataTransfer_t;

/** Default initialization of data transfer struct */
#define SE_DATATRANSFER_DEFAULT(address, length)                               \
  {                                                                            \
    (void*)(address),                  /* Pointer to data block */             \
    (void*)SE_DATATRANSFER_STOP,       /* This is the last block by default */ \
    (length) | SE_DATATRANSFER_REALIGN /* Add size, use realign by default */  \
  }

/**
 * SE Command structure to which all commands to the SE must adhere.
 */
typedef struct {
  uint32_t command;
  SE_DataTransfer_t* data_in;
  SE_DataTransfer_t* data_out;
  uint32_t parameters[SE_MAX_PARAMETERS];
  size_t num_parameters;
} SE_Command_t;

/** Default initialization of command struct */
#define SE_COMMAND_DEFAULT(command)       \
  {                                       \
    (command),        /* Given command */ \
    NULL,             /* No data in */    \
    NULL,             /* No data out */   \
    { 0, 0, 0, 0 },   /* No parameters */ \
    0                 /* No parameters */ \
  }

/** Possible responses to a command */
typedef uint32_t SE_Response_t;

typedef struct {
  /** Enable secure boot for the host. */
  bool enableSecureBoot;
  /** Require certificate based secure boot signing. */
  bool verifySecureBootCertificate;
  /** Enable anti-rollback for host application upgrades. */
  bool enableAntiRollback;

  /** Set flag to enable locking down all flash pages that cover the
   * secure-booted image, except the last page if end of signature is not
   * page-aligned. */
  bool secureBootPageLockNarrow;
  /** Set flag to enable locking down all flash pages that cover the
   * secure-booted image, including the last page if end of signature is not
   * page-aligned. */
  bool secureBootPageLockFull;
} SE_OTPInit_t;

typedef struct {
  /** Whether debug lock is enabled */
  bool debugLockEnabled;
  /** Whether device erase is enabled */
  bool deviceEraseEnabled;
  /** Whether secure debug is enabled */
  bool secureDebugEnabled;
} SE_DebugStatus_t;

typedef struct {
  /** Boot status code / error code (Bits [7:0]). */
  uint32_t bootStatus;
  /** SE firmware version. */
  uint32_t seFwVersion;
  /** Host firmware version (if available). */
  uint32_t hostFwVersion;
  /** Debug lock status. */
  SE_DebugStatus_t debugStatus;
  /** Secure boot enabled. */
  bool secureBootEnabled;
} SE_Status_t;

/*******************************************************************************
 *****************************   PROTOTYPES   **********************************
 ******************************************************************************/

void SE_addDataInput(SE_Command_t *command,
                     SE_DataTransfer_t *data);

void SE_addDataOutput(SE_Command_t *command,
                      SE_DataTransfer_t *data);

void SE_addParameter(SE_Command_t *command, uint32_t parameter);

void SE_executeCommand(SE_Command_t *command);

SE_Response_t SE_initOTP(SE_OTPInit_t *otp_init);

SE_Response_t SE_initPubkey(uint32_t key_type,
                            void* pubkey,
                            uint32_t numBytes,
                            bool signature);

#if defined(SEMAILBOX_PRESENT)

// User data commands
SE_Response_t SE_writeUserData(uint32_t offset,
                               void *data,
                               uint32_t numBytes);

SE_Response_t SE_eraseUserData(void);

// Initialization commands
SE_Response_t SE_readPubkey(uint32_t key_type,
                            void* pubkey,
                            uint32_t numBytes,
                            bool signature);

// Debug commands
SE_Response_t SE_debugLockStatus(SE_DebugStatus_t *status);
SE_Response_t SE_debugLockApply(void);
SE_Response_t SE_debugSecureEnable(void);
SE_Response_t SE_debugSecureDisable(void);
SE_Response_t SE_deviceEraseDisable(void);
SE_Response_t SE_deviceErase(void);

// Device status commands
SE_Response_t SE_getStatus(SE_Status_t *output);
SE_Response_t SE_serialNumber(void *serial);

#elif defined(CRYPTOACC_PRESENT)

SE_Response_t SE_getVersion(uint32_t *version);
SE_Response_t SE_getConfigStatusBits(uint32_t *cfgStatus);
SE_Response_t SE_ackCommand(SE_Command_t *command);

#endif // #if defined(SEMAILBOX_PRESENT)

// Utilities
#if defined(SEMAILBOX_PRESENT)
__STATIC_INLINE bool SE_isCommandCompleted(void);
__STATIC_INLINE SE_Response_t SE_readCommandResponse(void);
#elif defined(CRYPTOACC_PRESENT)
bool SE_isCommandCompleted(void);
uint32_t SE_readExecutedCommand(void);
SE_Response_t SE_readCommandResponse(void);
#endif // #if defined(SEMAILBOX_PRESENT)

__STATIC_INLINE void SE_waitCommandCompletion(void);
__STATIC_INLINE void SE_disableInterrupt(uint32_t flags);
__STATIC_INLINE void SE_enableInterrupt(uint32_t flags);

#if defined(SEMAILBOX_PRESENT)
/***************************************************************************//**
 * @brief
 *   Check whether the running command has completed.
 *
 * @details
 *   This function polls the SE-to-host mailbox interrupt flag.
 *
 * @return True if a command has completed and the result is available
 ******************************************************************************/
__STATIC_INLINE bool SE_isCommandCompleted(void)
{
  return (bool)(SEMAILBOX_HOST->RX_STATUS & SEMAILBOX_RX_STATUS_RXINT);
}
#endif

/***************************************************************************//**
 * @brief
 *   Wait for completion of the current command.
 *
 * @details
 *   This function "busy"-waits until the execution of the ongoing instruction
 *   has completed.
 ******************************************************************************/
__STATIC_INLINE void SE_waitCommandCompletion(void)
{
  /* Wait for completion */
  while (!SE_isCommandCompleted()) {
  }
}

#if defined(SEMAILBOX_PRESENT)
/***************************************************************************//**
 * @brief
 *   Read the status of the previously executed command.
 *
 * @details
 *   This function reads the status of the previously executed command.
 *
 * @note
 *   The command response needs to be read for every executed command, and can
 *   only be read once per executed command (FIFO behavior).
 *
 * @return
 *   One of the SE_RESPONSE return codes:
 *   SE_RESPONSE_OK when the command was executed successfully or a signature
 *   was successfully verified,
 *   SE_RESPONSE_INVALID_COMMAND when the command ID was not recognized,
 *   SE_RESPONSE_AUTHORIZATION_ERROR when the command is not authorized,
 *   SE_RESPONSE_INVALID_SIGNATURE when signature verification failed,
 *   SE_RESPONSE_BUS_ERROR when a bus error was thrown during the command, e.g.
 *   because of conflicting Secure/Non-Secure memory accesses,
 *   SE_RESPONSE_CRYPTO_ERROR on an internal SE failure, or
 *   SE_RESPONSE_INVALID_PARAMETER when an invalid parameter was passed
 ******************************************************************************/
__STATIC_INLINE SE_Response_t SE_readCommandResponse(void)
{
  SE_waitCommandCompletion();
  return (SE_Response_t)(SEMAILBOX_HOST->RX_HEADER & SE_RESPONSE_MASK);
}
#endif // #if defined(SEMAILBOX_PRESENT)

/***************************************************************************//**
 * @brief
 *   Disable one or more SE interrupts.
 *
 * @param[in] flags
 *   SE interrupt sources to disable. Use a bitwise logic OR combination of
 *   valid interrupt flags for the Secure Element module
 *    (SE_CONFIGURATION_(TX/RX)INTEN).
 ******************************************************************************/
__STATIC_INLINE void SE_disableInterrupt(uint32_t flags)
{
#if defined(SEMAILBOX_PRESENT)
  SEMAILBOX_HOST->CONFIGURATION &= ~flags;
#else
  (void) flags;
#endif
}

/***************************************************************************//**
 * @brief
 *   Enable one or more SE interrupts.
 *
 * @param[in] flags
 *   SE interrupt sources to enable. Use a bitwise logic OR combination of
 *   valid interrupt flags for the Secure Element module
 *   (SEMAILBOX_CONFIGURATION_TXINTEN or SEMAILBOX_CONFIGURATION_RXINTEN).
 ******************************************************************************/
__STATIC_INLINE void SE_enableInterrupt(uint32_t flags)
{
#if defined(SEMAILBOX_PRESENT)
  SEMAILBOX_HOST->CONFIGURATION |= flags;
#else
  (void) flags;
#endif
}

#ifdef __cplusplus
}
#endif

/** @} (end addtogroup SE) */
/** @} (end addtogroup emlib) */

#endif /* defined(SEMAILBOX_PRESENT)
       || defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2) */

#endif /* EM_SE_H */
