/***************************************************************************//**
 * @file em_crypto.c
 * @brief Cryptography accelerator peripheral API
 * @version 5.2.2
 *******************************************************************************
 * # License
 * <b>Copyright 2016 Silicon Laboratories, Inc. http://www.silabs.com</b>
 *******************************************************************************
 *
 * 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.@n
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.@n
 * 3. This notice may not be removed or altered from any source distribution.
 *
 * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Labs has no
 * obligation to support this Software. Silicon Labs is providing the
 * Software "AS IS", with no express or implied warranties of any kind,
 * including, but not limited to, any implied warranties of merchantability
 * or fitness for any particular purpose or warranties against infringement
 * of any proprietary rights of a third party.
 *
 * Silicon Labs will not be liable for any consequential, incidental, or
 * special damages, or any other relief, or for any claim by any third party,
 * arising from your use of this Software.
 *
 ******************************************************************************/
#include "em_device.h"

#if defined(CRYPTO_COUNT) && (CRYPTO_COUNT > 0)

#include "em_crypto.h"
#include "em_assert.h"

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

/***************************************************************************//**
 * @addtogroup CRYPTO
 * @{
 ******************************************************************************/

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

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

#define CRYPTO_INSTRUCTIONS_PER_REG              (4)
#define CRYPTO_INSTRUCTIONS_MAX                  (12)
#define CRYPTO_INSTRUCTION_REGS                  (CRYPTO_INSTRUCTIONS_MAX / CRYPTO_INSTRUCTIONS_PER_REG)

#define CRYPTO_SHA1_BLOCK_SIZE_IN_BITS           (512)
#define CRYPTO_SHA1_BLOCK_SIZE_IN_BYTES          (CRYPTO_SHA1_BLOCK_SIZE_IN_BITS / 8)
#define CRYPTO_SHA1_BLOCK_SIZE_IN_32BIT_WORDS    (CRYPTO_SHA1_BLOCK_SIZE_IN_BYTES / sizeof(uint32_t))
#define CRYPTO_SHA1_DIGEST_SIZE_IN_32BIT_WORDS   (CRYPTO_SHA1_DIGEST_SIZE_IN_BYTES / sizeof(uint32_t))

#define CRYPTO_SHA256_BLOCK_SIZE_IN_BITS         (512)
#define CRYPTO_SHA256_BLOCK_SIZE_IN_BYTES        (CRYPTO_SHA256_BLOCK_SIZE_IN_BITS / 8)
#define CRYPTO_SHA256_BLOCK_SIZE_IN_32BIT_WORDS  (CRYPTO_SHA256_BLOCK_SIZE_IN_BYTES / sizeof(uint32_t))

#define CRYPTO_SHA256_DIGEST_SIZE_IN_32BIT_WORDS (CRYPTO_SHA256_DIGEST_SIZE_IN_BYTES / sizeof(uint32_t))

#define PARTIAL_OPERAND_WIDTH_LOG2               (7)  /* 2^7 = 128 */
#define PARTIAL_OPERAND_WIDTH                    (1 << PARTIAL_OPERAND_WIDTH_LOG2)
#define PARTIAL_OPERAND_WIDTH_MASK               (PARTIAL_OPERAND_WIDTH - 1)
#define PARTIAL_OPERAND_WIDTH_IN_BYTES           (PARTIAL_OPERAND_WIDTH / 8)
#define PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS     (PARTIAL_OPERAND_WIDTH_IN_BYTES / sizeof(uint32_t))

#define SWAP32(x)                                (__REV(x))

#define CRYPTO_AES_BLOCKSIZE                     (16)

/*******************************************************************************
 ***********************   STATIC FUNCTIONS   **********************************
 ******************************************************************************/

static inline void CRYPTO_AES_ProcessLoop(CRYPTO_TypeDef *crypto,
                                          uint32_t len,
                                          CRYPTO_DataReg_TypeDef inReg,
                                          uint32_t * in,
                                          CRYPTO_DataReg_TypeDef outReg,
                                          uint32_t * out);

static void CRYPTO_AES_CBCx(CRYPTO_TypeDef *crypto,
                            uint8_t * out,
                            const uint8_t * in,
                            unsigned int len,
                            const uint8_t * key,
                            const uint8_t * iv,
                            bool encrypt,
                            CRYPTO_KeyWidth_TypeDef keyWidth);

static void CRYPTO_AES_CFBx(CRYPTO_TypeDef *crypto,
                            uint8_t * out,
                            const uint8_t * in,
                            unsigned int len,
                            const uint8_t * key,
                            const uint8_t * iv,
                            bool encrypt,
                            CRYPTO_KeyWidth_TypeDef keyWidth);

static void CRYPTO_AES_CTRx(CRYPTO_TypeDef *crypto,
                            uint8_t * out,
                            const uint8_t * in,
                            unsigned int len,
                            const uint8_t * key,
                            uint8_t * ctr,
                            CRYPTO_AES_CtrFuncPtr_TypeDef ctrFunc,
                            CRYPTO_KeyWidth_TypeDef keyWidth);

static void CRYPTO_AES_ECBx(CRYPTO_TypeDef *crypto,
                            uint8_t * out,
                            const uint8_t * in,
                            unsigned int len,
                            const uint8_t * key,
                            bool encrypt,
                            CRYPTO_KeyWidth_TypeDef keyWidth);

static void CRYPTO_AES_OFBx(CRYPTO_TypeDef *crypto,
                            uint8_t * out,
                            const uint8_t * in,
                            unsigned int len,
                            const uint8_t * key,
                            const uint8_t * iv,
                            CRYPTO_KeyWidth_TypeDef keyWidth);

#ifdef USE_VARIABLE_SIZED_DATA_LOADS
/***************************************************************************//**
 * @brief
 *   Write variable sized 32 bit data array (max 128 bits) to a DATAX register
 *
 * @details
 *   Write variable sized 32 bit array (max 128 bits / 4 words) to a DATAX
 *   register in the CRYPTO module.
 *
 * @param[in]  dataReg    The 128 bits DATA register.
 * @param[in]  val        Value of the data to write to the DATA register.
 * @param[in]  valSize    Size of @ref val in number of 32bit words.
 ******************************************************************************/
__STATIC_INLINE
void CRYPTO_DataWriteVariableSize(CRYPTO_DataReg_TypeDef    dataReg,
                                  const CRYPTO_Data_TypeDef val,
                                  int                       valSize)
{
  int i;
  volatile uint32_t * reg = (volatile uint32_t *) dataReg;

  if (valSize < 4) {
    /* Non optimal write of data. */
    for (i = 0; i < valSize; i++) {
      *reg = *val++;
    }
    for (; i < 4; i++) {
      *reg = 0;
    }
  } else {
    CRYPTO_BurstToCrypto(reg, &val[0]);
  }
}
#endif

/** @endcond */

/*******************************************************************************
 **************************   GLOBAL FUNCTIONS   *******************************
 ******************************************************************************/

/***************************************************************************//**
 * @brief
 *   Set the modulus used for wide modular operations.
 *
 * @details
 *   This function sets the modulus to be used by the modular instructions
 *   of the CRYPTO module.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[in]  modulusId
 *   Modulus identifier.
 ******************************************************************************/
