/*
    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 <device/device.h>
#include <device/driver.h>
#include <stdbool.h>

#include "libssm/hsm.h"


#ifdef CONFIG_MUTEK_SCHEDULER
# include <mutek/scheduler.h>
# include <hexo/lock.h>
#endif

struct dev_hsm_wait_rq_s
{
#ifdef CONFIG_MUTEK_SCHEDULER
  lock_t lock;
  struct sched_context_s *ctx;
#endif
  bool_t done;
};

static DEVHSM_CALLBACK(dev_hsm_lock_request_cb)
{
  struct dev_hsm_wait_rq_s *status = rq->pvdata;
  status->done = 1;
}

#ifdef CONFIG_MUTEK_SCHEDULER
static DEVHSM_CALLBACK(dev_hsm_wait_request_cb)
{
  struct dev_hsm_wait_rq_s *status = rq->pvdata;

  lock_spin(&status->lock);

  if (status->ctx != NULL)
    sched_context_start(status->ctx);

  status->done = 1;

  lock_release(&status->lock);
}

static error_t dev_hsm_wait_rq_new_pspe(const struct device_hsm_s *hdev,
					paddr_t pspe_addr, paddr_t integrity_page,
					uint8_t integrity_idx, paddr_t iv_page,
					uint8_t iv_idx, uint16_t sp_idx,
					bool_t unprotected, enum sb_page_size_e size,
					devhsm_callback_t *callback)
{
  struct dev_hsm_rq_s rq;
  struct dev_hsm_wait_rq_s status;

  lock_init(&status.lock);
  status.ctx = NULL;
  status.done = 0;

  rq.error = 0;
  rq.pvdata = &status;
  rq.callback = callback;
  rq.type = HSM_NEW_PSPE;
  rq.pspe.addr = pspe_addr;
  rq.pspe.integrity_page = integrity_page;
  rq.pspe.integrity_idx = integrity_idx;
  rq.pspe.iv_page = iv_page;
  rq.pspe.iv_idx = iv_idx;
  rq.pspe.sp_idx = sp_idx;
  rq.pspe.unprotected = unprotected;
  rq.pspe.size = size;

  error_t err = DEVICE_OP(hdev, request, &rq);
  if (err)
    return err;

  CPU_INTERRUPT_SAVESTATE_DISABLE;
  lock_spin(&status.lock);

  if (!status.done)
  {
    status.ctx = sched_get_current();
    sched_stop_unlock(&status.lock);
  }
  else
    lock_release(&status.lock);
  
  CPU_INTERRUPT_RESTORESTATE;
  lock_destroy(&status.lock);

  return rq.error;
}
#endif

static error_t dev_hsm_lock_rq_new_pspe(const struct device_hsm_s *hdev,
					paddr_t pspe_addr, paddr_t integrity_page,
					uint8_t integrity_idx, paddr_t iv_page,
					uint8_t iv_idx, uint16_t sp_idx,
					bool_t unprotected, enum sb_page_size_e size,
					devhsm_callback_t *callback)
{
  struct dev_hsm_rq_s rq;
  struct dev_hsm_wait_rq_s status;

  status.done = 0;

  rq.error = 0;
  rq.pvdata = &status;
  rq.callback = callback;
  rq.type = HSM_NEW_PSPE;
  rq.pspe.addr = pspe_addr;
  rq.pspe.integrity_page = integrity_page;
  rq.pspe.integrity_idx = integrity_idx;
  rq.pspe.iv_page = iv_page;
  rq.pspe.iv_idx = iv_idx;
  rq.pspe.sp_idx = sp_idx;
  rq.pspe.unprotected = unprotected;
  rq.pspe.size = size;

  error_t err = DEVICE_OP(hdev, request, &rq);
  if (err)
    return err;

#ifdef CONFIG_DEVICE_IRQ
  assert(cpu_is_interruptible());
#endif

  while (!status.done)
    order_compiler_mem();

  return rq.error;
}

error_t dev_hsm_wait_new_pspe(const struct device_hsm_s *hdev,
			      paddr_t pspe_addr, paddr_t integrity_page,
			      uint8_t integrity_idx, paddr_t iv_page,
			      uint8_t iv_idx, uint16_t sp_idx,
			      bool_t protected, enum sb_page_size_e size)
{
#ifdef CONFIG_MUTEK_SCHEDULER
  return dev_hsm_wait_rq_new_pspe(hdev, pspe_addr, integrity_page,
				  integrity_idx, iv_page, iv_idx, sp_idx,
				  protected, size, dev_hsm_wait_request_cb);
#else
  return dev_hsm_lock_rq_new_pspe(hdev, pspe_addr, integrity_page,
				  integrity_idx, iv_page, iv_idx, sp_idx,
				  protected, size, dev_hsm_lock_request_cb);
#endif
}

error_t dev_hsm_lock_new_pspe(const struct device_hsm_s *hdev,
			      paddr_t pspe_addr, paddr_t integrity_page,
			      uint8_t integrity_idx, paddr_t iv_page,
			      uint8_t iv_idx, uint16_t sp_idx,
			      bool_t protected, enum sb_page_size_e size)
{
  return dev_hsm_lock_rq_new_pspe(hdev, pspe_addr, integrity_page,
				  integrity_idx, iv_page, iv_idx, sp_idx,
				  protected, size, dev_hsm_lock_request_cb);
}


static error_t dev_hsm_wait_rq_delete_pspe(const struct device_hsm_s *hdev,
					   paddr_t pspe_addr, devhsm_callback_t *callback)
{
  struct dev_hsm_rq_s rq;
  struct dev_hsm_wait_rq_s status;

  lock_init(&status.lock);
  status.ctx = NULL;
  status.done = 0;

  rq.error = 0;
  rq.pvdata = &status;
  rq.callback = callback;
  rq.type = HSM_DELETE_PSPE;
  rq.pspe.addr = pspe_addr;

  error_t err = DEVICE_OP(hdev, request, &rq);
  if (err)
    return err;

  CPU_INTERRUPT_SAVESTATE_DISABLE;
  lock_spin(&status.lock);

  if (!status.done)
  {
    status.ctx = sched_get_current();
    sched_stop_unlock(&status.lock);
  }
  else
    lock_release(&status.lock);
  
  CPU_INTERRUPT_RESTORESTATE;
  lock_destroy(&status.lock);

  return rq.error;
}

static error_t dev_hsm_lock_rq_delete_pspe(const struct device_hsm_s *hdev,
					   paddr_t pspe_addr, devhsm_callback_t *callback)
{
  struct dev_hsm_rq_s rq;
  struct dev_hsm_wait_rq_s status;

  status.done = 0;

  rq.error = 0;
  rq.pvdata = &status;
  rq.callback = callback;
  rq.type = HSM_DELETE_PSPE;
  rq.pspe.addr = pspe_addr;

  error_t err = DEVICE_OP(hdev, request, &rq);
  if (err)
    return err;

#ifdef CONFIG_DEVICE_IRQ
  assert(cpu_is_interruptible());
#endif

  while (!status.done)
    order_compiler_mem();

  return rq.error;
}

error_t dev_hsm_wait_delete_pspe(const struct device_hsm_s *hdev,
				 paddr_t pspe_addr)
{
#ifdef CONFIG_MUTEK_SCHEDULER
  return dev_hsm_wait_rq_delete_pspe(hdev, pspe_addr, dev_hsm_wait_request_cb);
#else
  return dev_hsm_lock_rq_delete_pspe(hdev, pspe_addr, dev_hsm_lock_request_cb);
#endif
}

error_t dev_hsm_lock_delete_pspe(const struct device_hsm_s *hdev,
				 paddr_t pspe_addr)
{
  return dev_hsm_lock_rq_delete_pspe(hdev, pspe_addr, dev_hsm_lock_request_cb);
}

#ifdef CONFIG_MUTEK_SCHEDULER
static error_t dev_hsm_wait_rq_new_sp(const struct device_hsm_s *hdev,
				      paddr_t sp_addr, bool_t c, bool_t i,
				      enum hsm_prot_mode_e mode,
				      uint32_t *key_c, uint32_t *key_i,
				      devhsm_callback_t *callback)
{
  struct dev_hsm_rq_s rq;
  struct dev_hsm_wait_rq_s status;

  lock_init(&status.lock);
  status.ctx = NULL;
  status.done = 0;

  rq.error = 0;
  rq.pvdata = &status;
  rq.callback = callback;
  rq.type = HSM_NEW_SP;
  rq.sp.addr = sp_addr;
  rq.sp.c = c;
  rq.sp.i = i;
  rq.sp.mode = mode;
  rq.sp.key_c = key_c;
  rq.sp.key_i = key_i;

  error_t err = DEVICE_OP(hdev, request, &rq);
  if (err)
    return err;

  CPU_INTERRUPT_SAVESTATE_DISABLE;
  lock_spin(&status.lock);

  if (!status.done)
  {
    status.ctx = sched_get_current();
    sched_stop_unlock(&status.lock);
  }
  else
    lock_release(&status.lock);
  
  CPU_INTERRUPT_RESTORESTATE;
  lock_destroy(&status.lock);

  return rq.error;
}
#endif

static error_t dev_hsm_lock_rq_new_sp(const struct device_hsm_s *hdev,
				      paddr_t sp_addr, bool_t c, bool_t i,
				      enum hsm_prot_mode_e mode,
				      uint32_t *key_c, uint32_t *key_i,
				      devhsm_callback_t *callback)
{
  struct dev_hsm_rq_s rq;
  struct dev_hsm_wait_rq_s status;

  status.done = 0;

  rq.error = 0;
  rq.pvdata = &status;
  rq.callback = callback;
  rq.type = HSM_NEW_SP;
  rq.sp.addr = sp_addr;
  rq.sp.c = c;
  rq.sp.i = i;
  rq.sp.mode = mode;
  rq.sp.key_c = key_c;
  rq.sp.key_i = key_i;

  error_t err = DEVICE_OP(hdev, request, &rq);
  if (err)
    return err;

#ifdef CONFIG_DEVICE_IRQ
  assert(cpu_is_interruptible());
#endif

  while (!status.done)
    order_compiler_mem();

  return rq.error;
}

error_t dev_hsm_wait_new_sp(const struct device_hsm_s *hdev,
			    paddr_t sp_addr, bool_t c, bool_t i,
			    enum hsm_prot_mode_e mode,
			    uint32_t *key_c, uint32_t *key_i)
{
#ifdef CONFIG_MUTEK_SCHEDULER
  return dev_hsm_wait_rq_new_sp(hdev, sp_addr, c, i, mode, key_c, key_i,
				dev_hsm_wait_request_cb);
#else
  return dev_hsm_lock_rq_new_sp(hdev, sp_addr, c, i, mode, key_c, key_i,
				dev_hsm_lock_request_cb);
#endif
}

error_t dev_hsm_lock_new_sp(const struct device_hsm_s *hdev,
			    paddr_t sp_addr, bool_t c, bool_t i,
			    enum hsm_prot_mode_e mode,
			    uint32_t *key_c, uint32_t *key_i)
{
  return dev_hsm_lock_rq_new_sp(hdev, sp_addr, c, i, mode, key_c, key_i,
				dev_hsm_lock_request_cb);
}

#ifdef CONFIG_MUTEK_SCHEDULER
static error_t dev_hsm_wait_rq_delete_sp(const struct device_hsm_s *hdev,
					 paddr_t sp_addr, devhsm_callback_t *callback)
{
  struct dev_hsm_rq_s rq;
  struct dev_hsm_wait_rq_s status;

  lock_init(&status.lock);
  status.ctx = NULL;
  status.done = 0;

  rq.error = 0;
  rq.pvdata = &status;
  rq.callback = callback;
  rq.type = HSM_DELETE_SP;
  rq.sp.addr = sp_addr;

  error_t err = DEVICE_OP(hdev, request, &rq);
  if (err)
    return err;

  CPU_INTERRUPT_SAVESTATE_DISABLE;
  lock_spin(&status.lock);

  if (!status.done)
  {
    status.ctx = sched_get_current();
    sched_stop_unlock(&status.lock);
  }
  else
    lock_release(&status.lock);
  
  CPU_INTERRUPT_RESTORESTATE;
  lock_destroy(&status.lock);

  return rq.error;
}
#endif

static error_t dev_hsm_lock_rq_delete_sp(const struct device_hsm_s *hdev,
					 paddr_t sp_addr, devhsm_callback_t *callback)
{
  struct dev_hsm_rq_s rq;
  struct dev_hsm_wait_rq_s status;

  status.done = 0;

  rq.error = 0;
  rq.pvdata = &status;
  rq.callback = callback;
  rq.type = HSM_DELETE_SP;
  rq.sp.addr = sp_addr;

  error_t err = DEVICE_OP(hdev, request, &rq);
  if (err)
    return err;

#ifdef CONFIG_DEVICE_IRQ
  assert(cpu_is_interruptible());
#endif

  while (!status.done)
    order_compiler_mem();

  return rq.error;
}

error_t dev_hsm_wait_delete_sp(const struct device_hsm_s *hdev,
			       paddr_t sp_addr)
{
#ifdef CONFIG_MUTEK_SCHEDULER
  return dev_hsm_wait_rq_delete_sp(hdev, sp_addr, dev_hsm_wait_request_cb);
#else
  return dev_hsm_lock_rq_delete_sp(hdev, sp_addr, dev_hsm_lock_request_cb);
#endif
}

error_t dev_hsm_lock_delete_sp(const struct device_hsm_s *hdev,
			       paddr_t sp_addr)
{
  return dev_hsm_lock_rq_delete_sp(hdev, sp_addr, dev_hsm_lock_request_cb);
}

#ifdef CONFIG_MUTEK_SCHEDULER
static error_t dev_hsm_wait_rq_integrity(const struct device_hsm_s *hdev,
					 paddr_t addr, bool_t new,
					 devhsm_callback_t *callback)
{
  struct dev_hsm_rq_s rq;
  struct dev_hsm_wait_rq_s status;

  lock_init(&status.lock);
  status.ctx = NULL;
  status.done = 0;

  rq.error = 0;
  rq.pvdata = &status;
  rq.callback = callback;
  rq.type = HSM_INTEGRITY;
  rq.integrity.addr = addr;
  rq.integrity.new = new;

  error_t err = DEVICE_OP(hdev, request, &rq);
  if (err)
    return err;

  CPU_INTERRUPT_SAVESTATE_DISABLE;
  lock_spin(&status.lock);

  if (!status.done)
  {
    status.ctx = sched_get_current();
    sched_stop_unlock(&status.lock);
  }
  else
    lock_release(&status.lock);
  
  CPU_INTERRUPT_RESTORESTATE;
  lock_destroy(&status.lock);

  return rq.error;
}
#endif

static error_t dev_hsm_lock_rq_integrity(const struct device_hsm_s *hdev,
					 paddr_t addr, bool_t new,
					 devhsm_callback_t *callback)
{
  struct dev_hsm_rq_s rq;
  struct dev_hsm_wait_rq_s status;

  status.done = 0;

  rq.error = 0;
  rq.pvdata = &status;
  rq.callback = callback;
  rq.type = HSM_INTEGRITY;
  rq.integrity.addr = addr;
  rq.integrity.new = new;

  error_t err = DEVICE_OP(hdev, request, &rq);
  if (err)
    return err;

#ifdef CONFIG_DEVICE_IRQ
  assert(cpu_is_interruptible());
#endif

  while (!status.done)
    order_compiler_mem();

  return rq.error;
}

error_t dev_hsm_wait_integrity(const struct device_hsm_s *hdev,
			       paddr_t addr, bool_t new)
{
#ifdef CONFIG_MUTEK_SCHEDULER
  return dev_hsm_wait_rq_integrity(hdev, addr, new, dev_hsm_wait_request_cb);
#else
  return dev_hsm_lock_rq_integrity(hdev, addr, new, dev_hsm_lock_request_cb);
#endif
}

error_t dev_hsm_lock_integrity(const struct device_hsm_s *hdev,
			       paddr_t addr, bool_t new)
{
  return dev_hsm_lock_rq_integrity(hdev, addr, new, dev_hsm_lock_request_cb);
}

#ifdef CONFIG_MUTEK_SCHEDULER
static error_t dev_hsm_wait_rq_read(const struct device_hsm_s *hdev,
				    paddr_t src, paddr_t dest,
				    devhsm_callback_t *callback)
{
  struct dev_hsm_rq_s rq;
  struct dev_hsm_wait_rq_s status;

  lock_init(&status.lock);
  status.ctx = NULL;
  status.done = 0;

  rq.error = 0;
  rq.pvdata = &status;
  rq.callback = callback;
  rq.type = HSM_READ;
  rq.group.src = src;
  rq.group.dest = dest;
  rq.group.read_ok = false;

  error_t err = DEVICE_OP(hdev, request, &rq);
  if (err)
    return err;

  CPU_INTERRUPT_SAVESTATE_DISABLE;
  lock_spin(&status.lock);

  if (!status.done)
  {
    status.ctx = sched_get_current();
    sched_stop_unlock(&status.lock);
  }
  else
    lock_release(&status.lock);
  
  CPU_INTERRUPT_RESTORESTATE;
  lock_destroy(&status.lock);

  rq.group.read_ok = true;

  process_end_read(hdev->dev, &rq);
//  hdev->dev->drv_pv->fwm->completedreadGroup(rq.group.dest);

/*  err = DEVICE_OP(hdev, request, &rq);
  printk("err2 %x\n", err);
  if (err)
    return err;
*/
  return rq.error;
}
#endif

