text: /*
   * Copyright (C) 2017 C-SKY Microsystems Co., Ltd. All rights reserved.
   *
   * Licensed 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.
   */
  
  /******************************************************************************
   * @file     ck_sha.c
   * @brief    CSI Source File for SHA Driver
   * @version  V1.0
   * @date     02. June 2017
   ******************************************************************************/
  #include <stdio.h>
  #include <string.h>
  #include <stdbool.h>
  #include "csi_core.h"
  #include "drv_sha.h"
  #include "ck_sha_v1.h"
  
  #ifndef CONFIG_SHA_SUPPORT_MUL_THREAD
  #define CONFIG_SHA_SUPPORT_MUL_THREAD   1
  #endif
  
  typedef struct {
      uint32_t base;
      uint32_t irq;
      sha_event_cb_t cb;
      sha_status_t status;
      sha_mode_e mode;
      sha_endian_mode_e endian;
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
      uint32_t  state[16];
      uint32_t sha_buffer[32];
      uint32_t total;
      uint8_t first_cal;
  #endif
  } ck_sha_priv_t;
  
  #ifndef CONFIG_SHA_SUPPORT_MUL_THREAD
  static ck_sha_priv_t sha_handle[CONFIG_SHA_NUM];
  #endif
  bool finish_flag = 0;
  
  /* Driver Capabilities */
  static const sha_capabilities_t driver_capabilities = {
      .sha1 = 1, /* sha1 mode */
      .sha224 = 1, /* sha224 mode */
      .sha256 = 1, /* sha256 mode */
      .sha384 = 1, /* sha384 mode */
      .sha512 = 1, /* sha512 mode */
      .sha512_224 = 1, /* sha512_224 mode */
      .sha512_256 = 1, /* sha512_256 mode */
      .endianmode = 1, /* endian mode */
      .interruptmode = 1  /* interrupt mode */
  };
  
  #define ERR_SHA(errno) (CSI_DRV_ERRNO_SHA_BASE | errno)
  #define SHA_NULL_PARAM_CHK(para)                         \
          do {                                        \
              if (para == NULL) {                     \
                  return ERR_SHA(EDRV_PARAMETER);   \
              }                                       \
          } while (0)
  
  extern int32_t target_get_sha(int32_t idx, uint32_t *base, uint32_t *irq);
  extern int32_t target_get_sha_count(void);
  //
  // Functions
  //
  
  ck_sha_reg_t *sha_reg = NULL;
  
  #ifndef CONFIG_SHA_SUPPORT_MUL_THREAD
  static uint32_t sha_buffer[32];
  static uint32_t total[2] = {0x0};
  static uint8_t first_cal = 1;
  #endif
  static uint8_t sha_result[64] = {0x0};
  
  static int32_t sha_set_mode(sha_mode_e mode)
  {
      sha_reg->SHA_MODE &= ~0x7;
      sha_reg->SHA_MODE |= mode;
      return 0;
  }
  
  static void sha_set_source_message(uint32_t baseaddr)
  {
      sha_reg->SHA_BASEADDR = baseaddr;
  }
  
  static void sha_set_dest_message(uint32_t destaddr)
  {
      sha_reg->SHA_DESTADDR = destaddr;
  }
  
  static void sha_enable_without_count(void)
  {
      sha_reg->SHA_MODE |= 1<<25;
  }
  
  static void sha_disable_without_count(void)
  {
      sha_reg->SHA_MODE &= ~(1<<25);
  }
  
  static void sha_set_message_count(uint32_t count)
  {
      sha_reg->SHA_COUNTER0 = count;
      sha_reg->SHA_COUNTER1 = 0;
  }
  
  static int32_t sha_enable_initial(void)
  {
      sha_reg->SHA_MODE |= 1 << SHA_INIT_OFFSET;
      return 0;
  }
  
  static int32_t sha_disable_initial(void)
  {
      sha_reg->SHA_MODE &= ~(1 << SHA_INIT_OFFSET);
      return 0;
  }
  
  static int32_t sha_enable_calculate(void)
  {
      sha_reg->SHA_CON |= 1;
      return 0;
  }
  
  static int32_t sha_message_done(void)
  {
      while(sha_reg->SHA_CON & 0x1);
      return 0;
  }
  
  static void sha_new_encode(void)
  {
      sha_reg->SHA_INTSTATE = 0;
      sha_reg->SHA_MODE = 0;
  }
  
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
  static int32_t sha_restore_context(sha_handle_t handle, uint32_t *data)
  {
      uint32_t *result = (uint32_t *)&sha_reg->SHA_H0L;
  
      uint8_t i;
      for (i = 0; i < 16; i++) {
          result[i] = data[i];
      }
      return 0;
  }
  
  static int32_t sha_save_context(sha_handle_t handle, uint32_t *data)
  {
      uint32_t *result = (uint32_t *)&sha_reg->SHA_H0L;
  
      uint8_t i;
      for (i = 0; i < 16; i++) {
          data[i] = result[i];
      }
      return 0;
  }
  #endif
  
  static inline void sha_reverse_order(uint8_t *pdata, int32_t length)
  {
      uint8_t input_data[length];
      uint8_t result[length];
      uint32_t tmp = 0;
      int32_t i = 0;
      memcpy((void *)input_data, (void *)pdata, length);
  
      for (i = 0; i < length; i++) {
          tmp = i >> 2;
          tmp = tmp << 3;
          result[i] = input_data[tmp + 3 - i];
      }
  
      memcpy((void *)pdata, (void *)result, length);
  }
  
  /**
    \brief       get sha handle count.
    \return      sha handle count
  */
  int32_t csi_sha_get_instance_count(void)
  {
      return target_get_sha_count();
  }
  
  /**
    \brief       Initialize SHA Interface. 1. Initializes the resources needed for the SHA interface 2.registers event callback function
    \param[in]   idx must not exceed return value of csi_sha_get_instance_count()
    \param[in]   cb_event  Pointer to \ref sha_event_cb_t
    \return      return sha handle if success
  */
  sha_handle_t csi_sha_initialize(sha_handle_t handle, sha_event_cb_t cb_event)
  {
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
      uint32_t base = 0u;
      uint32_t irq;
      /* obtain the sha information */
      target_get_sha(0, &base, &irq);
      ck_sha_priv_t *sha_priv = handle;
      memset(sha_priv->state, 0, sizeof(sha_priv->state));
      sha_priv->total = 0;
      sha_priv->first_cal = 1;
  
  #else
      if (idx < 0 || idx >= CONFIG_SHA_NUM) {
          return NULL;
      }
  
      uint32_t base = 0u;
      uint32_t irq;
      /* obtain the sha information */
      int32_t real_idx = target_get_sha(idx, &base, &irq);
  
      if (real_idx != idx) {
          return NULL;
      }
      ck_sha_priv_t *sha_priv = &sha_handle[idx];
  #endif
      sha_priv->base = base;
      sha_priv->irq  = irq;
  
      /* initialize the sha context */
      sha_priv->cb = cb_event;
      sha_priv->status.busy = 0;
  
      return (sha_handle_t)sha_priv;
  }
  
  /**
    \brief       De-initialize SHA Interface. stops operation and releases the software resources used by the interface
    \param[in]   handle  sha handle to operate.
    \return      error code
  */
  int32_t csi_sha_uninitialize(sha_handle_t handle)
  {
      SHA_NULL_PARAM_CHK(handle);
  
      ck_sha_priv_t *sha_priv = handle;
      sha_priv->cb = NULL;
  
      return 0;
  }
  
  /**
    \brief       Get driver capabilities.
    \param[in]   handle sha handle to operate.
    \return      \ref sha_capabilities_t
  */
  sha_capabilities_t csi_sha_get_capabilities(sha_handle_t handle)
  {
      return driver_capabilities;
  }
  
  /**
    \brief       config sha mode.
    \param[in]   handle  sha handle to operate.
    \param[in]   mode      \ref sha_mode_e
    \param[in]   endian    \ref sha_endian_mode_e
    \return      error code
  */
  int32_t csi_sha_config(sha_handle_t handle, sha_mode_e mode, sha_endian_mode_e endian_mode)
  {
      SHA_NULL_PARAM_CHK(handle);
  
      ck_sha_priv_t *sha_priv = handle;
      sha_reg = (ck_sha_reg_t *)(sha_priv->base);
  
      /* config the sha mode */
      switch (mode) {
          case SHA_MODE_512_256:
          case SHA_MODE_512_224:
              return ERR_SHA(EDRV_UNSUPPORTED);
  
          case SHA_MODE_1:
          case SHA_MODE_224:
          case SHA_MODE_256:
          case SHA_MODE_384:
          case SHA_MODE_512:
              sha_priv->mode = mode;
              break;
  
          default:
              return ERR_SHA(EDRV_PARAMETER);
      }
  
      return 0;
  }
  
  /**
    \brief       start the engine
    \param[in]   handle  sha handle to operate.
    \param[in]   context  Pointer to the sha context.
    \return      error code
  */
  int32_t csi_sha_starts(sha_handle_t handle, void *context)
  {
      SHA_NULL_PARAM_CHK(handle);
  
      ck_sha_priv_t *sha_priv = handle;
      sha_priv->status.busy = 1;
  
      sha_new_encode();
      sha_set_mode(sha_priv->mode);
  
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
      memset(sha_priv->sha_buffer, 0, sizeof(sha_priv->sha_buffer));
      memset(sha_priv->state, 0, sizeof(sha_priv->state));
      sha_priv->first_cal = 1;
      sha_priv->total = 0;
  #endif
  
      return 0;
  }
  
  /**
    \brief       updata the engine
    \param[in]   handle  sha handle to operate.
    \param[in]   context  Pointer to the sha context.
    \param[in]   input   Pointer to the Source data
    \param[in]   len    the data len
    \return      error code
  */
  int32_t csi_sha_update(sha_handle_t handle, void *context, const void *input, uint32_t len)
  {
      SHA_NULL_PARAM_CHK(handle);
      SHA_NULL_PARAM_CHK(input);
      if (len <= 0) {
          return ERR_SHA(EDRV_PARAMETER);
      }
  
      ck_sha_priv_t *sha_priv = handle;
      sha_reg = (ck_sha_reg_t *)(sha_priv->base);
  
      uint8_t total_len_num;
      uint32_t block_size;
      if (sha_priv->mode < 4) {
          block_size = 64;
          total_len_num = 2;
      } else {
          block_size = 128;
          total_len_num = 4;
      }
  
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
      uint32_t *sha_buffer = sha_priv->sha_buffer;
      uint8_t first_cal = sha_priv->first_cal;
      sha_set_mode(sha_priv->mode);
      uint32_t left = sha_priv->total & (block_size - 1);
      uint32_t fill = block_size - left;
      uint32_t total_length = sha_priv->total << 3;
      uint32_t index = left >> 2;
  
      if (left & 0x3) {
          index++;
      }
      sha_priv->total += len;
      sha_priv->total &= 0xffffffff;
  #else
      uint32_t left = total[0] & (block_size - 1);
      uint32_t fill = block_size - left;
      uint32_t total_length = total[0] << 3;
      uint32_t index = left >> 2;
  
      if (left & 0x3) {
          index++;
      }
      total[0] += len;
      total[0] &= 0xffffffff;
  #endif
      uint8_t *p = (uint8_t *)input;
      /* when the text is not aligned by block and len > fill */
      if (left && len >= fill) {
          if (finish_flag) {
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
              memset(((uint8_t *)sha_buffer + left), 0x0, sizeof(sha_priv->sha_buffer) - left);
  #else
              memset(((uint8_t *)sha_buffer + left), 0x0, sizeof(sha_buffer) - left);
  #endif
              sha_buffer[index + total_len_num - 1] = total_length;
  
              sha_disable_without_count();
              sha_set_message_count(left << 3);
          } else {
              memcpy((void *)(((uint8_t *)sha_buffer) + left), p, fill);
              p += fill;
  
              sha_enable_without_count();
              sha_set_message_count(block_size << 3);
          }
  
          sha_set_source_message((uint32_t)sha_buffer);
          sha_set_dest_message((uint32_t)sha_result);
          if (first_cal == 0) {
              sha_enable_initial();
          } else {
              sha_disable_initial();
          }
  
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
          sha_restore_context(handle, (uint32_t *)sha_priv->state);
  #endif
  
          sha_enable_calculate();
          sha_message_done();
  
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
          sha_save_context(handle, (uint32_t *)sha_priv->state);
  #endif
          len -= fill;
          left = 0;
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
          sha_priv->first_cal = 0;
          first_cal = 0;
  #else
          first_cal = 0;
  #endif
      }
  
      /* calculate the hash by block */
      while (len >= block_size) {
          if (finish_flag) {
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
              memset(sha_buffer, 0, sizeof(sha_priv->sha_buffer));
  #else
              memset(sha_buffer, 0, sizeof(sha_buffer));
  #endif
              sha_buffer[total_len_num - 1] = total_length;
  
              sha_set_source_message((uint32_t)sha_buffer);
              sha_disable_without_count();
              sha_set_message_count(0);
          } else {
              memcpy(sha_buffer, p, block_size);
              sha_set_source_message((uint32_t)sha_buffer);
              sha_enable_without_count();
              sha_set_message_count(block_size << 3);
              p += block_size;
          }
          sha_set_dest_message((uint32_t)sha_result);
          if (first_cal == 0) {
              sha_enable_initial();
          } else {
              sha_disable_initial();
          }
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
          sha_restore_context(handle, (uint32_t *)sha_priv->state);
  #endif
  
          sha_enable_calculate();
          sha_message_done();
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
          sha_save_context(handle, (uint32_t *)sha_priv->state);
  #endif
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
          sha_priv->first_cal = 0;
          first_cal = 0;
  #else
          first_cal = 0;
  #endif
          len -= block_size;
      }
  
      /* when the text is not aligned by block and len < fill */
      if (len > 0) {
          memcpy((void *)(((uint8_t *)sha_buffer) + left), p, len);
      }
  
      sha_priv->status.busy = 0;
  
      return 0;
  }
  
  /**
    \brief       finish the engine
    \param[in]   handle  sha handle to operate.
    \param[in]   context  Pointer to the sha context.
    \param[out]  output   Pointer to the dest data
    \return      error code
  */
  int32_t csi_sha_finish(sha_handle_t handle, void *context, void *output)
  {
      SHA_NULL_PARAM_CHK(handle);
      SHA_NULL_PARAM_CHK(output);
  
      ck_sha_priv_t *sha_priv = handle;
      uint32_t block_size;
      if (sha_priv->mode < 4) {
          block_size = 64;
      } else {
          block_size = 128;
      }
  
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
      uint32_t last = sha_priv->total & (block_size - 1);
  #else
      uint32_t last = total[0] & (block_size - 1);
  #endif
      uint32_t padn = block_size - last;
  
      finish_flag = 1;
  
      csi_sha_update(handle, NULL, output, padn);
  
      uint8_t result_len = 20;
      /* convert the data endian according the sha mode */
      if (sha_priv->mode == SHA_MODE_1) {
          result_len = 20;
      } else if (sha_priv->mode == SHA_MODE_224) {
          result_len = 28;
      } else if (sha_priv->mode == SHA_MODE_256) {
          result_len = 32;
      } else if (sha_priv->mode == SHA_MODE_512) {
          result_len = 64;
      } else if (sha_priv->mode == SHA_MODE_384) {
          result_len = 48;
      }
      sha_reverse_order(sha_result, result_len);
      memcpy((uint8_t*)output, sha_result, result_len);
  
      finish_flag = 0;
  #ifdef CONFIG_SHA_SUPPORT_MUL_THREAD
      memset(sha_priv->sha_buffer, 0, sizeof(sha_priv->sha_buffer));
      sha_priv->first_cal = 1;
      sha_priv->total = 0;
  #else
      memset(sha_buffer, 0, sizeof(sha_buffer));
      first_cal = 1;
      total[0] = 0;
  #endif
      return 0;
  }
  
  /**
    \brief       Get SHA status.
    \param[in]   handle  sha handle to operate.
    \return      SHA status \ref sha_status_t
  */
  sha_status_t csi_sha_get_status(sha_handle_t handle)
  {
      ck_sha_priv_t *sha_priv = handle;
      return sha_priv->status;
  }
  