/*
 * Copyright (c) 2010-2012 United States Government, as represented by
 * the Secretary of Defense.  All rights reserved.
 *
 * This code has been derived from drivers/char/tpm_vtpm.c
 * from the xen 2.6.18 linux kernel
 *
 * Copyright (C) 2006 IBM Corporation
 *
 * This code has also been derived from drivers/char/tpm_xen.c
 * from the xen 2.6.18 linux kernel
 *
 * Copyright (c) 2005, IBM Corporation
 *
 * which was itself derived from drivers/xen/netfront/netfront.c
 * from the linux kernel
 *
 * Copyright (c) 2002-2004, K A Fraser
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, version 2 of the
 * License.
 */
#include <mini-os/os.h>
#include <mini-os/xenbus.h>
#include <mini-os/xmalloc.h>
#include <mini-os/events.h>
#include <mini-os/wait.h>
#include <mini-os/gnttab.h>
#include <xen/io/xenbus.h>
#include <xen/io/tpmif.h>
#include <mini-os/tpmfront.h>
#include <mini-os/lib.h>
#include <fcntl.h>

//#define TPMFRONT_PRINT_DEBUG
#ifdef TPMFRONT_PRINT_DEBUG
#define TPMFRONT_DEBUG(fmt,...) printk("Tpmfront:Debug("__FILE__":%d) " fmt, __LINE__, ##__VA_ARGS__)
#define TPMFRONT_DEBUG_MORE(fmt,...) printk(fmt, ##__VA_ARGS__)
#else
#define TPMFRONT_DEBUG(fmt,...)
#endif
#define TPMFRONT_ERR(fmt,...) printk("Tpmfront:Error " fmt, ##__VA_ARGS__)
#define TPMFRONT_LOG(fmt,...) printk("Tpmfront:Info " fmt, ##__VA_ARGS__)

#define min(a,b) (((a) < (b)) ? (a) : (b))

void tpmfront_handler(evtchn_port_t port, struct pt_regs *regs, void *data) {
   struct tpmfront_dev* dev = (struct tpmfront_dev*) data;
   tpmif_shared_page_t *shr = dev->page;
   /*If we get a response when we didnt make a request, just ignore it */
   if(!dev->waiting) {
      return;
   }

   switch (shr->state) {
   case TPMIF_STATE_FINISH: /* request was completed */
   case TPMIF_STATE_IDLE:   /* request was cancelled */
      break;
   default:
      /* Spurious wakeup; do nothing, request is still pending */
      return;
   }

   dev->waiting = 0;
#ifdef HAVE_LIBC
   if(dev->fd >= 0) {
      files[dev->fd].read = 1;
   }
#endif
   wake_up(&dev->waitq);
}

static int publish_xenbus(struct tpmfront_dev* dev) {
   xenbus_transaction_t xbt;
   int retry;
   char* err;
   /* Write the grant reference and event channel to xenstore */
again:
   if((err = xenbus_transaction_start(&xbt))) {
      TPMFRONT_ERR("Unable to start xenbus transaction, error was %s\n", err);
      free(err);
      return -1;
   }

   if((err = xenbus_printf(xbt, dev->nodename, "ring-ref", "%u", (unsigned int) dev->ring_ref))) {
      TPMFRONT_ERR("Unable to write %s/ring-ref, error was %s\n", dev->nodename, err);
      free(err);
      goto abort_transaction;
   }

   if((err = xenbus_printf(xbt, dev->nodename, "event-channel", "%u", (unsigned int) dev->evtchn))) {
      TPMFRONT_ERR("Unable to write %s/event-channel, error was %s\n", dev->nodename, err);
      free(err);
      goto abort_transaction;
   }

   if((err = xenbus_transaction_end(xbt, 0, &retry))) {
      TPMFRONT_ERR("Unable to complete xenbus transaction, error was %s\n", err);
      free(err);
      return -1;
   }
   if(retry) {
      goto again;
   }

   return 0;
abort_transaction:
   if((err = xenbus_transaction_end(xbt, 1, &retry))) {
      free(err);
   }
   return -1;
}

