/*
 * Original file:
 * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
 * All rights reserved.
 *
 * Port to Contiki:
 * Copyright (c) 2014 Andreas Dröscher <contiki@anticat.ch>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * \addtogroup cc2538-ecc
 * @{
 *
 * \file
 * Implementation of the cc2538 ECC driver
 */
#include "ecc-driver.h"
#include "reg.h"
#include "dev/nvic.h"

#define ASSERT(IF) if(!(IF)) { return PKA_STATUS_INVALID_PARAM; }

/*---------------------------------------------------------------------------*/
uint8_t
ecc_mul_start(uint32_t *scalar, ec_point_t *ec_point,
              ecc_curve_info_t *curve, uint32_t *result_vector,
              struct process *process)
{

  uint8_t extraBuf;
  uint32_t offset;
  int i;

  /* Check for the arguments. */
  ASSERT(NULL != scalar);
  ASSERT(NULL != ec_point);
  ASSERT(NULL != ec_point->x);
  ASSERT(NULL != ec_point->y);
  ASSERT(NULL != curve);
  ASSERT(curve->size <= PKA_MAX_CURVE_SIZE);
  ASSERT(NULL != result_vector);

  offset = 0;

  /* Make sure no PKA operation is in progress. */
  if((REG(PKA_FUNCTION) & PKA_FUNCTION_RUN) != 0) {
    return PKA_STATUS_OPERATION_INPRG;
  }

  /* Calculate the extra buffer requirement. */
  extraBuf = 2 + curve->size % 2;

  /* Update the A ptr with the offset address of the PKA RAM location
   * where the scalar will be stored. */
  REG(PKA_APTR) = offset >> 2;

  /* Load the scalar in PKA RAM. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = *scalar++;
  }

  /* Determine the offset for the next data. */
  offset += 4 * (i + (curve->size % 2));

  /* Update the B ptr with the offset address of the PKA RAM location
   * where the curve parameters will be stored. */
  REG(PKA_BPTR) = offset >> 2;