void CRYPTO_ModulusSet(CRYPTO_TypeDef *          crypto,
                       CRYPTO_ModulusId_TypeDef  modulusId)
{
  uint32_t temp = crypto->WAC & (~(_CRYPTO_WAC_MODULUS_MASK | _CRYPTO_WAC_MODOP_MASK));

  switch (modulusId) {
    case cryptoModulusBin256:
    case cryptoModulusBin128:
    case cryptoModulusGcmBin128:
    case cryptoModulusEccB233:
    case cryptoModulusEccB163:
#ifdef _CRYPTO_WAC_MODULUS_ECCBIN233N
    case cryptoModulusEccB233Order:
    case cryptoModulusEccB233KOrder:
    case cryptoModulusEccB163Order:
    case cryptoModulusEccB163KOrder:
#endif
      crypto->WAC = temp | modulusId | CRYPTO_WAC_MODOP_BINARY;
      break;

    case cryptoModulusEccP256:
    case cryptoModulusEccP224:
    case cryptoModulusEccP192:
#ifdef _CRYPTO_WAC_MODULUS_ECCPRIME256P
    case cryptoModulusEccP256Order:
    case cryptoModulusEccP224Order:
    case cryptoModulusEccP192Order:
#endif
      crypto->WAC = temp | modulusId | CRYPTO_WAC_MODOP_REGULAR;
      break;

    default:
      /* Unknown modulus identifier. */
      EFM_ASSERT(0);
  }
}

/***************************************************************************//**
 * @brief
 *   Read the key value currently used by the CRYPTO module.
 *
 * @details
 *   Read 128 bits or 256 bits from KEY register in the CRYPTO module.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[in]  val
 *   Value of the data to write to the KEYBUF register.
 *
 * @param[in]  keyWidth
 *   Key width - 128 or 256 bits
 ******************************************************************************/
void CRYPTO_KeyRead(CRYPTO_TypeDef *         crypto,
                    CRYPTO_KeyBuf_TypeDef    val,
                    CRYPTO_KeyWidth_TypeDef  keyWidth)
{
  EFM_ASSERT(val);

  CRYPTO_BurstFromCrypto(&crypto->KEY, &val[0]);
  if (keyWidth == cryptoKey256Bits) {
    CRYPTO_BurstFromCrypto(&crypto->KEY, &val[4]);
  }
}

/***************************************************************************//**
 * @brief
 *   Perform a SHA-1 hash operation on a message.
 *
 * @details
 *   This function performs a SHA-1 hash operation on the message specified by
 *   msg with length msgLen, and returns the message digest in msgDigest.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[in]  msg
 *   Message to hash.
 *
 * @param[in]  msgLen
 *   Length of message in bytes.
 *
 * @param[out] msgDigest
 *   Message digest.
 ******************************************************************************/
void CRYPTO_SHA_1(CRYPTO_TypeDef *             crypto,
                  const uint8_t *              msg,
                  uint64_t                     msgLen,
                  CRYPTO_SHA1_Digest_TypeDef   msgDigest)
{
  uint32_t  temp;
  uint32_t  len;
  int       blockLen;
  uint32_t  shaBlock[CRYPTO_SHA1_BLOCK_SIZE_IN_32BIT_WORDS] =
  {
    /* Initial value */
    0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0
  };
  uint8_t * p8ShaBlock = (uint8_t *) shaBlock;

  /* Initialize crypto module to do SHA-1. */
  crypto->CTRL     = CRYPTO_CTRL_SHA_SHA1;
  crypto->SEQCTRL  = 0;
  crypto->SEQCTRLB = 0;

  /* Set result width of MADD32 operation. */
  CRYPTO_ResultWidthSet(crypto, cryptoResult256Bits);

  /* Write init value to DDATA1.  */
  CRYPTO_DDataWrite(&crypto->DDATA1, shaBlock);

  /* Copy data to DDATA0 and select DDATA0 and DDATA1 for SHA operation. */
  CRYPTO_EXECUTE_2(crypto,
                   CRYPTO_CMD_INSTR_DDATA1TODDATA0,
                   CRYPTO_CMD_INSTR_SELDDATA0DDATA1);

  len = msgLen;

  while (len >= CRYPTO_SHA1_BLOCK_SIZE_IN_BYTES) {
    /* Write block to QDATA1.  */
    CRYPTO_QDataWrite(&crypto->QDATA1BIG, (uint32_t *) msg);

    /* Execute SHA */
    CRYPTO_EXECUTE_3(crypto,
                     CRYPTO_CMD_INSTR_SHA,
                     CRYPTO_CMD_INSTR_MADD32,
                     CRYPTO_CMD_INSTR_DDATA0TODDATA1);

    len -= CRYPTO_SHA1_BLOCK_SIZE_IN_BYTES;
    msg += CRYPTO_SHA1_BLOCK_SIZE_IN_BYTES;
  }

  blockLen = 0;

  /* Build the last (or second to last) block */
  for (; len; len--) {
    p8ShaBlock[blockLen++] = *msg++;
  }

  /* append the '1' bit */
  p8ShaBlock[blockLen++] = 0x80;

  /* if the length is currently above 56 bytes we append zeros
   * then compress.  Then we can fall back to padding zeros and length
   * encoding like normal.
   */
  if (blockLen > 56) {
    while (blockLen < 64)
      p8ShaBlock[blockLen++] = 0;

    /* Write block to QDATA1BIG. */
    CRYPTO_QDataWrite(&crypto->QDATA1BIG, shaBlock);

    /* Execute SHA */
    CRYPTO_EXECUTE_3(crypto,
                     CRYPTO_CMD_INSTR_SHA,
                     CRYPTO_CMD_INSTR_MADD32,
                     CRYPTO_CMD_INSTR_DDATA0TODDATA1);
    blockLen = 0;
  }

  /* pad upto 56 bytes of zeroes */
  while (blockLen < 56)
    p8ShaBlock[blockLen++] = 0;

  /* And finally, encode the message length. */
  {
    uint64_t msgLenInBits = msgLen << 3;
    temp = msgLenInBits >> 32;
    *(uint32_t*)&p8ShaBlock[56] = SWAP32(temp);
    temp = msgLenInBits & 0xFFFFFFFF;
    *(uint32_t*)&p8ShaBlock[60] = SWAP32(temp);
  }

  /* Write block to QDATA1BIG. */
  CRYPTO_QDataWrite(&crypto->QDATA1BIG, shaBlock);

  /* Execute SHA */
  CRYPTO_EXECUTE_3(crypto,
                   CRYPTO_CMD_INSTR_SHA,
                   CRYPTO_CMD_INSTR_MADD32,
                   CRYPTO_CMD_INSTR_DDATA0TODDATA1);

  /* Read resulting message digest from DDATA0BIG.  */
  ((uint32_t*)msgDigest)[0] = crypto->DDATA0BIG;
  ((uint32_t*)msgDigest)[1] = crypto->DDATA0BIG;
  ((uint32_t*)msgDigest)[2] = crypto->DDATA0BIG;
  ((uint32_t*)msgDigest)[3] = crypto->DDATA0BIG;
  ((uint32_t*)msgDigest)[4] = crypto->DDATA0BIG;
  temp = crypto->DDATA0BIG;
  temp = crypto->DDATA0BIG;
  temp = crypto->DDATA0BIG;
}

/***************************************************************************//**
 * @brief
 *   Perform a SHA-256 hash operation on a message.
 *
 * @details
 *   This function performs a SHA-256 hash operation on the message specified
 *   by msg with length msgLen, and returns the message digest in msgDigest.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[in]  msg
 *   Message to hash.
 *
 * @param[in]  msgLen
 *   Length of message in bytes.
 *
 * @param[out] msgDigest
 *   Message digest.
 ******************************************************************************/
