/* Intel 82802 Firmware HUB Random Number Generator Driver
   Copyright (c) 2000 Matt Sottek msottek@quiknet.com

    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 of the License, 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, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

#define __KERNEL__
#define MODULE

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/proc_fs.h>
#include <linux/pci.h>
#include <linux/timer.h>
#include <linux/tqueue.h>
#include <linux/sched.h>
#include <linux/param.h>
#include <linux/delay.h>

#include <asm/uaccess.h>
#include <asm/segment.h>
#include <asm/io.h>

/*
 * Linux 2.2 compatibility
 */
#ifndef DECLARE_WAITQUEUE
#define DECLARE_WAITQUEUE(WAIT, PTR)    struct wait_queue WAIT = { PTR, NULL }
#endif
#ifndef DECLARE_WAIT_QUEUE_HEAD
#define DECLARE_WAIT_QUEUE_HEAD(WAIT) struct wait_queue *WAIT
#endif


#define i82802_ADDRESS 0xffbc015f
#define i82802_PRESENT_MASK 0x40
#define i82802_ENABLED_MASK 0x01
#define i82802_STATUS_MASK 0x01

#define RNG_MINOR 0
#define RNG_MAJOR 0

void rng_fips_test();

unsigned char *rng_mem;
unsigned int rng_minor=0;
unsigned int rng_major=0;
int poker[16],runs[12];
long int pokertest;
int longrun = 0;
int ones = 0;
int rng_status = 0;
struct timer_list rng_timer;
int currentr = 0;
int rlength = 0;
int laps = 0;
int stat_message=0;
MODULE_PARM(rng_minor,"i");
MODULE_PARM(rng_major,"i");

void disable_rng() {
  unsigned long flags;

  save_flags(flags);
  cli();
  if(rng_status) {
    writeb(0x40,rng_mem);
    rng_status = 0;
    restore_flags(flags);
    MOD_DEC_USE_COUNT;
    printk("<1> RNG Disabled\n");
  }
  else {
    restore_flags(flags);
  }
}

void enable_rng() {
  unsigned long flags;

  save_flags(flags);
  cli();
  if(!rng_status) {
     writeb(0x41,rng_mem);
     rng_status = 1;
     restore_flags(flags);
     MOD_INC_USE_COUNT;
     printk("<1> RNG Enabled\n");
     rng_fips_test();
  }
  else {
    restore_flags(flags);
  }
}

void rng_wakeup(u_long rng_wait_queue_headP) {
  wake_up_interruptible((wait_queue_head_t *)rng_wait_queue_headP);
}