static error_t dev_hsm_lock_rq_read(const struct device_hsm_s *hdev,
				    paddr_t src, paddr_t dest,
				    devhsm_callback_t *callback)
{
  struct dev_hsm_rq_s rq;
  struct dev_hsm_wait_rq_s status;

  status.done = 0;

  rq.error = 0;
  rq.pvdata = &status;
  rq.callback = callback;
  rq.type = HSM_READ;
  rq.group.src = src;
  rq.group.dest = dest;
  rq.group.read_ok = false;

  error_t err = DEVICE_OP(hdev, request, &rq);
  if (err)
  {
    return err;
  }

#ifdef CONFIG_DEVICE_IRQ
  assert(cpu_is_interruptible());
#endif

  while (!status.done)
  {
    order_compiler_mem();
  }

  rq.group.read_ok = true;

  process_end_read(hdev->dev, &rq);
/*
  err = DEVICE_OP(hdev, request, &rq);
  if (err)
    return err;
*/
  return rq.error;
}

error_t dev_hsm_wait_read(const struct device_hsm_s *hdev,
			  paddr_t src, paddr_t dest)
{
#ifdef CONFIG_MUTEK_SCHEDULER
  return dev_hsm_wait_rq_read(hdev, src, dest, dev_hsm_wait_request_cb);
#else
  return dev_hsm_lock_rq_read(hdev, src, dest, dev_hsm_lock_request_cb);
#endif
}