void CRYPTO_SHA_256(CRYPTO_TypeDef *             crypto,
                    const uint8_t *              msg,
                    uint64_t                     msgLen,
                    CRYPTO_SHA256_Digest_TypeDef msgDigest)
{
  uint32_t  temp;
  uint32_t  len;
  int       blockLen;
  uint32_t  shaBlock[CRYPTO_SHA256_BLOCK_SIZE_IN_32BIT_WORDS] =
  {
    /* Initial value */
    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
    0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
  };
  uint8_t * p8ShaBlock = (uint8_t *) shaBlock;

  /* Initialize crypyo module to do SHA-256 (SHA-2). */
  crypto->CTRL     = CRYPTO_CTRL_SHA_SHA2;
  crypto->SEQCTRL  = 0;
  crypto->SEQCTRLB = 0;

  /* Set result width of MADD32 operation. */
  CRYPTO_ResultWidthSet(crypto, cryptoResult256Bits);

  /* Write init value to DDATA1.  */
  CRYPTO_DDataWrite(&crypto->DDATA1, shaBlock);

  /* Copy data ot DDATA0 and select DDATA0 and DDATA1 for SHA operation. */
  CRYPTO_EXECUTE_2(crypto,
                   CRYPTO_CMD_INSTR_DDATA1TODDATA0,
                   CRYPTO_CMD_INSTR_SELDDATA0DDATA1);
  len = msgLen;

  while (len >= CRYPTO_SHA256_BLOCK_SIZE_IN_BYTES) {
    /* Write block to QDATA1BIG.  */
    CRYPTO_QDataWrite(&crypto->QDATA1BIG, (uint32_t *) msg);

    /* Execute SHA */
    CRYPTO_EXECUTE_3(crypto,
                     CRYPTO_CMD_INSTR_SHA,
                     CRYPTO_CMD_INSTR_MADD32,
                     CRYPTO_CMD_INSTR_DDATA0TODDATA1);

    len -= CRYPTO_SHA256_BLOCK_SIZE_IN_BYTES;
    msg += CRYPTO_SHA256_BLOCK_SIZE_IN_BYTES;
  }

  blockLen = 0;

  /* Build the last (or second to last) block */
  for (; len; len--) {
    p8ShaBlock[blockLen++] = *msg++;
  }

  /* append the '1' bit */
  p8ShaBlock[blockLen++] = 0x80;

  /* if the length is currently above 56 bytes we append zeros
   * then compress.  Then we can fall back to padding zeros and length
   * encoding like normal.
   */
  if (blockLen > 56) {
    while (blockLen < 64)
      p8ShaBlock[blockLen++] = 0;

    /* Write block to QDATA1BIG. */
    CRYPTO_QDataWrite(&crypto->QDATA1BIG, shaBlock);

    /* Execute SHA */
    CRYPTO_EXECUTE_3(crypto,
                     CRYPTO_CMD_INSTR_SHA,
                     CRYPTO_CMD_INSTR_MADD32,
                     CRYPTO_CMD_INSTR_DDATA0TODDATA1);
    blockLen = 0;
  }

  /* Pad upto 56 bytes of zeroes */
  while (blockLen < 56)
    p8ShaBlock[blockLen++] = 0;

  /* And finally, encode the message length. */
  {
    uint64_t msgLenInBits = msgLen << 3;
    temp = msgLenInBits >> 32;
    *(uint32_t *)&p8ShaBlock[56] = SWAP32(temp);
    temp = msgLenInBits & 0xFFFFFFFF;
    *(uint32_t *)&p8ShaBlock[60] = SWAP32(temp);
  }

  /* Write the final block to QDATA1BIG. */
  CRYPTO_QDataWrite(&crypto->QDATA1BIG, shaBlock);

  /* Execute SHA */
  CRYPTO_EXECUTE_3(crypto,
                   CRYPTO_CMD_INSTR_SHA,
                   CRYPTO_CMD_INSTR_MADD32,
                   CRYPTO_CMD_INSTR_DDATA0TODDATA1);

  /* Read resulting message digest from DDATA0BIG.  */
  CRYPTO_DDataRead(&crypto->DDATA0BIG, (uint32_t *)msgDigest);
}

/***************************************************************************//**
 * @brief
 *   Set 32bit word array to zero.
 *
 * @param[in]  words32bits    Pointer to 32bit word array
 * @param[in]  num32bitWords  Number of 32bit words in array
 ******************************************************************************/
__STATIC_INLINE void cryptoBigintZeroize(uint32_t * words32bits,
                                         int        num32bitWords)
{
  while (num32bitWords--)
    *words32bits++ = 0;
}

/***************************************************************************//**
 * @brief
 *   Increment value of 32bit word array by one.
 *
 * @param[in] words32bits    Pointer to 32bit word array
 * @param[in] num32bitWords  Number of 32bit words in array
 ******************************************************************************/
__STATIC_INLINE void cryptoBigintIncrement(uint32_t * words32bits,
                                           int        num32bitWords)
{
  int i;
  for (i = 0; i < num32bitWords; i++) {
    if (++words32bits[i] != 0) {
      break;
    }
  }
  return;
}

/***************************************************************************//**
 * @brief
 *   Multiply two big integers.
 *
 * @details
 *   This function uses the CRYPTO unit to multiply two big integer operands.
 *   If USE_VARIABLE_SIZED_DATA_LOADS is defined, the sizes of the operands
 *   may be any multiple of 32 bits. If USE_VARIABLE_SIZED_DATA_LOADS is _not_
 *   defined, the sizes of the operands must be a multiple of 128 bits.
 *
 * @param[in]  A        operand A
 * @param[in]  aSize    size of operand A in bits
 * @param[in]  B        operand B
 * @param[in]  bSize    size of operand B in bits
 * @param[out] R        result of multiplication
 * @param[in]  rSize    size of result buffer R in bits
 ******************************************************************************/