static int wait_for_backend_connect(xenbus_event_queue* events, char* path)
{
   int state;

   TPMFRONT_LOG("Waiting for backend connection..\n");
   /* Wait for the backend to connect */
   while(1) {
      state = xenbus_read_integer(path);
      if ( state < 0)
	 state = XenbusStateUnknown;
      switch(state) {
	 /* Bad states, we quit with error */
	 case XenbusStateUnknown:
	 case XenbusStateClosing:
	 case XenbusStateClosed:
	    TPMFRONT_ERR("Unable to connect to backend\n");
	    return -1;
	 /* If backend is connected then break out of loop */
	 case XenbusStateConnected:
	    TPMFRONT_LOG("Backend Connected\n");
	    return 0;
	 default:
	    xenbus_wait_for_watch(events);
      }
   }

}

static int wait_for_backend_closed(xenbus_event_queue* events, char* path)
{
   int state;

   TPMFRONT_LOG("Waiting for backend to close..\n");
   while(1) {
      state = xenbus_read_integer(path);
      if ( state < 0)
	 state = XenbusStateUnknown;
      switch(state) {
	 case XenbusStateUnknown:
	    TPMFRONT_ERR("Backend Unknown state, forcing shutdown\n");
	    return -1;
	 case XenbusStateClosed:
	    TPMFRONT_LOG("Backend Closed\n");
	    return 0;
	 case XenbusStateInitWait:
	    TPMFRONT_LOG("Backend Closed (waiting for reconnect)\n");
	    return 0;
	 default:
	    xenbus_wait_for_watch(events);
      }
   }

}

static int wait_for_backend_state_changed(struct tpmfront_dev* dev, XenbusState state) {
   char* err;
   int ret = 0;
   xenbus_event_queue events = NULL;
   char path[512];

   snprintf(path, 512, "%s/state", dev->bepath);
   /*Setup the watch to wait for the backend */
   if((err = xenbus_watch_path_token(XBT_NIL, path, path, &events))) {
      TPMFRONT_ERR("Could not set a watch on %s, error was %s\n", path, err);
      free(err);
      return -1;
   }

   /* Do the actual wait loop now */
   switch(state) {
      case XenbusStateConnected:
	 ret = wait_for_backend_connect(&events, path);
	 break;
      case XenbusStateClosed:
	 ret = wait_for_backend_closed(&events, path);
	 break;
      default:
         TPMFRONT_ERR("Bad wait state %d, ignoring\n", state);
   }

   if((err = xenbus_unwatch_path_token(XBT_NIL, path, path))) {
      TPMFRONT_ERR("Unable to unwatch %s, error was %s, ignoring..\n", path, err);
      free(err);
   }
   return ret;
}

static int tpmfront_connect(struct tpmfront_dev* dev)
{
   char* err;
   /* Create shared page */
   dev->page = (tpmif_shared_page_t *)alloc_page();
   if(dev->page == NULL) {
      TPMFRONT_ERR("Unable to allocate page for shared memory\n");
      goto error;
   }
   memset(dev->page, 0, PAGE_SIZE);
   dev->ring_ref = gnttab_grant_access(dev->bedomid, virt_to_mfn(dev->page), 0);
   TPMFRONT_DEBUG("grant ref is %lu\n", (unsigned long) dev->ring_ref);

   /*Create event channel */
   if(evtchn_alloc_unbound(dev->bedomid, tpmfront_handler, dev, &dev->evtchn)) {
      TPMFRONT_ERR("Unable to allocate event channel\n");
      goto error_postmap;
   }
   unmask_evtchn(dev->evtchn);
   TPMFRONT_DEBUG("event channel is %lu\n", (unsigned long) dev->evtchn);

   /* Write the entries to xenstore */
   if(publish_xenbus(dev)) {
      goto error_postevtchn;
   }

   /* Change state to connected */
   dev->state = XenbusStateConnected;

   /* Tell the backend that we are ready */
   if((err = xenbus_printf(XBT_NIL, dev->nodename, "state", "%u", dev->state))) {
      TPMFRONT_ERR("Unable to write to xenstore %s/state, value=%u", dev->nodename, XenbusStateConnected);
      free(err);
      goto error;
   }

   return 0;
error_postevtchn:
      mask_evtchn(dev->evtchn);
      unbind_evtchn(dev->evtchn);
error_postmap:
      gnttab_end_access(dev->ring_ref);
      free_page(dev->page);
error:
   return -1;
}

