/*
 * 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 */

/*
 * The following is from linux's random.c, and is used
 * under the GPL
 *
 * Look there for a full explination on what these numbers
 * mean and how this works
 */

static struct poolinfo {
	int poolwords;
	int tap1, tap2, tap3, tap4, tap5;
  } poolinfo_table [] = {
    /* x^128 + x^103 + x^76 + x^51 +x^25 + x + 1 -- 105 */
    { 128,	103,	76,	51,	25,	1 },
    /* x^32 + x^26 + x^20 + x^14 + x^7 + x + 1 -- 15 */
    { 32,	26,	20,	14,	7,	1 },
    /* x^2048 + x^1638 + x^1231 + x^819 + x^411 + x + 1  -- 115 */
    {  2048,	1638,	1231,	819,	411,	1 },
    /* x^1024 + x^817 + x^615 + x^412 + x^204 + x + 1 -- 290 */
    { 1024,	817,	615,	412,	204,	1 },
    /* x^1024 + x^819 + x^616 + x^410 + x^207 + x^2 + 1 -- 115 */
    { 1024,	819,	616,	410,	207,	2 },
    /* x^512 + x^411 + x^308 + x^208 + x^104 + x + 1 -- 225 */
    { 512,	411,	308,	208,	104,	1 },
    /* x^512 + x^409 + x^307 + x^206 + x^102 + x^2 + 1 -- 95 */
    { 512,	409,	307,	206,	102,	2 },
    /* x^512 + x^409 + x^309 + x^205 + x^103 + x^2 + 1 -- 95 */
    { 512,	409,	309,	205,	103,	2 },
    /* x^256 + x^205 + x^155 + x^101 + x^52 + x + 1 -- 125 */
    { 256,	205,	155,	101,	52,	1 },
    /* x^128 + x^103 + x^78 + x^51 + x^27 + x^2 + 1 -- 70 */
    { 128,	103,	78,	51,	27,	2 },
    /* x^64 + x^52 + x^39 + x^26 + x^14 + x + 1 -- 15 */
    { 64,	52,	39,	26,	14,	1 },
};

const int number_of_taps = 10; // This number is the number of taps avoid - 1

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;
  int offset;
  //entropy_data rand_data;

  /* 
   * Twist table, and twisting and mixing code adopted from
   * Linux's random.c
   */
  static unsigned long const twist_table [8] = {
    0x00000000, 0x3b6e20c8, 0x76dc4190, 0x4db26158,
    0xedb88320, 0xd6d6a3e8, 0x9b64c2b0, 0xa00ae278 };
   unsigned long int tap1, tap2, tap3, tap4, tap5;
  int wordmask;
  static int input_rotate; /* How much to rotate the bits by ... */
  static short current_tap; /* What tap to use for this */
  unsigned long word;
  
  /* 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();
  }

  // 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;
  }

 /* Setup the offset pointer to add data */
  offset = entropy_write_offset + 1;
  if (offset + sizeof(unsigned long)  == ENTROPYBUFSIZE) {
    offset = 0;
  }

  if (offset == entropy_read_offset) {
    /* Buffer's full, just bail out */
    simple_unlock(&entropy_lock);
    return;
  }


  /* Get the taps based on which array to use */
  wordmask = poolinfo_table[current_tap].poolwords - 1;
  tap1 = poolinfo_table[current_tap].tap1;
  tap2 = poolinfo_table[current_tap].tap2;
  tap3 = poolinfo_table[current_tap].tap3;
  tap4 = poolinfo_table[current_tap].tap4;
  tap5 = poolinfo_table[current_tap].tap5;
  
  /* Mix the tap into the entropy coming in */
  c++;
  word = ((c << input_rotate) | (c >> (32 - input_rotate)));

  /* XOR in the various taps, and add to the buffer, then */
  /* advice the write pointer 				  */
  word ^= entropy_buffer[(offset + tap1) & wordmask];
  word ^= entropy_buffer[(offset + tap2) & wordmask];
  word ^= entropy_buffer[(offset + tap3) & wordmask];
  word ^= entropy_buffer[(offset + tap4) & wordmask];
  word ^= entropy_buffer[(offset + tap5) & wordmask];
  entropy_buffer[offset] = (word >> 3) ^ twist_table[word & 7];

  /* Now lets update the offset pointer, tell any waiting queue requests we got new    */
  /* buffered entropy for them to use (we'll have to add a hook somewhere to mix the   */
  /* entropy somehwere ...							       */

  entropy_write_offset += sizeof(unsigned long);

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

  while ((ior = (io_req_t) dequeue_head (&entropy_read_queue)) != NULL)
    iodone (ior);

  simple_unlock(&entropy_lock);
}