void CRYPTO_Mul(CRYPTO_TypeDef * crypto,
                uint32_t * A, int aSize,
                uint32_t * B, int bSize,
                uint32_t * R, int rSize)
{
  int i, j;

  /****************   Initializations   ******************/

#ifdef USE_VARIABLE_SIZED_DATA_LOADS
  int numWordsLastOperandA = (aSize & PARTIAL_OPERAND_WIDTH_MASK) >> 5;
  int numPartialOperandsA = numWordsLastOperandA
                            ? (aSize >> PARTIAL_OPERAND_WIDTH_LOG2) + 1
                            : aSize >> PARTIAL_OPERAND_WIDTH_LOG2;
  int numWordsLastOperandB = (bSize & PARTIAL_OPERAND_WIDTH_MASK) >> 5;
  int numPartialOperandsB = numWordsLastOperandB
                            ? (bSize >> PARTIAL_OPERAND_WIDTH_LOG2) + 1
                            : bSize >> PARTIAL_OPERAND_WIDTH_LOG2;
  int numWordsLastOperandR = (rSize & PARTIAL_OPERAND_WIDTH_MASK) >> 5;
  int numPartialOperandsR = numWordsLastOperandR
                            ? (rSize >> PARTIAL_OPERAND_WIDTH_LOG2) + 1
                            : rSize >> PARTIAL_OPERAND_WIDTH_LOG2;
  EFM_ASSERT(numPartialOperandsA + numPartialOperandsB <= numPartialOperandsR);
#else
  int      numPartialOperandsA = aSize >> PARTIAL_OPERAND_WIDTH_LOG2;
  int      numPartialOperandsB = bSize >> PARTIAL_OPERAND_WIDTH_LOG2;
  EFM_ASSERT((aSize & PARTIAL_OPERAND_WIDTH_MASK) == 0);
  EFM_ASSERT((bSize & PARTIAL_OPERAND_WIDTH_MASK) == 0);
#endif
  EFM_ASSERT(aSize + bSize <= rSize);

  /* Set R to zero. */
  cryptoBigintZeroize(R, rSize >> 5);

  /* Set multiplication width. */
  crypto->WAC = CRYPTO_WAC_MULWIDTH_MUL128 | CRYPTO_WAC_RESULTWIDTH_256BIT;

  /* Setup DMA request signalling in order for MCU to run in parallel with
     CRYPTO instruction sequence execution, and prepare data loading which
     can take place immediately when CRYPTO is ready inside the instruction
     sequence. */
  crypto->CTRL =
    CRYPTO_CTRL_DMA0RSEL_DATA0 | CRYPTO_CTRL_DMA0MODE_FULL
    | CRYPTO_CTRL_DMA1RSEL_DATA1 | CRYPTO_CTRL_DMA1MODE_FULL;

  CRYPTO_EXECUTE_4(crypto,
                   CRYPTO_CMD_INSTR_CCLR,    /* Carry = 0 */
                   CRYPTO_CMD_INSTR_CLR,     /* DDATA0 = 0 */
                   /* clear result accumulation register */
                   CRYPTO_CMD_INSTR_DDATA0TODDATA2,
                   CRYPTO_CMD_INSTR_SELDDATA1DDATA3);
  /*
     register map:
     DDATA0: working register
     DDATA1: B(j)
     DDATA2: R(i+j+1) and R(i+j), combined with DMA entry for B(j)
     DDATA3: A(i)
   */

  CRYPTO_SEQ_LOAD_10(crypto,
                     /* Temporarily load partial operand B(j) to DATA0. */
                     /* R(i+j+1) is still in DATA1 */
                     CRYPTO_CMD_INSTR_DMA0TODATA,
                     /* Move B(j) to DDATA1 */
                     CRYPTO_CMD_INSTR_DDATA2TODDATA1,

                     /* Restore previous partial result (now R(i+j)) */
                     CRYPTO_CMD_INSTR_DATA1TODATA0,

                     /* Load next partial result R(i+j+1) */
                     CRYPTO_CMD_INSTR_DMA1TODATA,

                     /* Execute partial multiplication A(i)inDDATA1 * B(j)inDDATA3*/
                     CRYPTO_CMD_INSTR_MULO,

                     /* Add the result to the previous partial result */
                     /* AND take the previous carry value into account */
                     /* at the right place (bit 128, ADDIC instruction */
                     CRYPTO_CMD_INSTR_SELDDATA0DDATA2,
                     CRYPTO_CMD_INSTR_ADDIC,

                     /* Save the new partial result (lower half) */
                     CRYPTO_CMD_INSTR_DDATA0TODDATA2,
                     CRYPTO_CMD_INSTR_DATATODMA0,
                     /* Reset the operand selector for next*/
                     CRYPTO_CMD_INSTR_SELDDATA2DDATA3
                     );

  /****************   End Initializations   ******************/

  for (i = 0; i < numPartialOperandsA; i++) {
    /* Load partial operand #1 A>>(i*PARTIAL_OPERAND_WIDTH) to DDATA1. */
#ifdef USE_VARIABLE_SIZED_DATA_LOADS
    if ( (numWordsLastOperandA != 0) && (i == numPartialOperandsA - 1) ) {
      CRYPTO_DataWriteVariableSize(&crypto->DATA2,
                                   &A[i * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS],
                                   numWordsLastOperandA);
    } else {
      CRYPTO_DataWrite(&crypto->DATA2, &A[i * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS]);
    }
#else
    CRYPTO_DataWrite(&crypto->DATA2, &A[i * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS]);
#endif

    /* Load partial result in R>>(i*PARTIAL_OPERAND_WIDTH) to DATA1. */
#ifdef USE_VARIABLE_SIZED_DATA_LOADS
    if ( (numWordsLastOperandR != 0) && (i == numPartialOperandsR - 1) ) {
      CRYPTO_DataWriteVariableSize(&crypto->DATA1,
                                   &R[i * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS],
                                   numWordsLastOperandR);
    } else {
      CRYPTO_DataWrite(&crypto->DATA1, &R[i * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS]);
    }
#else
    CRYPTO_DataWrite(&crypto->DATA1, &R[i * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS]);
#endif

    /* Clear carry */
    crypto->CMD = CRYPTO_CMD_INSTR_CCLR;

    /* Setup number of sequence iterations and block size. */
    crypto->SEQCTRL = CRYPTO_SEQCTRL_BLOCKSIZE_16BYTES
                      | (PARTIAL_OPERAND_WIDTH_IN_BYTES * numPartialOperandsB);

    /* Execute the MULtiply instruction sequence. */
    CRYPTO_InstructionSequenceExecute(crypto);

    for (j = 0; j < numPartialOperandsB; j++) {
      /* Load partial operand 2 B>>(j*`PARTIAL_OPERAND_WIDTH) to DDATA2
         (via DATA0). */
#ifdef USE_VARIABLE_SIZED_DATA_LOADS
      if ( (numWordsLastOperandB != 0) && (j == numPartialOperandsB - 1) ) {
        CRYPTO_DataWriteVariableSize(&crypto->DATA0,
                                     &B[j * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS],
                                     numWordsLastOperandB);
      } else {
        CRYPTO_DataWrite(&crypto->DATA0,
                         &B[j * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS]);
      }
#else
      CRYPTO_DataWrite(&crypto->DATA0,
                       &B[j * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS]);
#endif

      /* Load most significant partial result
         R>>((i+j+1)*`PARTIAL_OPERAND_WIDTH) into DATA1. */
#ifdef USE_VARIABLE_SIZED_DATA_LOADS
      if ( (numWordsLastOperandR != 0) && ( (i + j + 1) == numPartialOperandsR - 1) ) {
        CRYPTO_DataWriteVariableSize(&crypto->DATA1,
                                     &R[(i + j + 1) * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS],
                                     numWordsLastOperandR);
      } else {
        CRYPTO_DataWrite(&crypto->DATA1,
                         &R[(i + j + 1) * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS]);
      }
#else
      CRYPTO_DataWrite(&crypto->DATA1,
                       &R[(i + j + 1) * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS]);
#endif
      /* Store least significant partial result */
      CRYPTO_DataRead(&crypto->DATA0,
                      &R[(i + j) * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS]);
    } /* for (j=0; j<numPartialOperandsB; j++) */

    /* Handle carry at the end of the inner loop. */
    if (CRYPTO_CarryIsSet(crypto)) {
      cryptoBigintIncrement(&R[(i + numPartialOperandsB + 1)
                               * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS],
                            (numPartialOperandsA - i - 1)
                            * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS);
    }

    CRYPTO_DataRead(&crypto->DATA1,
                    &R[(i + numPartialOperandsB)
                       * PARTIAL_OPERAND_WIDTH_IN_32BIT_WORDS]);
  } /* for (i=0; i<numPartialOperandsA; i++) */
}

/***************************************************************************//**
 * @brief
 *   AES Cipher-block chaining (CBC) cipher mode encryption/decryption, 128 bit key.
 *
 * @details
 *   Encryption:
 * @verbatim
 *           Plaintext                  Plaintext
 *               |                          |
 *               V                          V
 * InitVector ->XOR        +-------------->XOR
 *               |         |                |
 *               V         |                V
 *       +--------------+  |        +--------------+
 * Key ->| Block cipher |  |  Key ->| Block cipher |
 *       |  encryption  |  |        |  encryption  |
 *       +--------------+  |        +--------------+
 *               |---------+                |
 *               V                          V
 *           Ciphertext                 Ciphertext
 * @endverbatim
 *   Decryption:
 * @verbatim
 *         Ciphertext                 Ciphertext
 *              |----------+                |
 *              V          |                V
 *       +--------------+  |        +--------------+
 * Key ->| Block cipher |  |  Key ->| Block cipher |
 *       |  decryption  |  |        |  decryption  |
 *       +--------------+  |        +--------------+
 *               |         |                |
 *               V         |                V
 * InitVector ->XOR        +-------------->XOR
 *               |                          |
 *               V                          V
 *           Plaintext                  Plaintext
 * @endverbatim
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   When doing encryption, this is the 128 bit encryption key. When doing
 *   decryption, this is the 128 bit decryption key. The decryption key may
 *   be generated from the encryption key with CRYPTO_AES_DecryptKey128().
 *   If this argument is null, the key will not be loaded, as it is assumed
 *   the key has been loaded into KEYHA previously.
 *
 * @param[in] iv
 *   128 bit initialization vector to use.
 *
 * @param[in] encrypt
 *   Set to true to encrypt, false to decrypt.
 ******************************************************************************/