error_t dev_hsm_lock_read(const struct device_hsm_s *hdev,
			  paddr_t src, paddr_t dest)
{
  return dev_hsm_lock_rq_read(hdev, src, dest, dev_hsm_lock_request_cb);
}


#ifdef CONFIG_MUTEK_SCHEDULER
static error_t dev_hsm_wait_rq_write(const struct device_hsm_s *hdev,
				    paddr_t src, paddr_t dest,
				    devhsm_callback_t *callback)
{
  struct dev_hsm_rq_s rq;
  struct dev_hsm_wait_rq_s status;

  lock_init(&status.lock);
  status.ctx = NULL;
  status.done = 0;

  rq.error = 0;
  rq.pvdata = &status;
  rq.callback = callback;
  rq.type = HSM_WRITE;
  rq.group.src = src;
  rq.group.dest = dest;

  error_t err = DEVICE_OP(hdev, request, &rq);
  if (err)
    return err;

  CPU_INTERRUPT_SAVESTATE_DISABLE;
  lock_spin(&status.lock);

  if (!status.done)
  {
    status.ctx = sched_get_current();
    sched_stop_unlock(&status.lock);
  }
  else
    lock_release(&status.lock);
  
  CPU_INTERRUPT_RESTORESTATE;
  lock_destroy(&status.lock);

  return rq.error;
}
#endif