struct tpmfront_dev* init_tpmfront(const char* _nodename)
{
   struct tpmfront_dev* dev;
   const char* nodename;
   char path[512];
   char* value, *err;
   unsigned long long ival;

   printk("============= Init TPM Front ================\n");

   dev = malloc(sizeof(struct tpmfront_dev));
   memset(dev, 0, sizeof(struct tpmfront_dev));

#ifdef HAVE_LIBC
   dev->fd = -1;
#endif

   nodename = _nodename ? _nodename : "device/vtpm/0";
   dev->nodename = strdup(nodename);

   init_waitqueue_head(&dev->waitq);

   /* Get backend domid */
   snprintf(path, 512, "%s/backend-id", dev->nodename);
   if((err = xenbus_read(XBT_NIL, path, &value))) {
      TPMFRONT_ERR("Unable to read %s during tpmfront initialization! error = %s\n", path, err);
      free(err);
      goto error;
   }
   if(sscanf(value, "%llu", &ival) != 1) {
      TPMFRONT_ERR("%s has non-integer value (%s)\n", path, value);
      free(value);
      goto error;
   }
   free(value);
   dev->bedomid = ival;

   /* Get backend xenstore path */
   snprintf(path, 512, "%s/backend", dev->nodename);
   if((err = xenbus_read(XBT_NIL, path, &dev->bepath))) {
      TPMFRONT_ERR("Unable to read %s during tpmfront initialization! error = %s\n", path, err);
      free(err);
      goto error;
   }

   /* Publish protocol v2 feature */
   snprintf(path, 512, "%s/feature-protocol-v2", dev->nodename);
   if ((err = xenbus_write(XBT_NIL, path, "1")))
   {
      TPMFRONT_ERR("Unable to write feature-protocol-v2 node: %s\n", err);
      free(err);
      goto error;
   }

   /* Create and publish grant reference and event channel */
   if (tpmfront_connect(dev)) {
      goto error;
   }

   /* Wait for backend to connect */
   if( wait_for_backend_state_changed(dev, XenbusStateConnected)) {
      goto error;
   }

   /* Ensure backend is also using protocol v2 */
   snprintf(path, 512, "%s/feature-protocol-v2", dev->bepath);
   if((err = xenbus_read(XBT_NIL, path, &value))) {
      TPMFRONT_ERR("Unable to read %s during tpmfront initialization! error = %s\n", path, err);
      free(err);
      goto error;
   }
   if(strcmp(value, "1")) {
      TPMFRONT_ERR("%s has an invalid value (%s)\n", path, value);
      free(value);
      goto error;
   }
   free(value);

   TPMFRONT_LOG("Initialization Completed successfully\n");