void CRYPTO_AES_CBC128(CRYPTO_TypeDef *  crypto,
                       uint8_t *         out,
                       const uint8_t *   in,
                       unsigned int      len,
                       const uint8_t *   key,
                       const uint8_t *   iv,
                       bool              encrypt)
{
  crypto->CTRL = CRYPTO_CTRL_AES_AES128;
  CRYPTO_AES_CBCx(crypto, out, in, len, key, iv, encrypt, cryptoKey128Bits);
}

/***************************************************************************//**
 * @brief
 *   AES Cipher-block chaining (CBC) cipher mode encryption/decryption, 256 bit
 *   key.
 *
 * @details
 *   Please see CRYPTO_AES_CBC128() for CBC figure.
 *
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   When doing encryption, this is the 256 bit encryption key. When doing
 *   decryption, this is the 256 bit decryption key. The decryption key may
 *   be generated from the encryption key with CRYPTO_AES_DecryptKey256().
 *
 * @param[in] iv
 *   128 bit initialization vector to use.
 *
 * @param[in] encrypt
 *   Set to true to encrypt, false to decrypt.
 ******************************************************************************/
void CRYPTO_AES_CBC256(CRYPTO_TypeDef *  crypto,
                       uint8_t *         out,
                       const uint8_t *   in,
                       unsigned int      len,
                       const uint8_t *   key,
                       const uint8_t *   iv,
                       bool              encrypt)
{
  crypto->CTRL = CRYPTO_CTRL_AES_AES256;
  CRYPTO_AES_CBCx(crypto, out, in, len, key, iv, encrypt, cryptoKey256Bits);
}

/***************************************************************************//**
 * @brief
 *   AES Cipher feedback (CFB) cipher mode encryption/decryption, 128 bit key.
 *
 * @details
 *   Encryption:
 * @verbatim
 *           InitVector    +----------------+
 *               |         |                |
 *               V         |                V
 *       +--------------+  |        +--------------+
 * Key ->| Block cipher |  |  Key ->| Block cipher |
 *       |  encryption  |  |        |  encryption  |
 *       +--------------+  |        +--------------+
 *               |         |                |
 *               V         |                V
 *  Plaintext ->XOR        |   Plaintext ->XOR
 *               |---------+                |
 *               V                          V
 *           Ciphertext                 Ciphertext
 * @endverbatim
 *   Decryption:
 * @verbatim
 *          InitVector     +----------------+
 *               |         |                |
 *               V         |                V
 *       +--------------+  |        +--------------+
 * Key ->| Block cipher |  |  Key ->| Block cipher |
 *       |  encryption  |  |        |  encryption  |
 *       +--------------+  |        +--------------+
 *               |         |                |
 *               V         |                V
 *              XOR<- Ciphertext           XOR<- Ciphertext
 *               |                          |
 *               V                          V
 *           Plaintext                  Plaintext
 * @endverbatim
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   128 bit encryption key is used for both encryption and decryption modes.
 *
 * @param[in] iv
 *   128 bit initialization vector to use.
 *
 * @param[in] encrypt
 *   Set to true to encrypt, false to decrypt.
 ******************************************************************************/
void CRYPTO_AES_CFB128(CRYPTO_TypeDef *  crypto,
                       uint8_t *         out,
                       const uint8_t *   in,
                       unsigned int      len,
                       const uint8_t *   key,
                       const uint8_t *   iv,
                       bool              encrypt)
{
  crypto->CTRL = CRYPTO_CTRL_AES_AES128;
  CRYPTO_AES_CFBx(crypto, out, in, len, key, iv, encrypt, cryptoKey128Bits);
}

/***************************************************************************//**
 * @brief
 *   AES Cipher feedback (CFB) cipher mode encryption/decryption, 256 bit key.
 *
 * @details
 *   Please see CRYPTO_AES_CFB128() for CFB figure.
 *
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   256 bit encryption key is used for both encryption and decryption modes.
 *
 * @param[in] iv
 *   128 bit initialization vector to use.
 *
 * @param[in] encrypt
 *   Set to true to encrypt, false to decrypt.
 ******************************************************************************/
void CRYPTO_AES_CFB256(CRYPTO_TypeDef *  crypto,
                       uint8_t *         out,
                       const uint8_t *   in,
                       unsigned int      len,
                       const uint8_t *   key,
                       const uint8_t *   iv,
                       bool              encrypt)
{
  crypto->CTRL = CRYPTO_CTRL_AES_AES256;
  CRYPTO_AES_CFBx(crypto, out, in, len, key, iv, encrypt, cryptoKey256Bits);
}

/***************************************************************************//**
 * @brief
 *   AES Counter (CTR) cipher mode encryption/decryption, 128 bit key.
 *
 * @details
 *   Encryption:
 * @verbatim
 *           Counter                    Counter
 *              |                          |
 *              V                          V
 *       +--------------+           +--------------+
 * Key ->| Block cipher |     Key ->| Block cipher |
 *       |  encryption  |           |  encryption  |
 *       +--------------+           +--------------+
 *              |                          |
 * Plaintext ->XOR            Plaintext ->XOR
 *              |                          |
 *              V                          V
 *         Ciphertext                 Ciphertext
 * @endverbatim
 *   Decryption:
 * @verbatim
 *           Counter                    Counter
 *              |                          |
 *              V                          V
 *       +--------------+           +--------------+
 * Key ->| Block cipher |     Key ->| Block cipher |
 *       |  encryption  |           |  encryption  |
 *       +--------------+           +--------------+
 *               |                          |
 * Ciphertext ->XOR           Ciphertext ->XOR
 *               |                          |
 *               V                          V
 *           Plaintext                  Plaintext
 * @endverbatim
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   128 bit encryption key.
 *   If this argument is null, the key will not be loaded, as it is assumed
 *   the key has been loaded into KEYHA previously.
 *
 * @param[in,out] ctr
 *   128 bit initial counter value. The counter is updated after each AES
 *   block encoding through use of @p ctrFunc.
 *
 * @param[in] ctrFunc
 *   Function used to update counter value. Not supported by CRYPTO.
 *   This parameter is included in order for backwards compatibility with
 *   the EFM32 em_aes.h API.
 ******************************************************************************/
void CRYPTO_AES_CTR128(CRYPTO_TypeDef *  crypto,
                       uint8_t *         out,
                       const uint8_t *   in,
                       unsigned int      len,
                       const uint8_t *   key,
                       uint8_t *         ctr,
                       CRYPTO_AES_CtrFuncPtr_TypeDef ctrFunc)
{
  crypto->CTRL = CRYPTO_CTRL_AES_AES128;
  CRYPTO_AES_CTRx(crypto, out, in, len, key, ctr, ctrFunc, cryptoKey128Bits);
}

/***************************************************************************//**
 * @brief
 *   AES Counter (CTR) cipher mode encryption/decryption, 256 bit key.
 *
 * @details
 *   Please see CRYPTO_AES_CTR128() for CTR figure.
 *
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   256 bit encryption key.
 *
 * @param[in,out] ctr
 *   128 bit initial counter value. The counter is updated after each AES
 *   block encoding through use of @p ctrFunc.
 *
 * @param[in] ctrFunc
 *   Function used to update counter value. Not supported by CRYPTO.
 *   This parameter is included in order for backwards compatibility with
 *   the EFM32 em_aes.h API.
 ******************************************************************************/
void CRYPTO_AES_CTR256(CRYPTO_TypeDef *  crypto,
                       uint8_t *         out,
                       const uint8_t *   in,
                       unsigned int      len,
                       const uint8_t *   key,
                       uint8_t *         ctr,
                       CRYPTO_AES_CtrFuncPtr_TypeDef ctrFunc)
{
  crypto->CTRL = CRYPTO_CTRL_AES_AES256;
  CRYPTO_AES_CTRx(crypto, out, in, len, key, ctr, ctrFunc, cryptoKey256Bits);
}

