/*
 * Mach Kernel - Entropy Generating Device
 * Copyright (C) 2007 Free Software Foundation.
 * 
 * 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; either version 2, or (at your option)
 * any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "entropy.h"
#include <string.h>
#include <kern/mach_clock.h>

/* Variable Declrations */
static char entropy_buffer[ENTROPYBUFSIZE]; /* Entropy Buffer */
decl_simple_lock_data(static,entropy_lock); /* Lock to each function */ 
static int entropy_read_offset; /* Current read offset */
static int entropy_write_offset; /* Current write offset */
static int entropy_init_done = 0; /* If this device is already initalized */
static int entropy_in_use; /* Mark that this device is in use */
static queue_head_t entropy_read_queue; /* I/O queue requests for blocking read */

void entropyinit() {
  /* Initalize all variables to zero, and
   * setup our queues and locks */
  entropy_read_offset = 0;
  entropy_write_offset = 0;
  entropy_in_use = 0;
  queue_init(&entropy_read_queue);
  simple_lock_init(&entropy_lock);
  entropy_init_done = 1;
}

io_return_t entropyopen(dev_t dev, int flag, io_req_t ior) {
 /* Lock the function so we don't get a race condition */
  simple_lock(&entropy_lock);

  /* Check to see if we've initalized entropy */
  if(entropy_init_done == 0) {
	  entropyinit();
  }

  entropy_in_use = 1;

  /* We're done, unlock, and return success */
  simple_unlock(&entropy_lock);
  return D_SUCCESS;
}

io_return_t entropyclose(dev_t dev, int flag) {
  /* Lock so we don't get a race condition or something worse */
  simple_lock(&entropy_lock);
  entropy_in_use = 0;

  simple_unlock(&entropy_lock);
  return D_SUCCESS;
}

/* Needed to make the compiler happy */
static boolean_t entropy_read_done(io_req_t ior);

io_return_t entropyread(dev_t dev, io_req_t ior) {
  int err, amt, len;

  /* Allocate memory*/
  err = device_read_alloc(ior, ior->io_count);
  if (err != KERN_SUCCESS) {
    return err;
  }

  /* Lock the device */
  simple_lock(&entropy_lock);
  if (entropy_read_offset == entropy_write_offset) {
    /* We got no entropy at the moment, queue it up */
    if (ior->io_mode & D_NOWAIT) {
     /* Got a non-blocking socket, so just tell it we would block */
     simple_unlock(&entropy_lock);
     return D_WOULD_BLOCK;
    }

    /* Pass the point to the read_done function which */
    /* will notify if the IO read is REALLY done      */

    ior->io_done = entropy_read_done;
 
    // queue it up
    enqueue_tail(&entropy_read_queue, (queue_entry_t) ior);
    simple_unlock(&entropy_lock);
    return D_IO_QUEUED;
  }

 /* Determine how much we're reading out  */ 
 /* amt is how much we want to copy out   */
 /* len is how much we can copy out       */ 
 len = entropy_write_offset - entropy_read_offset;
 if (len < 0 )
   len += ENTROPYBUFSIZE;

 amt = ior->io_count;
 if (amt > len)
   amt = len;
 if (entropy_read_offset + amt <= ENTROPYBUFSIZE)
   {
     /* Copy available entropy into the device buffer */
     memcpy (ior->io_data, entropy_buffer + entropy_read_offset, amt);
   } else {
     /* We need to wrap around so do it in two copies */ 
     int cnt;
     cnt = ENTROPYBUFSIZE - entropy_read_offset;
     memcpy(ior->io_data, entropy_buffer + entropy_read_offset, cnt);
     memcpy(ior->io_data, entropy_buffer, amt - cnt); 
   }

  /* Move the read offset */
  entropy_read_offset += amt;
  if (entropy_read_offset >= ENTROPYBUFSIZE)
    entropy_read_offset -= ENTROPYBUFSIZE;

  /* Notify the caller how much data we were able to return */
  ior->io_residual = ior->io_count - amt;

  /* Unlock, and return success */
  simple_unlock(&entropy_lock);
  return D_SUCCESS;
}