  /* Write curve parameter 'p' as 1st part of vector B immediately
   * following vector A at PKA RAM */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->prime[i];
  }

  /* Determine the offset for the next data. */
  offset += 4 * (i + extraBuf);

  /* Copy curve parameter 'a' in PKA RAM. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->a[i];
  }

  /* Determine the offset for the next data. */
  offset += 4 * (i + extraBuf);

  /* Copy curve parameter 'b' in PKA RAM. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->b[i];
  }

  /* Determine the offset for the next data. */
  offset += 4 * (i + extraBuf);

  /* Update the C ptr with the offset address of the PKA RAM location
   * where the x, y will be stored. */
  REG(PKA_CPTR) = offset >> 2;

  /* Write elliptic curve point.x co-ordinate value. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = ec_point->x[i];
  }

  /* Determine the offset for the next data. */
  offset += 4 * (i + extraBuf);

  /* Write elliptic curve point.y co-ordinate value. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = ec_point->y[i];
  }

  /* Determine the offset for the next data. */
  offset += 4 * (i + extraBuf);

  /* Update the result location. */
  *result_vector = PKA_RAM_BASE + offset;

  /* Load D ptr with the result location in PKA RAM. */
  REG(PKA_DPTR) = offset >> 2;

  /* Load length registers. */
  REG(PKA_ALENGTH) = curve->size;
  REG(PKA_BLENGTH) = curve->size;

  /* set the PKA function to ECC-MULT and start the operation. */
  REG(PKA_FUNCTION) = 0x0000D000;

  /* Enable Interrupt */
  if(process != NULL) {
    pka_register_process_notification(process);
    nvic_interrupt_unpend(NVIC_INT_PKA);
    nvic_interrupt_enable(NVIC_INT_PKA);
  }

  return PKA_STATUS_SUCCESS;
}
/*---------------------------------------------------------------------------*/
uint8_t
ecc_mul_get_result(ec_point_t *ec_point,
                   uint32_t result_vector)
{
  int i;
  uint32_t addr;
  uint32_t regMSWVal;
  uint32_t len;

  /* Check for the arguments. */
  ASSERT(NULL != ec_point);
  ASSERT(NULL != ec_point->x);
  ASSERT(NULL != ec_point->y);
  ASSERT(result_vector > PKA_RAM_BASE);
  ASSERT(result_vector < (PKA_RAM_BASE + PKA_RAM_SIZE));

  /* Verify that the operation is completed. */
  if((REG(PKA_FUNCTION) & PKA_FUNCTION_RUN) != 0) {
    return PKA_STATUS_OPERATION_INPRG;
  }

  /* Disable Interrupt */
  nvic_interrupt_disable(NVIC_INT_PKA);
  pka_register_process_notification(NULL);

  if(REG(PKA_SHIFT) == 0x00000000) {
    /* Get the MSW register value. */
    regMSWVal = REG(PKA_MSW);

    /* Check to make sure that the result vector is not all zeroes. */
    if(regMSWVal & PKA_MSW_RESULT_IS_ZERO) {
      return PKA_STATUS_RESULT_0;
    }

    /* Get the length of the result */
    len = ((regMSWVal & PKA_MSW_MSW_ADDRESS_M) + 1)
        - ((result_vector - PKA_RAM_BASE) >> 2);

    addr = result_vector;

    /* copy the x co-ordinate value of the result from vector D into
     * the \e ec_point. */
    for(i = 0; i < len; i++) {
      ec_point->x[i] = REG(addr + 4 * i);
    }

    addr += 4 * (i + 2 + len % 2);

    /* copy the y co-ordinate value of the result from vector D into
     * the \e ec_point. */
    for(i = 0; i < len; i++) {
      ec_point->y[i] = REG(addr + 4 * i);
    }

    return PKA_STATUS_SUCCESS;
  } else {
    return PKA_STATUS_FAILURE;
  }
}
/*---------------------------------------------------------------------------*/
uint8_t
ecc_mul_gen_pt_start(uint32_t *scalar, ecc_curve_info_t *curve,
                     uint32_t *result_vector, struct process *process)
{
  uint8_t extraBuf;
  uint32_t offset;
  int i;

  /* check for the arguments. */
  ASSERT(NULL != scalar);
  ASSERT(NULL != curve);
  ASSERT(curve->size <= PKA_MAX_CURVE_SIZE);
  ASSERT(NULL != result_vector);

  offset = 0;

  /* Make sure no operation is in progress. */
  if((REG(PKA_FUNCTION) & PKA_FUNCTION_RUN) != 0) {
    return PKA_STATUS_OPERATION_INPRG;
  }

  /* Calculate the extra buffer requirement. */
  extraBuf = 2 + curve->size % 2;

  /* Update the A ptr with the offset address of the PKA RAM location
   * where the scalar will be stored. */
  REG(PKA_APTR) = offset >> 2;

  /* Load the scalar in PKA RAM. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = *scalar++;
  }

  /* Determine the offset in PKA RAM for the next data. */
  offset += 4 * (i + (curve->size % 2));

  /* Update the B ptr with the offset address of the PKA RAM location
   * where the curve parameters will be stored. */
  REG(PKA_BPTR) = offset >> 2;

  /* Write curve parameter 'p' as 1st part of vector B. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->prime[i];
  }

  /* Determine the offset in PKA RAM for the next data. */
  offset += 4 * (i + extraBuf);

  /* Write curve parameter 'a' in PKA RAM. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->a[i];
  }

  /* Determine the offset in PKA RAM for the next data. */
  offset += 4 * (i + extraBuf);

  /* write curve parameter 'b' in PKA RAM. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->b[i];
  }

  /* Determine the offset in PKA RAM for the next data. */
  offset += 4 * (i + extraBuf);

  /* Update the C ptr with the offset address of the PKA RAM location
   * where the x, y will be stored. */
  REG(PKA_CPTR) = offset >> 2;

  /* Write x co-ordinate value of the Generator point in PKA RAM. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->x[i];
  }

  /* Determine the offset in PKA RAM for the next data. */
  offset += 4 * (i + extraBuf);

  /* Write y co-ordinate value of the Generator point in PKA RAM. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->y[i];
  }

  /* Determine the offset in PKA RAM for the next data. */
  offset += 4 * (i + extraBuf);

  /* Update the result location. */
  *result_vector = PKA_RAM_BASE + offset;

  /* Load D ptr with the result location in PKA RAM. */
  REG(PKA_DPTR) = offset >> 2;

  /* Load length registers. */
  REG(PKA_ALENGTH) = curve->size;
  REG(PKA_BLENGTH) = curve->size;

  /* Set the PKA function to ECC-MULT and start the operation. */
  REG(PKA_FUNCTION) = 0x0000D000;

  /* Enable Interrupt */
  if(process != NULL) {
    pka_register_process_notification(process);
    nvic_interrupt_unpend(NVIC_INT_PKA);
    nvic_interrupt_enable(NVIC_INT_PKA);
  }

  return PKA_STATUS_SUCCESS;
}
/*---------------------------------------------------------------------------*/
uint8_t
ecc_mul_gen_pt_get_result(ec_point_t *ec_point,
                          uint32_t result_vector)
{

  int i;
  uint32_t regMSWVal;
  uint32_t addr;
  uint32_t len;

  /* Check for the arguments. */
  ASSERT(NULL != ec_point);
  ASSERT(NULL != ec_point->x);
  ASSERT(NULL != ec_point->y);
  ASSERT(result_vector > PKA_RAM_BASE);
  ASSERT(result_vector < (PKA_RAM_BASE + PKA_RAM_SIZE));

  /* Verify that the operation is completed. */
  if((REG(PKA_FUNCTION) & PKA_FUNCTION_RUN) != 0) {
    return PKA_STATUS_OPERATION_INPRG;
  }

  /* Disable Interrupt */
  nvic_interrupt_disable(NVIC_INT_PKA);
  pka_register_process_notification(NULL);

  if(REG(PKA_SHIFT) == 0x00000000) {
    /* Get the MSW register value. */
    regMSWVal = REG(PKA_MSW);

    /* Check to make sure that the result vector is not all zeroes. */
    if(regMSWVal & PKA_MSW_RESULT_IS_ZERO) {
      return PKA_STATUS_RESULT_0;
    }

    /* Get the length of the result. */
    len = ((regMSWVal & PKA_MSW_MSW_ADDRESS_M) + 1)
        - ((result_vector - PKA_RAM_BASE) >> 2);

    addr = result_vector;

    /* Copy the x co-ordinate value of the result from vector D into the
     * EC point. */
    for(i = 0; i < len; i++) {
      ec_point->x[i] = REG(addr + 4 * i);
    }

    addr += 4 * (i + 2 + len % 2);

    /* Copy the y co-ordinate value of the result from vector D into the
     * EC point. */
    for(i = 0; i < len; i++) {
      ec_point->y[i] = REG(addr + 4 * i);
    }

    return PKA_STATUS_SUCCESS;
  } else {
    return PKA_STATUS_FAILURE;
  }
}
/*---------------------------------------------------------------------------*/
uint8_t
ecc_add_start(ec_point_t *ec_point1, ec_point_t *ec_point2,
              ecc_curve_info_t *curve, uint32_t *result_vector,
              struct process *process)
{