static error_t dev_hsm_lock_rq_write(const struct device_hsm_s *hdev,
				    paddr_t src, paddr_t dest,
				    devhsm_callback_t *callback)
{
  struct dev_hsm_rq_s rq;
  struct dev_hsm_wait_rq_s status;

  status.done = 0;

  rq.error = 0;
  rq.pvdata = &status;
  rq.callback = callback;
  rq.type = HSM_INTEGRITY;
  rq.type = HSM_READ;
  rq.group.src = src;
  rq.group.dest = dest;

  error_t err = DEVICE_OP(hdev, request, &rq);
  if (err)
    return err;

#ifdef CONFIG_DEVICE_IRQ
  assert(cpu_is_interruptible());
#endif

  while (!status.done)
    order_compiler_mem();

  return rq.error;
}

error_t dev_hsm_wait_write(const struct device_hsm_s *hdev,
			  paddr_t src, paddr_t dest)
{
#ifdef CONFIG_MUTEK_SCHEDULER
  return dev_hsm_wait_rq_write(hdev, src, dest, dev_hsm_wait_request_cb);
#else
  return dev_hsm_lock_rq_write(hdev, src, dest, dev_hsm_lock_request_cb);
#endif
}

error_t dev_hsm_lock_write(const struct device_hsm_s *hdev,
			  paddr_t src, paddr_t dest)
{
  return dev_hsm_lock_rq_write(hdev, src, dest, dev_hsm_lock_request_cb);
}