/* This function called when the io read is complete */
/* by device_read (I think) 			     */
static boolean_t entropy_read_done(io_req_t ior) {
  int amt, len;

  /* Lock the device */
  simple_lock(&entropy_lock);
  if (entropy_read_offset == entropy_write_offset) {
    /* The queue is empty so return false */
    ior->io_done = entropy_read_done;
    enqueue_tail(&entropy_read_queue, (queue_entry_t) ior);
    simple_unlock (&entropy_lock);
    return FALSE;
  }

  len = entropy_write_offset - entropy_read_offset;
  if (len < 0)
    len += ENTROPYBUFSIZE;

  amt = ior->io_count;
  if (amt > len)
    amt = len;

  if (entropy_read_offset + amt <= ENTROPYBUFSIZE) {
    /* Copy the data fromt the buffer */
    memcpy (ior->io_data, entropy_buffer + entropy_read_offset, amt);
  } else {
    /* The buffer needs to wrap around, so copy in two installments */
    int cnt;

    cnt = ENTROPYBUFSIZE - entropy_read_offset;
    memcpy (ior->io_data, entropy_buffer + entropy_read_offset, cnt);
    memcpy (ior->io_data + cnt, entropy_buffer, amt - cnt);
  }

  entropy_read_offset += amt;
  if (entropy_read_offset >= ENTROPYBUFSIZE) {
    entropy_read_offset -= ENTROPYBUFSIZE;
  }
  
  ior->io_residual = ior->io_count - amt;

  /* Finish up, and unlock */  
  simple_unlock(&entropy_lock);
  ds_read_done (ior);

  return TRUE;
}

io_return_t entropygetstat(dev_t dev, int flavor,
		            int *data, unsigned int *count) {
  switch (flavor) {
     case DEV_GET_SIZE:
	data[DEV_GET_SIZE_DEVICE_SIZE] = 0;
	data[DEV_GET_SIZE_RECORD_SIZE] = 1;
	*count = DEV_GET_SIZE_COUNT;
	break;
    default:
	return D_INVALID_OPERATION;
  }

  return D_SUCCESS;
}

void entropy_putchar(int c) {
  /* We'll get data from a given source, and stick it in the buffer */
  io_req_t ior;
  /* Its possible we MIGHT get here before the device is opened */
  /* so just make sure we're initalized before doing anythign!  */

  if(!entropy_init_done) {
    entropyinit();
  }

  /* See if the buffer is full, if it is, bail out */
  int offset = entropy_write_offset + 1;

  if (offset == ENTROPYBUFSIZE) {
    offset = 0;
  }

  if (offset == entropy_read_offset) {
    return;
  }

 // Ok, we're going to TRY to lock since we don't want to block
  // trying to get the lock
  if (!simple_lock_try(&entropy_lock)) {
   /* Didn't get it, bail out */
    return;
  }

  entropy_write_offset += 1;

  if (entropy_write_offset == ENTROPYBUFSIZE) {
    entropy_write_offset = 0;
  }

   // Add the entropy to the buffer
  // And move the write offset
  entropy_buffer[entropy_write_offset] = c;

// Deep magic (tells any read functiosn more entropy is available)
  while ((ior = (io_req_t) dequeue_head (&entropy_read_queue)) != NULL)
    iodone (ior);

  simple_unlock(&entropy_lock);
}

void entropy_putdata(void *data, int size) {
  // This function is used to copy data from memory pointers directly
  // into the bufffer

  io_req_t ior; // Used to tell reads that we got entropy
  
  // See if we are initalized; if not, initalize the driver
  if (!entropy_init_done) {
    entropyinit();
  }
 
 
  /* See if the buffer is full, if it is, bail out */
  int offset = entropy_write_offset + 1;

  if (offset == ENTROPYBUFSIZE) {
    offset = 0;
  }

  if (offset == entropy_read_offset) {
    return;
  }

 // Try to grab a lock on the device, otherwise bail
  if (!simple_lock_try(&entropy_lock)) {
    return; // Bailing out
  }

  // Lets advance the pointer on
  entropy_write_offset++;

  // See if the buffer needs to wrap around
  if (entropy_write_offset == ENTROPYBUFSIZE) {
    entropy_write_offset = 0;
  }

  // Is the data we're copying in bigger then our buffer?
  if (size >= ENTROPYBUFSIZE) {
    // Set the offset to zero, and then copy set the size to the size of the buffer
    size = ENTROPYBUFSIZE;
  }

  // Do we need to wrap around?
  if (size + entropy_write_offset > ENTROPYBUFSIZE) {
    // Yup, we'll do the copy in two stops.
    int cnt;
    cnt = ENTROPYBUFSIZE - entropy_write_offset;
    memcpy(entropy_buffer + entropy_write_offset, data, cnt);
    memcpy(entropy_buffer, data, size - cnt);
  } else {
    // Nope, just copy
    memcpy(entropy_buffer + entropy_write_offset, data, size);
  }

  entropy_write_offset += size;
  if (entropy_write_offset >= ENTROPYBUFSIZE) {
    entropy_write_offset -= ENTROPYBUFSIZE;
  }

  // Tell any reads that we have more entropy
  while ((ior = (io_req_t) dequeue_head (&entropy_read_queue)) != NULL)
    iodone (ior);

  simple_unlock(&entropy_lock);
  return;
}

