/*
    This file is part of MutekH.
    
    MutekH 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.
    
    MutekH 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 MutekH; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301 USA.

    Copyright Jeremie Brunel <jeremie.brunel@telecom-paristech.fr> (c) 2014
*/

#include <hexo/endian.h>
#include <hexo/iospace.h>

#include <device/irq.h>
#include <mutek/mem_alloc.h>
#include <mutek/printk.h>

#include <libssm/hsm.h>
#include <libssm/sb_hsm_drv.h>
#include <libssm/sb_hsm_external.h>

struct hsm_private_s
{
  struct hsm_op_s *fwm;

  dev_hsm_queue_root_t queue;
  struct dev_irq_ep_s  irq_ep;
};

void process_end_read(struct device_s *dev, struct dev_hsm_rq_s *rq)
{
  struct hsm_private_s *drv_pv= dev->drv_pv;
  struct hsm_op_s *fwm = drv_pv->fwm;

  if (rq->group.read_ok)
  {
    fwm->completedreadGroup(rq->group.dest);
  }
  else
  {
    printk("Cannot end a non-started read\n");
  }
}

static void hsm_start_rq(struct device_s *dev, struct dev_hsm_rq_s *rq)
{
  struct hsm_private_s *drv_pv= dev->drv_pv;
  struct hsm_op_s *fwm = drv_pv->fwm;

  switch (rq->type)
  {
  case HSM_NEW_PSPE:
    fwm->pspe_init(rq->pspe.addr,
		   rq->pspe.integrity_page,
		   rq->pspe.integrity_idx,
		   rq->pspe.iv_page,
		   rq->pspe.iv_idx,
		   rq->pspe.sp_idx,
		   rq->pspe.unprotected,
		   rq->pspe.size);
    break;

  case HSM_DELETE_PSPE:
    fwm->pspe_delete(rq->pspe.addr);
    break;

  case HSM_NEW_SP:
    {
      enum sb_confidentiality_mode_e c;
      if (rq->sp.c)
      {
	if (rq->sp.mode == HSM_PROT_RO)
	  c = sb_sp_c_otp;
	else
	  c = sb_sp_c_cbc;
      }
      else
      {
	c = sb_sp_c_none;
      }

      enum sb_integrity_mode_e i;
      if (rq->sp.i)
      {
	if (rq->sp.mode == HSM_PROT_RO)
	  i = sb_sp_i_mac;
	else
	  i = sb_sp_i_mact;
      }
      else
      {
	i = sb_sp_i_none;
      }
      fwm->sp_init(rq->sp.addr,
		     c,
		     i,
		     rq->sp.key_c,
		     rq->sp.key_i);
    }
    break;

  case HSM_DELETE_SP:
    fwm->sp_delete(rq->sp.addr);
    break;

  case HSM_INTEGRITY:
    fwm->init_page_integrity(rq->integrity.addr,
			       rq->integrity.new);
    break;

  case HSM_READ:
    fwm->prereadGroup(rq->group.src);
    break;

  case HSM_WRITE:
    fwm->writeGroup(rq->group.dest,
		      rq->group.src);
    break;

  default:
    printk("Unknown request type '%u' for SecBus HSM Drv\n", rq->type);
  }
}

DEVHSM_REQUEST(hsm_request)
{
  struct device_s *dev = hdev->dev;
  struct hsm_private_s *pv = dev->drv_pv;

  LOCK_SPIN_IRQ(&dev->lock);

  rq->hdev = hdev;

  bool_t empty = dev_hsm_queue_isempty(&pv->queue);
  dev_hsm_queue_pushback(&pv->queue, rq);

  if (empty)
    hsm_start_rq(dev, rq);

  LOCK_RELEASE_IRQ(&dev->lock);

  return 0;
}

static DEV_INIT(hsm_init);
static DEV_CLEANUP(hsm_cleanup);

static const struct devenum_ident_s hsm_ids[] =
{
  DEVENUM_FDTNAME_ENTRY("soclib:vci_secbus"),
  {0}
};

static const struct driver_hsm_s hsm_module_drv =
{
  .class_ = DRIVER_CLASS_HSM,
  .f_request = hsm_request,
};

const struct driver_s hsm_drv =
{
  .desc      = "SecBus HSM",
  .id_table  = hsm_ids,
  
  .f_init    = hsm_init,
  .f_cleanup = hsm_cleanup,

  .classes   = { &hsm_module_drv, 0 }
};

REGISTER_DRIVER(hsm_drv);

static DEV_IRQ_EP_PROCESS(hsm_irq)
{
  struct device_s *dev = ep->dev;
  struct hsm_private_s *pv = dev->drv_pv;
  struct dev_hsm_rq_s *rq;

  lock_spin(&dev->lock);

  rq = dev_hsm_queue_head(&pv->queue);

  rq->callback(rq);

  if (rq->type == HSM_READ)
  {
    if (rq->group.read_ok)
      pv->fwm->completedreadGroup(rq->group.dest);
  }

  dev_hsm_queue_pop(&pv->queue);

  pv->fwm->getstatus();

  if ((rq = dev_hsm_queue_head(&pv->queue)))
    hsm_start_rq(dev, rq);

  lock_release(&dev->lock);
}

static DEV_INIT(hsm_init)
{
  struct hsm_private_s *pv;

  dev->status = DEVICE_DRIVER_INIT_FAILED;

  pv = mem_alloc(sizeof(*pv), (mem_scope_sys));
  if (!pv)
    return -ENOMEM;

  pv->fwm = (struct hsm_op_s*)0x84000000;
  pv->fwm->enable_int();

  dev->drv = &hsm_drv;
  dev->drv_pv = pv;

  dev_hsm_queue_init(&pv->queue);

  device_irq_source_init(dev, &pv->irq_ep, 1, &hsm_irq, DEV_IRQ_SENSE_HIGH_LEVEL);
  if (device_irq_source_link(dev, &pv->irq_ep, 1, 1))
    goto err_mem;

  dev->status = DEVICE_DRIVER_INIT_DONE;

  return 0;
err_mem:
  mem_free(pv);
  return -1;
}

static DEV_CLEANUP(hsm_cleanup)
{
  struct hsm_private_s *pv = dev->drv_pv;
  
  device_irq_source_unlink(dev, &pv->irq_ep, 1);
  mem_free(pv);
}
