/* -*- c++ -*-
 *
 * SOCLIB_LGPL_HEADER_BEGIN
 * 
 * This file is part of SoCLib, GNU LGPLv2.1.
 * 
 * SoCLib is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; version 2.1 of the License.
 * 
 * SoCLib is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with SoCLib; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * SOCLIB_LGPL_HEADER_END
 *
 */

#include "soclib_crypto.h"

namespace soclib {
namespace caba {

#define tmpl(x) template<typename vci_param> x Crypto<vci_param>

//#define SOCLIB_MODULE_DEBUG
using namespace std;

tmpl(bool)::on_write(int seg, typename vci_param::addr_t addr, typename vci_param::data_t data, int be)
{
    int cell = (int)addr;

#ifdef SOCLIB_MODULE_DEBUG
    std::cout 
        << name()
        << ": write register "
        << cell
        << " with data 0x"
        << std::hex << data
        << std::endl;
#endif

      switch (cell) {
	case SOCLIB_CRYPTO_KEY_ADDR(0) ... SOCLIB_CRYPTO_KEY_ADDR(SOCLIB_CRYPTO_KEY_COUNT - 1):
		_rkey[cell/4] = data;
		return true;
	case SOCLIB_CRYPTO_IV_ADDR(0) ... SOCLIB_CRYPTO_IV_ADDR(SOCLIB_CRYPTO_IV_COUNT - 1):
		_riv[cell/4 - SOCLIB_CRYPTO_IV_ADDR(0)/4] = data;
		return true;
	case SOCLIB_CRYPTO_OP_ADDR:
                _rstatus = _rstatus | SOCLIB_CRYPTO_STATUS_BUSY;
		_start = true;
		_rcfg = data;
		return true;
	case SOCLIB_CRYPTO_IRQEN_ADDR:
		_rirqen = data;
		return true;
	case SOCLIB_CRYPTO_DMASRC_ADDR:
		_rsrc = data;
		return true;
        case SOCLIB_CRYPTO_DMADST_ADDR:
		_rdst = data;
		return true;
        case SOCLIB_CRYPTO_DMALEN_ADDR:
		_rlen = data;
		return true;
        default:
          ended(SOCLIB_CRYPTO_STATUS_ERROR_WRITE_IO);
	};
	return false;
}

tmpl(bool)::on_read(int seg, typename vci_param::addr_t addr, typename vci_param::data_t &data)
{
    int cell = (int)addr;

#ifdef SOCLIB_MODULE_DEBUG
    std::cout 
        << name()
        << ": read register "
        << cell
        << std::endl;
#endif

    switch (cell) {
	case SOCLIB_CRYPTO_KEY_ADDR(0) ... SOCLIB_CRYPTO_KEY_ADDR(15):
		data = _rkey[cell];
		return true;
	case SOCLIB_CRYPTO_IV_ADDR(0) ... SOCLIB_CRYPTO_IV_ADDR(15):
		data = _riv[cell];
		return true;
	case SOCLIB_CRYPTO_OP_ADDR:
		data = _rcfg;
		return true;
	case SOCLIB_CRYPTO_STATUS_ADDR:
		data = _rstatus;
		return true;
	case SOCLIB_CRYPTO_IRQPEND_ADDR:
		data = _rirq;
                _rirq = 0;
		return true;
	case SOCLIB_CRYPTO_DMASRC_ADDR:
		data = _rsrc;
		return true;
        case SOCLIB_CRYPTO_DMADST_ADDR:
		data  = _rdst;
		return true;
        case SOCLIB_CRYPTO_DMALEN_ADDR:
		data = _rlen;
		return true;
        default:
          ended(SOCLIB_CRYPTO_STATUS_ERROR_READ_IO);
	};
	return false;
}

tmpl(void)::ended(uint32_t error)
{
  _rstatus = 0;
 
  if (error)
  { 
    std::cout 
        << name()
        << ": ERROR with return value: "
        << error
        << std::endl;
  }

#ifdef SOCLIB_MODULE_DEBUG
    std::cout 
        << name()
        << " : End of SOCLIB_CRYPTO"
        << std::endl;
#endif

  _rlen = 0;
  _must_finish = false;
  _handling = false;
  _rstatus = SOCLIB_CRYPTO_STATUS_ERROR_SHIFTED(error); 
  if (_rirqen & SOCLIB_CRYPTO_IRQEN_DONE)
    _rirq = true;
}

tmpl(void)::crypto_process()
{
  switch (_rcfg) {
      case SOCLIB_CRYPTO_OP_AES128_ECB_CRYPT:
        aes_process(1, 128, ecb);
      	break;
      case SOCLIB_CRYPTO_OP_AES128_ECB_DECRYPT:
        aes_process(0, 128, ecb);
      	break;
      case SOCLIB_CRYPTO_OP_AES192_ECB_CRYPT:
        aes_process(1, 192, ecb);
      	break;
      case SOCLIB_CRYPTO_OP_AES192_ECB_DECRYPT:
        aes_process(0, 192, ecb);
      	break;
      case SOCLIB_CRYPTO_OP_AES256_ECB_CRYPT:
        aes_process(1, 256, ecb);
      	break;
      case SOCLIB_CRYPTO_OP_AES256_ECB_DECRYPT:
        aes_process(0, 256, ecb);
      	break;
      case SOCLIB_CRYPTO_OP_AES128_CBC_CRYPT:
        aes_process(1, 128, cbc);
      	break;
      case SOCLIB_CRYPTO_OP_AES128_CBC_DECRYPT:
        aes_process(0, 128, cbc);
      	break;
      case SOCLIB_CRYPTO_OP_AES192_CBC_CRYPT:
        aes_process(1, 192, cbc);
      	break;
      case SOCLIB_CRYPTO_OP_AES192_CBC_DECRYPT:
        aes_process(0, 192, cbc);
      	break;
      case SOCLIB_CRYPTO_OP_AES256_CBC_CRYPT:
        aes_process(1, 256, cbc);
      	break;
      case SOCLIB_CRYPTO_OP_AES256_CBC_DECRYPT:
        aes_process(0, 256, cbc);
      	break;
      default:
        ended(SOCLIB_CRYPTO_STATUS_ERROR_CRYPTO_CFG);
    };
};


tmpl(void)::aes_process(bool enc, uint32_t size, uint32_t mode)
{
 /* data structure that contains the key itself */
  AES_KEY aes_key;

  if (enc)
    /* set the encryption key */
    AES_set_encrypt_key((unsigned char*)_rkey, size, &aes_key);
  else
    /* set the decryption key */
    AES_set_decrypt_key((unsigned char*)_rkey, size, &aes_key);

  _crypto_count = AES_ENGINE_COUNT * 8 * _burst/AES_ENGINE_WIDTH;

  if (mode == cbc)
    AES_cbc_encrypt(_buffer, _buffer, _burst, &aes_key, (unsigned char*)_riv, enc);
  else if (mode == ecb)
    for (int i=0;i<_burst/AES_ENGINE_WIDTH;i++)
      AES_ecb_encrypt(_buffer + i * AES_ENGINE_WIDTH, _buffer + i * AES_ENGINE_WIDTH, &aes_key, enc);
}

tmpl(void)::read_done(req_t *req )
{
  if (!req->failed() && !_must_finish )
    _crypto_start = true;
  else
    ended(SOCLIB_CRYPTO_STATUS_ERROR_READ);
  delete req;
}

tmpl(void)::write_done(req_t *req )
{
  _offset += _burst;

  if (!req->failed() && !_must_finish )
    next_req();
  else {
    ended(SOCLIB_CRYPTO_STATUS_ERROR_WRITE);
  }
  delete req;
}

tmpl(void)::next_req()
{
  ssize_t _remaining = (ssize_t)_rlen-(size_t)_offset;

  if (_remaining == 0) {
    ended(SOCLIB_CRYPTO_STATUS_ERROR_NONE);
    return;
  }
  else if (_remaining < 0) {
    ended(SOCLIB_CRYPTO_STATUS_ERROR_REMAINING);
    return;
  }

  _burst = _bsize;

  if (_remaining < _bsize)
    _burst = _remaining;

  soclib::caba::VciInitSimpleReadReq<vci_param> *req = 
  new soclib::caba::VciInitSimpleReadReq<vci_param>(
      _buffer, _rsrc+_offset, _burst);
  req->setDone( this, ON_T(read_done) );
  vci_init_fsm.doReq(req);
}
tmpl(void)::transition()
{
  uint8_t i;

  if (!srstn) {
    vci_target_fsm.reset();
    vci_init_fsm.reset();
    _rlen = 0;
    _rsrc = 0;
    _rdst = 0;
    _burst = 0;
    _start = 0;
    _rlen = 0;
    _rstatus = 0;
    _rirq = false;
    _handling = false;
    _crypto_count = 0;
    _crypto_start = 0;
    _must_finish = false;
    for(i=0;i<SOCLIB_CRYPTO_KEY_COUNT;i++)
      _rkey[i] = 0;
    for(i=0;i<SOCLIB_CRYPTO_IV_COUNT;i++)
      _riv[i] = 0;
    return;
  }

  if (_start && !_handling ) {
    _start = 0;
    _handling = true;
    _offset = 0;
    next_req();
  }
    
  if(!_handling )
     _must_finish = false;

  if (_crypto_start)
  {
    crypto_process();
    _crypto_count = AES_ENGINE_COUNT;
    _crypto_start = false;
  }

  if (_crypto_count > 0)
  {
    _crypto_count --;
    if (_crypto_count == 0) 
    {
      ssize_t _remaining = (ssize_t)_rlen-(size_t)_offset;
      _burst = _bsize;
      if (_remaining < _bsize)
        _burst = _remaining;

      soclib::caba::VciInitSimpleWriteReq<vci_param> *new_req =
        new soclib::caba::VciInitSimpleWriteReq<vci_param>( _rdst+_offset, _buffer, _burst );
        new_req->setDone( this, ON_T(write_done) );
        vci_init_fsm.doReq( new_req );
    }
  }

  vci_target_fsm.transition();
  vci_init_fsm.transition();
}

tmpl(void)::genMoore()
{
  vci_target_fsm.genMoore();
  vci_init_fsm.genMoore();

  irq = _rirq;
}
  

tmpl(/**/)::Crypto(sc_module_name name, const soclib::caba::MappingTable &mt, const soclib::caba::IntTab &srcid, const soclib::caba::IntTab &tgtid, const size_t burst_size ):
  soclib::caba::BaseModule(name),
  vci_target_fsm(vci_target, mt.getSegmentList(tgtid)),
  vci_init_fsm(vci_initiator, mt.indexForId(srcid)),
  clk("clk"),
  srstn("resetn"),
  vci_target("vci_target"),
  vci_initiator("vci_initiator"),
  irq("irq")
{
  
  _bsize = burst_size;

  _buffer = new uint8_t[_bsize]; 

  vci_target_fsm.on_read_write(on_read, on_write);

  assert(burst_size && "Useless DMA with no buffer");
  assert(burst_size < (1<<vci_param::K) && "I will be unable to create requests that big");

  SC_METHOD(transition);
  dont_initialize();
  sensitive << clk.pos();
  SC_METHOD(genMoore);
  dont_initialize();
  sensitive << clk.neg();
}

}}