/***************************************************************************//**
 * @brief
 *   Update last 32 bits of 128 bit counter, by incrementing with 1.
 *
 * @details
 *   Notice that no special consideration is given to possible wrap around. If
 *   32 least significant bits are 0xFFFFFFFF, they will be updated to 0x00000000,
 *   ignoring overflow.
 *
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in,out] ctr
 *   Buffer holding 128 bit counter to be updated.
 ******************************************************************************/
void CRYPTO_AES_CTRUpdate32Bit(uint8_t * ctr)
{
  uint32_t * _ctr = (uint32_t *) ctr;

  _ctr[3] = __REV(__REV(_ctr[3]) + 1);
}

/***************************************************************************//**
 * @brief
 *   Generate 128 bit AES decryption key from 128 bit encryption key. The
 *   decryption key is used for some cipher modes when decrypting.
 *
 * @details
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place 128 bit decryption key. Must be at least 16 bytes long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding 128 bit encryption key. Must be at least 16 bytes long.
 ******************************************************************************/
void CRYPTO_AES_DecryptKey128(CRYPTO_TypeDef *  crypto,
                              uint8_t *         out,
                              const uint8_t *   in)
{
  uint32_t       * _out = (uint32_t *) out;
  const uint32_t * _in  = (const uint32_t *) in;

  /* Setup CRYPTO in AES-128 mode. */
  crypto->CTRL = CRYPTO_CTRL_AES_AES128;

  /* Load key */
  CRYPTO_BurstToCrypto(&crypto->KEYBUF, &_in[0]);

  /* Do dummy encryption to generate decrypt key */
  crypto->CMD  = CRYPTO_CMD_INSTR_AESENC;

  /* Save decryption key */
  CRYPTO_BurstFromCrypto(&crypto->KEY, &_out[0]);
}

/***************************************************************************//**
 * @brief
 *   Generate 256 bit AES decryption key from 256 bit encryption key. The
 *   decryption key is used for some cipher modes when decrypting.
 *
 * @details
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place 256 bit decryption key. Must be at least 32 bytes long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding 256 bit encryption key. Must be at least 32 bytes long.
 ******************************************************************************/
void CRYPTO_AES_DecryptKey256(CRYPTO_TypeDef *  crypto,
                              uint8_t *         out,
                              const uint8_t *   in)
{
  uint32_t       * _out = (uint32_t *) out;
  const uint32_t * _in  = (const uint32_t *) in;

  /* Setup CRYPTO in AES-256 mode. */
  crypto->CTRL = CRYPTO_CTRL_AES_AES256;

  /* Load key */
  CRYPTO_BurstToCrypto(&crypto->KEYBUF, &_in[0]);
  CRYPTO_BurstToCrypto(&crypto->KEYBUF, &_in[4]);

  /* Do dummy encryption to generate decrypt key */
  crypto->CMD  = CRYPTO_CMD_INSTR_AESENC;

  /* Save decryption key */
  CRYPTO_BurstFromCrypto(&crypto->KEY, &_out[0]);
  CRYPTO_BurstFromCrypto(&crypto->KEY, &_out[4]);
}

/***************************************************************************//**
 * @brief
 *   AES Electronic Codebook (ECB) cipher mode encryption/decryption,
 *   128 bit key.
 *
 * @details
 *   Encryption:
 * @verbatim
 *          Plaintext                  Plaintext
 *              |                          |
 *              V                          V
 *       +--------------+           +--------------+
 * Key ->| Block cipher |     Key ->| Block cipher |
 *       |  encryption  |           |  encryption  |
 *       +--------------+           +--------------+
 *              |                          |
 *              V                          V
 *         Ciphertext                 Ciphertext
 * @endverbatim
 *   Decryption:
 * @verbatim
 *         Ciphertext                 Ciphertext
 *              |                          |
 *              V                          V
 *       +--------------+           +--------------+
 * Key ->| Block cipher |     Key ->| Block cipher |
 *       |  decryption  |           |  decryption  |
 *       +--------------+           +--------------+
 *              |                          |
 *              V                          V
 *          Plaintext                  Plaintext
 * @endverbatim
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   When doing encryption, this is the 128 bit encryption key. When doing
 *   decryption, this is the 128 bit decryption key. The decryption key may
 *   be generated from the encryption key with CRYPTO_AES_DecryptKey128().
 *
 * @param[in] encrypt
 *   Set to true to encrypt, false to decrypt.
 ******************************************************************************/
void CRYPTO_AES_ECB128(CRYPTO_TypeDef *  crypto,
                       uint8_t *         out,
                       const uint8_t *   in,
                       unsigned int      len,
                       const uint8_t *   key,
                       bool              encrypt)
{
  crypto->CTRL = CRYPTO_CTRL_AES_AES128;
  CRYPTO_AES_ECBx(crypto, out, in, len, key, encrypt, cryptoKey128Bits);
}

/***************************************************************************//**
 * @brief
 *   AES Electronic Codebook (ECB) cipher mode encryption/decryption,
 *   256 bit key.
 *
 * @details
 *   Please see CRYPTO_AES_ECB128() for ECB figure.
 *
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   When doing encryption, this is the 256 bit encryption key. When doing
 *   decryption, this is the 256 bit decryption key. The decryption key may
 *   be generated from the encryption key with CRYPTO_AES_DecryptKey256().
 *
 * @param[in] encrypt
 *   Set to true to encrypt, false to decrypt.
 ******************************************************************************/
void CRYPTO_AES_ECB256(CRYPTO_TypeDef *  crypto,
                       uint8_t *         out,
                       const uint8_t *   in,
                       unsigned int      len,
                       const uint8_t *   key,
                       bool              encrypt)
{
  crypto->CTRL = CRYPTO_CTRL_AES_AES256;
  CRYPTO_AES_ECBx(crypto, out, in, len, key, encrypt, cryptoKey256Bits);
}

/***************************************************************************//**
 * @brief
 *   AES Output feedback (OFB) cipher mode encryption/decryption, 128 bit key.
 *
 * @details
 *   Encryption:
 * @verbatim
 *          InitVector    +----------------+
 *              |         |                |
 *              V         |                V
 *       +--------------+ |        +--------------+
 * Key ->| Block cipher | |  Key ->| Block cipher |
 *       |  encryption  | |        |  encryption  |
 *       +--------------+ |        +--------------+
 *              |         |                |
 *              |---------+                |
 *              V                          V
 * Plaintext ->XOR            Plaintext ->XOR
 *              |                          |
 *              V                          V
 *         Ciphertext                 Ciphertext
 * @endverbatim
 *   Decryption:
 * @verbatim
 *          InitVector    +----------------+
 *              |         |                |
 *              V         |                V
 *       +--------------+ |        +--------------+
 * Key ->| Block cipher | |  Key ->| Block cipher |
 *       |  encryption  | |        |  encryption  |
 *       +--------------+ |        +--------------+
 *              |         |                |
 *              |---------+                |
 *              V                          V
 * Ciphertext ->XOR           Ciphertext ->XOR
 *              |                          |
 *              V                          V
 *          Plaintext                  Plaintext
 * @endverbatim
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   128 bit encryption key.
 *
 * @param[in] iv
 *   128 bit initialization vector to use.
 ******************************************************************************/
void CRYPTO_AES_OFB128(CRYPTO_TypeDef *  crypto,
                       uint8_t *         out,
                       const uint8_t *   in,
                       unsigned int      len,
                       const uint8_t *   key,
                       const uint8_t *   iv)
{
  crypto->CTRL = CRYPTO_CTRL_AES_AES128;
  CRYPTO_AES_OFBx(crypto, out, in, len, key, iv, cryptoKey128Bits);
}