/* These are the startup tests suggested by the FIPS 140-1 spec section
*  4.11.1 (http://csrc.nist.gov/fips/fips1401.htm)
*  The Monobit, Poker, Runs, and Long Runs tests are implemented below.
*  This test is run at startup and at periodic intervals to verify
*  data is sufficently random. If the tests are failed the RNG module
*  will no longer submit data to the entropy pool, but the tests will
*  continue to run at the given interval. If at a later time the RNG
*  passes all tests it will be re-enabled for the next period.
*   The reason for this is that it is not unlikely that at some time
*  during normal operation one of the tests will fail. This does not
*  necessarily mean the RNG is not operating properly, it is just a
*  statistically rare event. In that case we don't want to forever
*  disable the RNG, we will just leave it disabled for the period of
*  time until the tests are rerun.
*
*  For argument sake I tested /proc/urandom with these tests and it
*  took 142,095 tries before I got a failure, and urandom isn't as
*  random as random :)
*/
void rng_fips_test() {
  int j;
  unsigned char rbyte = 0;
  unsigned long flags=0;
  int passed=1;

  if(!laps) {
    /* Initialize */
    currentr = 0;
    rlength = 0;
    for(j=0; j<16; j++) {
      poker[j] = 0;
    }
    for(j=0; j<12; j++) {
      runs[j] = 0;
    }
    rlength = 999;
    ones = 0;
  }

  if(laps < 2500 ) {
    while(1) {
      save_flags(flags);
      cli();

      rbyte = readb(rng_mem);   
      // Check if we're enabled and status is ready
      if(!(rbyte & i82802_ENABLED_MASK)) {
	/* Someone turned us off mid test we'll just reset and
	   exit... no need to finish this run */
	laps = 0;
	restore_flags(flags);
	return;
      }
      rbyte = readb(rng_mem + 1);
      if(rbyte & i82802_STATUS_MASK) {
	rbyte = readb(rng_mem + 2);
	restore_flags(flags);
	break;
      }
      restore_flags(flags);
      mdelay(4);
    }

    poker[rbyte>>4] += 1;
    poker[rbyte & 15] += 1;

    /* Trick to make sure currentr != the first bit so we don't screw
       up the first runlength */
    if(rlength == 999) {
      currentr = !((rbyte & 128)>>7);
      runs[currentr * 6] = -1;
      rlength = 1;
    }
    for(j=7; j>=0; j--) {
      if(rbyte & 1<<j){ones++;}
      if(((rbyte & 1<<j)>>j) == currentr) {
	rlength++;
      }
      else {
	/* If runlength is 1-6 count it in correct bucket. 0's go in
	   runs[0-5] 1's go in runs[6-11] hence the 6*currentr below */
	if(rlength < 6) {
	  runs[rlength - 1 + (6*currentr)]++;
	}
	if(rlength >= 6) {
	  runs[5 + (6*currentr)]++;
	}
	/* Check if we just failed longrun test */
	if(rlength >= longrun) {
	  longrun = rlength;
	}
	rlength=1;
	/* flip the current run type */
	currentr = (rbyte & 1<<j)>>j;
      }
    }
    laps++;
  }
  else {
    /* add in the last (possibly incomplete) run */
    if(rlength <= 6) {
      runs[rlength - 1 + (6*currentr)]++;
    }
    if(rlength == 34) {
      longrun = 1;
    }

    /* Do poker test */
    pokertest = 0;

    for(j=0; j<16; j++) {
      pokertest += poker[j] * poker[j];
    }
    stat_message = 0;
    if(! ((ones < 10346) && (ones > 9654)) ){
      printk("<1> RNG failed Monobit test...disabling\n");
      stat_message = stat_message & 1; 
      passed = 0;
    }
    if(! ((pokertest < 1580457) && (pokertest > 1562821)) ){
      printk("<1> RNG failed Poker test...disabling\n");
      stat_message = stat_message & 2;
      passed = 0;
    }
    if(! ((runs[0] >= 2267) && (runs[0] <= 2733) &&
	  (runs[1] >= 1079) && (runs[1] <= 1421) &&
	  (runs[2] >= 502)  && (runs[2] <= 748) &&
	  (runs[3] >= 223)  && (runs[3] <= 402) &&
	  (runs[4] >= 90)   && (runs[4] <= 223) &&
	  (runs[5] >= 90)   && (runs[5] <= 223) &&
	  (runs[6] >= 2267) && (runs[6] <= 2733) &&
	  (runs[7] >= 1079) && (runs[7] <= 1421) &&
	  (runs[8] >= 502)  && (runs[8] <= 748) &&
	  (runs[9] >= 223)  && (runs[9] <= 402) &&
	  (runs[10] >= 90)   && (runs[10] <= 223) &&
	  (runs[11] >= 90)   && (runs[11] <= 223)) ) {
      printk("<1> RNG failed Runs test...disabling\n");
      stat_message = stat_message & 4;
      passed = 0;
    }
    if(longrun >= 34) {
      printk("<1> RNG failed LongRun test...disabling\n");
      stat_message = stat_message & 8;
      passed = 0;
    }
    if(passed){ enable_rng(); }
    else { disable_rng(); }
 
    laps = 0;
  }
  init_timer(&rng_timer);
  rng_timer.expires = jiffies + 1;
  rng_timer.function = (void *)rng_fips_test;
  rng_timer.data = (unsigned long)NULL;
  add_timer(&rng_timer);
  return;

  /*
   * Put rbyte in the entropy pool here!
   */
}



/*
 * Called when reading from the device. This should go away
 * if we can put data in the pool directly
*/
ssize_t rng_read(struct file *rng_file,char *buffer,size_t length,
		 loff_t *off) {
  unsigned char rbyte;
  int sleep_time=1;

  struct timer_list rng_stall_timer;
  DECLARE_WAITQUEUE(rng_wait_queue, current);
  DECLARE_WAIT_QUEUE_HEAD(rng_wait_queue_head);

  while(1) {
    rbyte = readb(rng_mem);
    // Check if we're enabled and status is ready
    if(!(rbyte & i82802_ENABLED_MASK)) {
      /* The RNG isn't on. We're going to sleep a long time */
      sleep_time = 100;
    }
    else {
      rbyte = readb(rng_mem + 1);
      if(rbyte & i82802_STATUS_MASK) {
	rbyte = readb(rng_mem + 2);
	break;
      }
      sleep_time = 1;
    }
    init_timer(&rng_stall_timer);
    rng_stall_timer.expires = jiffies + sleep_time;
    rng_stall_timer.function = (void *)rng_wakeup;
    rng_stall_timer.data = (unsigned long)&rng_wait_queue_head;
    add_timer(&rng_stall_timer);
    interruptible_sleep_on(&rng_wait_queue_head);
    if(signal_pending(current)) return -ERESTARTSYS;
  }
  copy_to_user(buffer,&rbyte,1);
  rng_file->f_pos++;
  return 1;
}