   return dev;

error:
   shutdown_tpmfront(dev);
   return NULL;
}
void shutdown_tpmfront(struct tpmfront_dev* dev)
{
   char* err;
   char path[512];
   if(dev == NULL) {
      return;
   }
   TPMFRONT_LOG("Shutting down tpmfront\n");
   /* disconnect */
   if(dev->state == XenbusStateConnected) {
      /* Tell backend we are closing */
      dev->state = XenbusStateClosing;
      if((err = xenbus_printf(XBT_NIL, dev->nodename, "state", "%u", (unsigned int) dev->state))) {
	 TPMFRONT_ERR("Unable to write to %s, error was %s", dev->nodename, err);
	 free(err);
      }

      /* Clean up xenstore entries */
      snprintf(path, 512, "%s/event-channel", dev->nodename);
      if((err = xenbus_rm(XBT_NIL, path))) {
	 free(err);
      }
      snprintf(path, 512, "%s/ring-ref", dev->nodename);
      if((err = xenbus_rm(XBT_NIL, path))) {
	 free(err);
      }

      /* Tell backend we are closed */
      dev->state = XenbusStateClosed;
      if((err = xenbus_printf(XBT_NIL, dev->nodename, "state", "%u", (unsigned int) dev->state))) {
	 TPMFRONT_ERR("Unable to write to %s, error was %s", dev->nodename, err);
	 free(err);
      }

      /* Wait for the backend to close and unmap shared pages, ignore any errors */
      wait_for_backend_state_changed(dev, XenbusStateClosed);

      /* Prepare for a later reopen (possibly by a kexec'd kernel) */
      dev->state = XenbusStateInitialising;
      if((err = xenbus_printf(XBT_NIL, dev->nodename, "state", "%u", (unsigned int) dev->state))) {
	 TPMFRONT_ERR("Unable to write to %s, error was %s", dev->nodename, err);
	 free(err);
      }

      /* Close event channel and unmap shared page */
      mask_evtchn(dev->evtchn);
      unbind_evtchn(dev->evtchn);
      gnttab_end_access(dev->ring_ref);

      free_page(dev->page);
   }

   /* Cleanup memory usage */
   if(dev->respbuf) {
      free(dev->respbuf);
   }
   if(dev->bepath) {
      free(dev->bepath);
   }
   if(dev->nodename) {
      free(dev->nodename);
   }
   free(dev);
}

int tpmfront_send(struct tpmfront_dev* dev, const uint8_t* msg, size_t length)
{
   unsigned int offset;
   tpmif_shared_page_t *shr = NULL;
#ifdef TPMFRONT_PRINT_DEBUG
   int i;
#endif
   /* Error Checking */
   if(dev == NULL || dev->state != XenbusStateConnected) {
      TPMFRONT_ERR("Tried to send message through disconnected frontend\n");
      return -1;
   }
   shr = dev->page;

#ifdef TPMFRONT_PRINT_DEBUG
   TPMFRONT_DEBUG("Sending Msg to backend size=%u", (unsigned int) length);
   for(i = 0; i < length; ++i) {
      if(!(i % 30)) {
	 TPMFRONT_DEBUG_MORE("\n");
      }
      TPMFRONT_DEBUG_MORE("%02X ", msg[i]);
   }
   TPMFRONT_DEBUG_MORE("\n");
#endif

   /* Copy to shared pages now */
   offset = sizeof(*shr);
   if (length + offset > PAGE_SIZE) {
      TPMFRONT_ERR("Message too long for shared page\n");
      return -1;
   }
   memcpy(offset + (uint8_t*)shr, msg, length);
   shr->length = length;
   barrier();
   shr->state = TPMIF_STATE_SUBMIT;

   dev->waiting = 1;
   dev->resplen = 0;
#ifdef HAVE_LIBC
   if(dev->fd >= 0) {
      files[dev->fd].read = 0;
      files[dev->fd].tpmfront.respgot = 0;
      files[dev->fd].tpmfront.offset = 0;
   }
#endif
   wmb();
   notify_remote_via_evtchn(dev->evtchn);
   return 0;
}
int tpmfront_recv(struct tpmfront_dev* dev, uint8_t** msg, size_t *length)
{
   unsigned int offset;
   tpmif_shared_page_t *shr = NULL;
#ifdef TPMFRONT_PRINT_DEBUG
int i;
#endif
   if(dev == NULL || dev->state != XenbusStateConnected) {
      TPMFRONT_ERR("Tried to receive message from disconnected frontend\n");
      return -1;
   }
   /*Wait for the response */
   wait_event(dev->waitq, (!dev->waiting));
   shr = dev->page;

   /* Initialize */
   *msg = NULL;
   *length = 0;
   offset = sizeof(*shr);

   if (shr->state != TPMIF_STATE_FINISH)
      goto quit;

   *length = shr->length;

   if (*length + offset > PAGE_SIZE) {
      TPMFRONT_ERR("Reply too long for shared page\n");
      return -1;
   }

   /* Alloc the buffer */
   if(dev->respbuf) {
      free(dev->respbuf);
   }
   *msg = dev->respbuf = malloc(*length);
   dev->resplen = *length;

   /* Copy the bits */
   memcpy(*msg, offset + (uint8_t*)shr, *length);

#ifdef TPMFRONT_PRINT_DEBUG
   TPMFRONT_DEBUG("Received response from backend size=%u", (unsigned int) *length);
   for(i = 0; i < *length; ++i) {
      if(!(i % 30)) {
	 TPMFRONT_DEBUG_MORE("\n");
      }
      TPMFRONT_DEBUG_MORE("%02X ", (*msg)[i]);
   }
   TPMFRONT_DEBUG_MORE("\n");
#endif
#ifdef HAVE_LIBC
   if(dev->fd >= 0) {
      files[dev->fd].tpmfront.respgot = 1;
   }
#endif
quit:
   return 0;
}