/***************************************************************************//**
 * @brief
 *   AES Output feedback (OFB) cipher mode encryption/decryption, 256 bit key.
 *
 * @details
 *   Please see CRYPTO_AES_OFB128() for OFB figure.
 *
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   256 bit encryption key.
 *
 * @param[in] iv
 *   128 bit initialization vector to use.
 ******************************************************************************/
void CRYPTO_AES_OFB256(CRYPTO_TypeDef *  crypto,
                       uint8_t *         out,
                       const uint8_t *   in,
                       unsigned int      len,
                       const uint8_t *   key,
                       const uint8_t *   iv)
{
  crypto->CTRL = CRYPTO_CTRL_AES_AES256;
  CRYPTO_AES_OFBx(crypto, out, in, len, key, iv, cryptoKey256Bits);
}

/*******************************************************************************
 **************************   LOCAL FUNCTIONS   *******************************
 ******************************************************************************/

/***************************************************************************//**
 * @brief
 *   Cipher-block chaining (CBC) cipher mode encryption/decryption, 128/256 bit key.
 *
 * @details
 *   Please see CRYPTO_AES_CBC128() for CBC figure.
 *
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   When doing encryption, this is the 256 bit encryption key. When doing
 *   decryption, this is the 256 bit decryption key. The decryption key may
 *   be generated from the encryption key with CRYPTO_AES_DecryptKey256().
 *
 * @param[in] iv
 *   128 bit initialization vector to use.
 *
 * @param[in] encrypt
 *   Set to true to encrypt, false to decrypt.
 *
 * @param[in] keyWidth
 *   Set to cryptoKey128Bits or cryptoKey256Bits.
 ******************************************************************************/
static void CRYPTO_AES_CBCx(CRYPTO_TypeDef *  crypto,
                            uint8_t *         out,
                            const uint8_t *   in,
                            unsigned int      len,
                            const uint8_t *   key,
                            const uint8_t *   iv,
                            bool              encrypt,
                            CRYPTO_KeyWidth_TypeDef keyWidth)
{
  EFM_ASSERT(!(len % CRYPTO_AES_BLOCKSIZE));

  /* Initialize control registers. */
  crypto->WAC = 0;

  CRYPTO_KeyBufWrite(crypto, (uint32_t *)key, keyWidth);

  if (encrypt) {
    CRYPTO_DataWrite(&crypto->DATA0, (uint32_t *)iv);

    crypto->SEQ0 = CRYPTO_CMD_INSTR_DATA1TODATA0XOR << _CRYPTO_SEQ0_INSTR0_SHIFT
                   | CRYPTO_CMD_INSTR_AESENC        << _CRYPTO_SEQ0_INSTR1_SHIFT;

    CRYPTO_AES_ProcessLoop(crypto, len,
                           &crypto->DATA1, (uint32_t *) in,
                           &crypto->DATA0, (uint32_t *) out);
  } else {
    CRYPTO_DataWrite(&crypto->DATA2, (uint32_t *) iv);

    crypto->SEQ0 = CRYPTO_CMD_INSTR_DATA1TODATA0      << _CRYPTO_SEQ0_INSTR0_SHIFT
                   | CRYPTO_CMD_INSTR_AESDEC          << _CRYPTO_SEQ0_INSTR1_SHIFT
                   | CRYPTO_CMD_INSTR_DATA2TODATA0XOR << _CRYPTO_SEQ0_INSTR2_SHIFT
                   | CRYPTO_CMD_INSTR_DATA1TODATA2    << _CRYPTO_SEQ0_INSTR3_SHIFT;

    crypto->SEQ1 = 0;

    /* The following call is equivalent to the last call in the
       'if( encrypt )' branch. However moving this
       call outside the conditional scope results in slightly poorer
       performance for some compiler optimizations. */
    CRYPTO_AES_ProcessLoop(crypto, len,
                           &crypto->DATA1, (uint32_t *) in,
                           &crypto->DATA0, (uint32_t *) out);
  }
}

/***************************************************************************//**
 * @brief
 *   Cipher feedback (CFB) cipher mode encryption/decryption, 128/256 bit key.
 *
 * @details
 *   Please see CRYPTO_AES_CFB128() for CFB figure.
 *
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   256 bit encryption key is used for both encryption and decryption modes.
 *
 * @param[in] iv
 *   128 bit initialization vector to use.
 *
 * @param[in] encrypt
 *   Set to true to encrypt, false to decrypt.
 *
 * @param[in] keyWidth
 *   Set to cryptoKey128Bits or cryptoKey256Bits.
 ******************************************************************************/
static void CRYPTO_AES_CFBx(CRYPTO_TypeDef *  crypto,
                            uint8_t *         out,
                            const uint8_t *   in,
                            unsigned int      len,
                            const uint8_t *   key,
                            const uint8_t *   iv,
                            bool              encrypt,
                            CRYPTO_KeyWidth_TypeDef keyWidth)
{
  EFM_ASSERT(!(len % CRYPTO_AES_BLOCKSIZE));

  /* Initialize control registers. */
  crypto->WAC = 0;

  /* Load Key */
  CRYPTO_KeyBufWrite(crypto, (uint32_t *)key, keyWidth);

  /* Load instructions to CRYPTO sequencer. */
  if (encrypt) {
    /* Load IV */
    CRYPTO_DataWrite(&crypto->DATA0, (uint32_t *)iv);

    crypto->SEQ0 = CRYPTO_CMD_INSTR_AESENC            << _CRYPTO_SEQ0_INSTR0_SHIFT
                   | CRYPTO_CMD_INSTR_DATA1TODATA0XOR << _CRYPTO_SEQ0_INSTR1_SHIFT;

    CRYPTO_AES_ProcessLoop(crypto, len,
                           &crypto->DATA1, (uint32_t *)in,
                           &crypto->DATA0, (uint32_t *)out
                           );
  } else {
    /* Load IV */
    CRYPTO_DataWrite(&crypto->DATA2, (uint32_t *)iv);

    crypto->SEQ0 = CRYPTO_CMD_INSTR_DATA2TODATA0      << _CRYPTO_SEQ0_INSTR0_SHIFT
                   | CRYPTO_CMD_INSTR_AESENC          << _CRYPTO_SEQ0_INSTR1_SHIFT
                   | CRYPTO_CMD_INSTR_DATA1TODATA0XOR << _CRYPTO_SEQ0_INSTR2_SHIFT
                   | CRYPTO_CMD_INSTR_DATA1TODATA2    << _CRYPTO_SEQ0_INSTR3_SHIFT;
    crypto->SEQ1 = 0;

    CRYPTO_AES_ProcessLoop(crypto, len,
                           &crypto->DATA1, (uint32_t *)in,
                           &crypto->DATA0, (uint32_t *)out
                           );
  }
}

/***************************************************************************//**
 * @brief
 *   Counter (CTR) cipher mode encryption/decryption, 128/256 bit key.
 *
 * @details
 *   Please see CRYPTO_AES_CTR128() for CTR figure.
 *
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   256 bit encryption key.
 *
 * @param[in,out] ctr
 *   128 bit initial counter value. The counter is updated after each AES
 *   block encoding through use of @p ctrFunc.
 *
 * @param[in] ctrFunc
 *   Function used to update counter value. Not supported by CRYPTO.
 *   This parameter is included in order for backwards compatibility with
 *   the EFM32 em_aes.h API.
 *
 * @param[in] keyWidth
 *   Set to cryptoKey128Bits or cryptoKey256Bits.
 ******************************************************************************/