  uint8_t extraBuf;
  uint32_t offset;
  int i;

  /* Check for the arguments. */
  ASSERT(NULL != ec_point1);
  ASSERT(NULL != ec_point1->x);
  ASSERT(NULL != ec_point1->y);
  ASSERT(NULL != ec_point2);
  ASSERT(NULL != ec_point2->x);
  ASSERT(NULL != ec_point2->y);
  ASSERT(NULL != curve);
  ASSERT(NULL != result_vector);

  offset = 0;

  /* Make sure no operation is in progress. */
  if((REG(PKA_FUNCTION) & PKA_FUNCTION_RUN) != 0) {
    return PKA_STATUS_OPERATION_INPRG;
  }

  /* Calculate the extra buffer requirement. */
  extraBuf = 2 + curve->size % 2;

  /* Update the A ptr with the offset address of the PKA RAM location
   * where the first ecPt will be stored. */
  REG(PKA_APTR) = offset >> 2;

  /* Load the x co-ordinate value of the first EC point in PKA RAM. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = ec_point1->x[i];
  }

  /* Determine the offset in PKA RAM for the next data. */
  offset += 4 * (i + extraBuf);

  /* Load the y co-ordinate value of the first EC point in PKA RAM. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = ec_point1->y[i];
  }

  /* Determine the offset in PKA RAM for the next data. */
  offset += 4 * (i + extraBuf);