int tpmfront_cmd(struct tpmfront_dev* dev, uint8_t* req, size_t reqlen, uint8_t** resp, size_t* resplen)
{
   int rc;
   if((rc = tpmfront_send(dev, req, reqlen))) {
      return rc;
   }
   if((rc = tpmfront_recv(dev, resp, resplen))) {
      return rc;
   }

   return 0;
}

int tpmfront_set_locality(struct tpmfront_dev* dev, int locality)
{
   if (!dev || !dev->page)
      return -1;
   dev->page->locality = locality;
   return 0;
}

#ifdef HAVE_LIBC
#include <errno.h>
int tpmfront_open(struct tpmfront_dev* dev)
{
   /* Silently prevent multiple opens */
   if(dev->fd != -1) {
      return dev->fd;
   }

   dev->fd = alloc_fd(FTYPE_TPMFRONT);
   printk("tpmfront_open(%s) -> %d\n", dev->nodename, dev->fd);
   files[dev->fd].tpmfront.dev = dev;
   files[dev->fd].tpmfront.offset = 0;
   files[dev->fd].tpmfront.respgot = 0;
   return dev->fd;
}

int tpmfront_posix_write(int fd, const uint8_t* buf, size_t count)
{
   int rc;
   struct tpmfront_dev* dev;
   dev = files[fd].tpmfront.dev;

   if(count == 0) {
      return 0;
   }

   /* Return an error if we are already processing a command */
   if(dev->waiting) {
      errno = EINPROGRESS;
      return -1;
   }
   /* Send the command now */
   if((rc = tpmfront_send(dev, buf, count)) != 0) {
      errno = EIO;
      return -1;
   }
   return count;
}

int tpmfront_posix_read(int fd, uint8_t* buf, size_t count)
{
   int rc;
   uint8_t* dummybuf;
   size_t dummysz;
   struct tpmfront_dev* dev;

   dev = files[fd].tpmfront.dev;

   if(count == 0) {
      return 0;
   }

   /* get the response if we haven't already */
   if(files[dev->fd].tpmfront.respgot == 0) {
      if ((rc = tpmfront_recv(dev, &dummybuf, &dummysz)) != 0) {
	 errno = EIO;
	 return -1;
      }
   }

   /* handle EOF case */
   if(files[dev->fd].tpmfront.offset >= dev->resplen) {
      return 0;
   }

   /* Compute the number of bytes and do the copy operation */
   if((rc = min(count, dev->resplen - files[dev->fd].tpmfront.offset)) != 0) {
      memcpy(buf, dev->respbuf + files[dev->fd].tpmfront.offset, rc);
      files[dev->fd].tpmfront.offset += rc;
   }

   return rc;
}

int tpmfront_posix_fstat(int fd, struct stat* buf)
{
   uint8_t* dummybuf;
   size_t dummysz;
   int rc;
   struct tpmfront_dev* dev = files[fd].tpmfront.dev;

   /* If we have a response waiting, then read it now from the backend
    * so we can get its length*/
   if(dev->waiting || (files[dev->fd].read == 1 && !files[dev->fd].tpmfront.respgot)) {
      if ((rc = tpmfront_recv(dev, &dummybuf, &dummysz)) != 0) {
	 errno = EIO;
	 return -1;
      }
   }

   buf->st_mode = O_RDWR;
   buf->st_uid = 0;
   buf->st_gid = 0;
   buf->st_size = dev->resplen;
   buf->st_atime = buf->st_mtime = buf->st_ctime = time(NULL);

   return 0;
}


#endif