/* Open Device file
 */
int rng_open(struct inode *rng_inode,struct file *rng_file) {
  printk("<1> RNG Device opened\n");
  MOD_INC_USE_COUNT;
  return 0;
}

/* Close Device File
 */
int rng_release(struct inode *rng_inode, struct file *rng_file) {
  printk("<1> RNG Device closed\n");
  MOD_DEC_USE_COUNT;
  return 0;
}

struct file_operations rng_fops = {
  open:rng_open,
  release:rng_release,
  read:rng_read
};

int rng_proc_write(struct file *file, const char *buf,
		   unsigned long count, void *data) {

  if(count >= 1) {
    if(buf[0] == 48){disable_rng();}
    if(buf[0] == 49) {
       enable_rng();
    }
  }
  return 1;
}

int rng_proc_read(char *buf, char **start, off_t offset,
		   int len, int unused) {
  len = sprintf(buf, "Intel 82802 Random Number generator present.\n");
  if(rng_status == 1) {
    len += sprintf(buf + len, " Status: Enabled\n\n");
  }
  else {
    len += sprintf(buf + len, " Status: Disabled\n\n");
  }
  len += sprintf(buf + len, "Last statistical analysis results:\n");
  if(stat_message & 1) {len += sprintf(buf + len, "Failed Ones Test\n");}
  else {len += sprintf(buf + len, "Passed Ones Test\n");}

  if(stat_message & 2) {len += sprintf(buf + len, "Failed Poker Test\n");}
  else { len += sprintf(buf + len, "Passed Poker Test\n"); }

  if(stat_message & 4) { len += sprintf(buf + len, "Failed Runs Test\n");}
  else { len += sprintf(buf + len, "Passed Runs Test\n"); }

  if(stat_message & 8) {len += sprintf(buf + len, "Failed Longrun Test\n");}
  else { len += sprintf(buf + len, "Passed Longrun Test\n"); }

  return len;
}

/*
struct proc_dir_entry rng_proc_entry = {
  0,
  11,"sys/dev/rng",
  S_IFREG | S_IRUGO,
  1,0,0,
  0,
  NULL,
  (void *)&rng_proc_read,
};
*/
struct proc_dir_entry *rng_proc_entry;

struct proc_dir_entry proc_root;



/* Initialize Module:
 * RNG is detected and enabled if present. The fips 140-1 test is
 * then run to make sure device is operating properly. Since this
 * test takes a couple seconds it is scheduled for later and init_module
 * returns.
 */
int init_module(void) {
  int err;
  unsigned char input;
  unsigned char bus,function;

  /* Check for the 810 ICH first */
  if(pcibios_find_device(0x8086,0x2418,0,&bus,&function) &&
     pcibios_find_device(0x8086,0x2428,0,&bus,&function)) {
     printk("<1>RNG not found\n");
     return -EBUSY;
  }
  else {
    printk("<3>RNG found an 810 system\n");
  }
  /* Check for Intel 82802 */
  rng_mem = ioremap(i82802_ADDRESS,3);
  if(!rng_mem) {
    printk("<1>RNG Memory not mapped!\n");
    return -EBUSY;
  }
  input = readb(rng_mem);
  if(input & i82802_PRESENT_MASK) {
    printk("<1>i82802 RNG Detected\n");
    enable_rng();
  }
  else {
   printk("<1>RNG not found\n");
   return -EBUSY;
  }

  /* Allocate /proc entry */
  rng_proc_entry = create_proc_entry("sys/dev/rng", S_IFREG|S_IRUGO,
				     NULL);
  rng_proc_entry->get_info = (void *)rng_proc_read;
  rng_proc_entry->write_proc = (void *) rng_proc_write;

  /* Code for allocating major/minor number */
  if(!rng_minor){rng_minor = RNG_MINOR;}
  if(!rng_major){rng_major = RNG_MAJOR;}
  err = register_chrdev(rng_major,"rng",&rng_fops);
  if(err > 0){rng_major = err;}
  else {return err;}

  printk("<1>Intel 82802 RNG Module loaded.\n");
  return 0;
}

void cleanup_module(void){

  /* Release major/minor */
  unregister_chrdev(rng_major,"rng");

  /* Release proc entry */
  remove_proc_entry("sys/dev/rng",NULL);

  /* unmap io memory */
  iounmap(rng_mem);

  printk("<1> RNG Module has exited\n");
}