  /* Update the B ptr with the offset address of the PKA RAM location
   * where the curve parameters will be stored. */
  REG(PKA_BPTR) = offset >> 2;

  /* Write curve parameter 'p' as 1st part of vector B */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->prime[i];
  }

  /* Determine the offset in PKA RAM for the next data. */
  offset += 4 * (i + extraBuf);

  /* Write curve parameter 'a'. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->a[i];
  }

  /* Determine the offset in PKA RAM for the next data. */
  offset += 4 * (i + extraBuf);

  /* Update the C ptr with the offset address of the PKA RAM location
   * where the ecPt2 will be stored. */
  REG(PKA_CPTR) = offset >> 2;

  /* Load the x co-ordinate value of the second EC point in PKA RAM. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = ec_point2->x[i];
  }

  /* Determine the offset in PKA RAM for the next data. */
  offset += 4 * (i + extraBuf);

  /* Load the y co-ordinate value of the second EC point in PKA RAM. */
  for(i = 0; i < curve->size; i++) {
    REG(PKA_RAM_BASE + offset + 4 * i) = ec_point2->y[i];
  }

  /* Determine the offset in PKA RAM for the next data. */
  offset += 4 * (i + extraBuf);

  /* Copy the result vector location. */
  *result_vector = PKA_RAM_BASE + offset;

  /* Load D ptr with the result location in PKA RAM. */
  REG(PKA_DPTR) = offset >> 2;

  /* Load length registers. */
  REG(PKA_BLENGTH) = curve->size;

  /* Set the PKA Function to ECC-ADD and start the operation. */
  REG(PKA_FUNCTION) = 0x0000B000;

  /* Enable Interrupt */
  if(process != NULL) {
    pka_register_process_notification(process);
    nvic_interrupt_unpend(NVIC_INT_PKA);
    nvic_interrupt_enable(NVIC_INT_PKA);
  }

  return PKA_STATUS_SUCCESS;
}
/*---------------------------------------------------------------------------*/
uint8_t
ecc_add_get_result(ec_point_t *ec_point, uint32_t result_vector)
{
  uint32_t regMSWVal;
  uint32_t addr;
  int i;
  uint32_t len;

  /* Check for the arguments. */
  ASSERT(NULL != ec_point);
  ASSERT(NULL != ec_point->x);
  ASSERT(NULL != ec_point->y);
  ASSERT(result_vector > PKA_RAM_BASE);
  ASSERT(result_vector < (PKA_RAM_BASE + PKA_RAM_SIZE));

  if((REG(PKA_FUNCTION) & PKA_FUNCTION_RUN) != 0) {
    return PKA_STATUS_OPERATION_INPRG;
  }

  /* Disable Interrupt */
  nvic_interrupt_disable(NVIC_INT_PKA);
  pka_register_process_notification(NULL);

  if(REG(PKA_SHIFT) == 0x00000000) {
    /* Get the MSW register value. */
    regMSWVal = REG(PKA_MSW);

    /* Check to make sure that the result vector is not all zeroes. */
    if(regMSWVal & PKA_MSW_RESULT_IS_ZERO) {
      return PKA_STATUS_RESULT_0;
    }

    /* Get the length of the result. */
    len = ((regMSWVal & PKA_MSW_MSW_ADDRESS_M) + 1)
        - ((result_vector - PKA_RAM_BASE) >> 2);

    addr = result_vector;

    /* Copy the x co-ordinate value of result from vector D into the
     * the output EC Point. */
    for(i = 0; i < len; i++) {
      ec_point->x[i] = REG(addr + 4 * i);
    }

    addr += 4 * (i + 2 + len % 2);

    /* Copy the y co-ordinate value of result from vector D into the
     * the output EC Point. */
    for(i = 0; i < len; i++) {
      ec_point->y[i] = REG(addr + 4 * i);
    }

    return PKA_STATUS_SUCCESS;
  } else {
    return PKA_STATUS_FAILURE;
  }
}
/** @} */