/****************************************************************************
 * arch/arm/src/lpc43xx/lpc43_aes.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/crypto/crypto.h>
#include <nuttx/arch.h>
#include <arch/board/board.h>

#include "arm_internal.h"
#include "chip.h"
#include <chip/lpc43_aes.h>

#define AES_BLOCK_SIZE 16

/****************************************************************************
 * Private Data
 ****************************************************************************/

static struct lpc43_aes_s *g_aes;

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static int aes_init(const void *iv,
                    const void *key, uint32_t keysize,
                    int mode, int encrypt)
{
  unsigned int cmd = 0;
  unsigned int ret = 0;

  if (g_aes == NULL)
    {
      return -ENOSYS;
    }

  /* The LPC43 aes engine can load two keys from otp and one random
   * generated key. This behavior doesn't fit current api.  So if
   * key == NULL, we will usr keysize as identifier of the special key.
   */

  if (keysize != 16 && key)
    {
      return -EINVAL;
    }

  if (mode != AES_MODE_ECB && mode != AES_MODE_CBC)
    {
      return -EINVAL;
    }

  if (encrypt == CYPHER_ENCRYPT)
    {
      cmd = mode == AES_MODE_ECB ?
                    AES_API_CMD_ENCODE_ECB : AES_API_CMD_ENCODE_CBC;
    }
  else
    {
      cmd = mode == AES_MODE_ECB ?
                    AES_API_CMD_DECODE_ECB : AES_API_CMD_DECODE_CBC;
    }

  g_aes->aes_init();

  if (key != NULL)
    {
      g_aes->aes_load_key_sw(key);
    }
  else
    {
      switch (keysize)
        {
          case 0:
            g_aes->aes_load_key1();
            break;

          case 1:
            g_aes->aes_load_key2();
            break;

          case 2:
            g_aes->aes_load_key_rng();
            break;
        }
    }

  g_aes->aes_load_iv_sw((const unsigned char *)iv);

  ret = g_aes->aes_set_mode(cmd);
  switch (ret)
    {
      case AES_API_ERR_WRONG_CMD:
      case AES_API_ERR_NOT_SUPPORTED:
      case AES_API_ERR_KEY_ALREADY_PROGRAMMED:
        ret = -EINVAL;
        break;

      case AES_API_ERR_DMA_CHANNEL_CFG:
      case AES_API_ERR_DMA_MUX_CFG:
      case AES_API_ERR_DMA_BUSY:
        ret = -EBUSY;
        break;
    }

  return 0;
}

static int aes_update(const void *out,
                      uint32_t *outl, const void *in,
                      uint32_t inl)
{
  if (g_aes == NULL)
    {
      return -ENOSYS;
    }

  if ((inl & (AES_BLOCK_SIZE - 1)) != 0)
    {
      return -EINVAL;
    }

  if (inl > *outl)
    {
      return -EINVAL;
    }

  return g_aes->aes_operate((unsigned char *)out,
                            (unsigned char *)in, inl / 16);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

int aes_cypher(void *out, const void *in, size_t size, const void *iv,
               const void *key, size_t keysize, int mode, int encrypt)
{
  unsigned int ret = 0;
  uint32_t outl = size;

  g_aes = (struct lpc43_g_aes *) * ((uint32_t *)LPC43_ROM_AES_DRIVER_TABLE);

  ret = aes_init(iv, key, keysize, mode, encrypt);

  if (ret != OK)
    {
      return ret;
    }

  return aes_update(out, &outl, in, size);
}