static void CRYPTO_AES_CTRx(CRYPTO_TypeDef *  crypto,
                            uint8_t *         out,
                            const uint8_t *   in,
                            unsigned int      len,
                            const uint8_t *   key,
                            uint8_t *         ctr,
                            CRYPTO_AES_CtrFuncPtr_TypeDef ctrFunc,
                            CRYPTO_KeyWidth_TypeDef keyWidth)
{
  (void) ctrFunc;

  EFM_ASSERT(!(len % CRYPTO_AES_BLOCKSIZE));

  /* Initialize control registers. */
  crypto->CTRL |= CRYPTO_CTRL_INCWIDTH_INCWIDTH4;
  crypto->WAC   = 0;

  CRYPTO_KeyBufWrite(crypto, (uint32_t *)key, keyWidth);

  CRYPTO_DataWrite(&crypto->DATA1, (uint32_t *) ctr);

  crypto->SEQ0 = CRYPTO_CMD_INSTR_DATA1TODATA0    << _CRYPTO_SEQ0_INSTR0_SHIFT
                 | CRYPTO_CMD_INSTR_AESENC        << _CRYPTO_SEQ0_INSTR1_SHIFT
                 | CRYPTO_CMD_INSTR_DATA0TODATA3  << _CRYPTO_SEQ0_INSTR2_SHIFT
                 | CRYPTO_CMD_INSTR_DATA1INC      << _CRYPTO_SEQ0_INSTR3_SHIFT;

  crypto->SEQ1 = CRYPTO_CMD_INSTR_DATA2TODATA0XOR << _CRYPTO_SEQ1_INSTR4_SHIFT;

  CRYPTO_AES_ProcessLoop(crypto, len,
                         &crypto->DATA2, (uint32_t *) in,
                         &crypto->DATA0, (uint32_t *) out);

  CRYPTO_DataRead(&crypto->DATA1, (uint32_t *) ctr);
}

/***************************************************************************//**
 * @brief
 *   Electronic Codebook (ECB) cipher mode encryption/decryption, 128/256 bit key.
 *
 * @details
 *   Please see CRYPTO_AES_ECB128() for ECB figure.
 *
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   When doing encryption, this is the 256 bit encryption key. When doing
 *   decryption, this is the 256 bit decryption key. The decryption key may
 *   be generated from the encryption key with CRYPTO_AES_DecryptKey256().
 *
 * @param[in] encrypt
 *   Set to true to encrypt, false to decrypt.
 *
 * @param[in] keyWidth
 *   Set to cryptoKey128Bits or cryptoKey256Bits.
 ******************************************************************************/
static void CRYPTO_AES_ECBx(CRYPTO_TypeDef *  crypto,
                            uint8_t *         out,
                            const uint8_t *   in,
                            unsigned int      len,
                            const uint8_t *   key,
                            bool              encrypt,
                            CRYPTO_KeyWidth_TypeDef keyWidth)
{
  EFM_ASSERT(!(len % CRYPTO_AES_BLOCKSIZE));

  crypto->WAC = 0;

  CRYPTO_KeyBufWrite(crypto, (uint32_t *)key, keyWidth);

  if (encrypt) {
    crypto->SEQ0 = CRYPTO_CMD_INSTR_AESENC         << _CRYPTO_SEQ0_INSTR0_SHIFT
                   | CRYPTO_CMD_INSTR_DATA0TODATA1 << _CRYPTO_SEQ0_INSTR1_SHIFT;
  } else {
    crypto->SEQ0 = CRYPTO_CMD_INSTR_AESDEC         << _CRYPTO_SEQ0_INSTR0_SHIFT
                   | CRYPTO_CMD_INSTR_DATA0TODATA1 << _CRYPTO_SEQ0_INSTR1_SHIFT;
  }

  CRYPTO_AES_ProcessLoop(crypto, len,
                         &crypto->DATA0, (uint32_t *) in,
                         &crypto->DATA1, (uint32_t *) out);
}

/***************************************************************************//**
 * @brief
 *   Output feedback (OFB) cipher mode encryption/decryption, 128/256 bit key.
 *
 * @details
 *   Please see CRYPTO_AES_OFB128() for OFB figure.
 *
 *   Please refer to general comments on layout and byte ordering of parameters.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] key
 *   256 bit encryption key.
 *
 * @param[in] iv
 *   128 bit initialization vector to use.
 *
 * @param[in] keyWidth
 *   Set to cryptoKey128Bits or cryptoKey256Bits.
 ******************************************************************************/
static void CRYPTO_AES_OFBx(CRYPTO_TypeDef *  crypto,
                            uint8_t *         out,
                            const uint8_t *   in,
                            unsigned int      len,
                            const uint8_t *   key,
                            const uint8_t *   iv,
                            CRYPTO_KeyWidth_TypeDef keyWidth)
{
  EFM_ASSERT(!(len % CRYPTO_AES_BLOCKSIZE));

  crypto->WAC = 0;

  CRYPTO_KeyBufWrite(crypto, (uint32_t *)key, keyWidth);

  CRYPTO_DataWrite(&crypto->DATA2, (uint32_t *)iv);

  crypto->SEQ0 = CRYPTO_CMD_INSTR_DATA0TODATA1   << _CRYPTO_SEQ0_INSTR0_SHIFT
                 | CRYPTO_CMD_INSTR_DATA2TODATA0 << _CRYPTO_SEQ0_INSTR1_SHIFT
                 | CRYPTO_CMD_INSTR_AESENC       << _CRYPTO_SEQ0_INSTR2_SHIFT
                 | CRYPTO_CMD_INSTR_DATA0TODATA2 << _CRYPTO_SEQ0_INSTR3_SHIFT;
  crypto->SEQ1 = CRYPTO_CMD_INSTR_DATA1TODATA0XOR << _CRYPTO_SEQ1_INSTR4_SHIFT
                 | CRYPTO_CMD_INSTR_DATA0TODATA1  << _CRYPTO_SEQ1_INSTR5_SHIFT;

  CRYPTO_AES_ProcessLoop(crypto, len,
                         &crypto->DATA0, (uint32_t *) in,
                         &crypto->DATA1, (uint32_t *) out);
}

/***************************************************************************//**
 * @brief
 *   Function performs generic AES loop.
 *
 * @details
 *   Function loads given register with provided input data. Triggers CRYPTO to
 *   perform sequence of instructions and read specified output register to
 *   output buffer.
 *
 * @param[in]  crypto
 *   Pointer to CRYPTO peripheral register block.
 *
 * @param[in] len
 *   Number of bytes to encrypt/decrypt. Must be a multiple of 16.
 *
 * @param[in] inReg
 *   Input register - one of DATA0,DATA1,DATA2,DATA3
 *
 * @param[in] in
 *   Buffer holding data to encrypt/decrypt. Must be at least @p len long.
 *
 * @param[in] outReg
 *   Output register - one of DATA0,DATA1,DATA2,DATA3
 *
 * @param[out] out
 *   Buffer to place encrypted/decrypted data. Must be at least @p len long. It
 *   may be set equal to @p in, in which case the input buffer is overwritten.
 ******************************************************************************/
static inline void CRYPTO_AES_ProcessLoop(CRYPTO_TypeDef *        crypto,
                                          uint32_t                len,
                                          CRYPTO_DataReg_TypeDef  inReg,
                                          uint32_t *              in,
                                          CRYPTO_DataReg_TypeDef  outReg,
                                          uint32_t *              out)
{
  len /= CRYPTO_AES_BLOCKSIZE;
  crypto->SEQCTRL = 16 << _CRYPTO_SEQCTRL_LENGTHA_SHIFT;

  while (len--) {
    /* Load data and trigger encryption */
    CRYPTO_DataWrite(inReg, (uint32_t *)in);

    crypto->CMD = CRYPTO_CMD_SEQSTART;

    /* Save encrypted/decrypted data */
    CRYPTO_DataRead(outReg, (uint32_t *)out);

    out += 4;
    in  += 4;
  }
}

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

#endif /* defined(CRYPTO_COUNT) && (CRYPTO_COUNT > 0) */
