/*
 * ----------------------------------------------------------------------------
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer,
 *    without modification.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY.
 *
 * ----------------------------------------------------------------------------
 *
 * Alternatively, this software may be distributed under the terms of the
 * terms of the GNU General Public License version 2 as published by the
 * Free Software Foundation.
 *
 * 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
 *
 * ----------------------------------------------------------------------------
 *
 * For history of changes, ChangeLog.txt
 *
 * This header file describes the register of the Quantis PCI card. The code has been adapted from
 * a microsoft provided sample file.
 *
 *
 *Module Name:

    QuantisOp.c  - Code to manipulate the Quantis card

Environment:

    Kernel/User mode
 */

 /* Number of trials to get random numbers when FIFO fills up
   faster that it can be read. */ 
#define MAX_COUNT_WATCHER 64

#include "CommonWindows.h"

QUANTIS_EXTERN_C_START

#include "Quantis.h"
#include "QuantisOp.h"

#include "QuantisOp.tmh" //auto generated by WPP

QuantisConfReg mask2reg(__in ModuleMask_t mask, __in int type)
{
  QuantisConfReg reg = (mask & (1 << 0) ? 1 << 0  : 0)
                | (mask & (1 << 1) ? 1 << 8  : 0)
                | (mask & (1 << 2) ? 1 << 16 : 0)
                | (mask & (1 << 3) ? 1 << 24 : 0);
  return reg << type;
}


ModuleMask_t reg2mask(__in QuantisConfReg reg,__in int type)
{
  reg >>= type;
  return (reg & (1 << 0)  ? (1 << 0) : 0)
    |    (reg & (1 << 8)  ? (1 << 1) : 0)
    |    (reg & (1 << 16) ? (1 << 2) : 0)
    |    (reg & (1 << 24) ? (1 << 3) : 0);
}


void quantis_flush_fifo(__in PQUANTIS_DEVICE_EXTENSION qdev)
{
  quantis_reg_set(qdev, &qdev->Regs->FC_CA, 0);
}

ModuleMask_t quantis_rng_error(__in PQUANTIS_DEVICE_EXTENSION qdev)
{
  QuantisConfReg reg    = quantis_reg_get(qdev, &qdev->Regs->MX_SR);
  QuantisConfReg test   = reg2mask(reg, MX_SOFTWARE_TEST_MODE);
  QuantisConfReg status = reg2mask(reg, MX_HARDWARE_READY);
  QuantisConfReg enable = reg2mask(reg, MX_SOFTWARE_ENABLE);

  if (test)
  {
    return 0;
  }
  else
  {
    return (enable & status) == 0;
  }
}


int quantis_rng_fifo_bytes(__in PQUANTIS_DEVICE_EXTENSION qdev)
{
  QuantisConfReg status = quantis_get_fifo_status(qdev);

  if (status & QUANTIS_FIFO_FULL)
  {
    return QUANTIS_FIFO_SIZE;
  }
  else if (status & QUANTIS_FIFO_FL3)
  {
    return (QUANTIS_FIFO_SIZE / 4 * 3);
  }
  else if (status & QUANTIS_FIFO_FL2)
  {
    return (QUANTIS_FIFO_SIZE / 2);
  }
  else if (status & QUANTIS_FIFO_FL1)
  {
    return (QUANTIS_FIFO_SIZE / 4);
  }
  else
  {
    return 0;
  }
}

int quantis_rng_wait_fifo(__in PQUANTIS_DEVICE_EXTENSION qdev)
{
  unsigned int timeout = 10000;
  unsigned int available_bytes = 0;

  while(timeout > 0)
  {
    available_bytes = quantis_rng_fifo_bytes(qdev);
    if (available_bytes > 0)
    {
      return available_bytes;
    }
    else
    {
       timeout--;
    }
  }

  /* Wait timed out*/
  return -1;
}

void quantis_rng_reset(__in PQUANTIS_DEVICE_EXTENSION qdev)
{
  quantis_reg_set(qdev, &qdev->Regs->CC_ER, 1);
  quantis_reg_set(qdev, &qdev->Regs->CC_DR, 1);
}

QuantisConfReg quantis_rng_version(__in PQUANTIS_DEVICE_EXTENSION qdev)
{
  return quantis_reg_get(qdev, &qdev->Regs->CV_SR);
}

void quantis_rng_enable_modules(__in PQUANTIS_DEVICE_EXTENSION qdev, __in ModuleMask_t mask)
{
  /* enable modules */
  quantis_reg_set(qdev, &qdev->Regs->MX_ER, mask2reg(mask, MX_SOFTWARE_ENABLE));

  /* Flush FIFO */
  quantis_flush_fifo(qdev);
}


void quantis_rng_disable_modules(__in PQUANTIS_DEVICE_EXTENSION qdev,__in ModuleMask_t mask)
{
  /* Disable modules */
  quantis_reg_set(qdev, &qdev->Regs->MX_DR, mask2reg(mask, MX_SOFTWARE_ENABLE));

  /* Flush FIFO */
  quantis_flush_fifo(qdev);
}


ModuleMask_t quantis_rng_modules_status(__in PQUANTIS_DEVICE_EXTENSION qdev)
{
  return reg2mask(quantis_reg_get(qdev, &qdev->Regs->MX_SR), MX_SOFTWARE_ENABLE)
      &  reg2mask(quantis_reg_get(qdev, &qdev->Regs->MX_SR), MX_HARDWARE_READY);
}


ModuleMask_t quantis_rng_modules_mask(__in PQUANTIS_DEVICE_EXTENSION qdev)
{
  return reg2mask(quantis_reg_get(qdev, &qdev->Regs->MX_SR), MX_HARDWARE_ENABLE);
}

int quantis_rng_read(__in PQUANTIS_DEVICE_EXTENSION qdev,
                      __out_bcount(length) unsigned char* buffer,
                      __in unsigned int length)
{
  unsigned int read_bytes = 0;
  unsigned int random_data_length;
  unsigned int available_bytes=0;
  unsigned int CountWatcher=0; /* Don't execute too many times (possibiliy infinitely) the loop in case
                                  of slow response time. */
  
  /* Verify at least one module is enabled and everything works correctly */
  if (quantis_rng_error(qdev))
  {
    /* Module status error (is at least one module enabled? */
      KdPrint(("No Quantis modules seems to be ready.\n"));
      DoTraceMessage(QUANTIS_ERROR,"No Quantis modules seems to be ready.\n");
    return -1;
  }

  KdPrint(("quantis_rng_read: %u bytes should be read\n",length));
  DoTraceMessage(QUANTIS_DEBUG,"quantis_rng_read: %u bytes should be read\n",length);
  
  /* Read random bytes */
  while (read_bytes < length)
  {    
    /* Get the number of bytes available on the FIFO */
    int readable_bytes = quantis_rng_wait_fifo(qdev);
    
    #ifdef QUANTIS_DEBUG
       KdPrint(("quantis_rng_read: %d read bytes, %d readable\n",read_bytes,readable_bytes));
       DoTraceMessage(QUANTIS_DEBUG,"quantis_rng_read: %d read bytes, %d readable\n",read_bytes,readable_bytes);
    #endif
    
    if (readable_bytes < 0)
    {
      KdPrint(("quantis_rng_read: Quantis card not ready for reading bytes.\n"));
      DoTraceMessage(QUANTIS_WARNING,"quantis_rng_read: Quantis card not ready for reading bytes.\n");
      /* Timeout while waiting random bytes */
      return -2;
    }
    else{
       available_bytes=(unsigned int)readable_bytes; //tricky, but to bypass warning
       if (available_bytes > (length - read_bytes))
       {
         available_bytes = (length - read_bytes);
       }
    }

    while (available_bytes > 0)
    {
      //       /* this can potentially cause endianness problems */
      RandomData random_data = quantis_reg_get(qdev, &qdev->Regs->FD_RR);
      QuantisConfRegValue fifo_status = quantis_get_fifo_status(qdev);
      random_data_length=sizeof(RandomData);
      
      KdPrint(("Integer read in random register: %x\n",random_data));

      if (fifo_status & QUANTIS_FIFO_ERROR)
      {
        /* The FIFO has overflown, reset it */
        KdPrint(("Fifo overlown.\n"));
        DoTraceMessage(QUANTIS_WARNING,"Fifo overlown.\n");
        quantis_flush_fifo(qdev);
        available_bytes = 0;
        CountWatcher++;
        if (CountWatcher > MAX_COUNT_WATCHER){
           /* Occurs when too slow response time (particularly on debugging) */
           KdPrint(("quantis_rng_read: Quantis card not ready for reading bytes (the buffer was %d times in error).\n",
                    CountWatcher));
           DoTraceMessage(QUANTIS_WARNING,
                          "quantis_rng_read: Quantis card not ready for reading bytes (the buffer was %d times in error).\n",
                          CountWatcher);
           return -2;
        }
        break;
      }

      /* Checks we don't read too much data just in case when only few bytes
         of unsigned int are valid. */
      if (random_data_length > available_bytes)
      {
        random_data_length = available_bytes;
      }

      /* Avoid buffer overflow */
      if ((read_bytes + random_data_length) > QUANTIS_DEVICE_BUFFER_SIZE)
      {
        return read_bytes;
      }

      /* copy random data to the buffer */
      if (random_data_length ==  sizeof(RandomData)){
        /* The amount of data to copy is the size of an integer */      
        ((PQuantisConfReg)buffer)[read_bytes/sizeof(RandomData)]=random_data;
      }
      else{
        /* Only few bytes of unsigned int are valid, only those bytes are copied */
        memcpy(buffer + read_bytes, &random_data, random_data_length);
      }
      available_bytes -= random_data_length;
      read_bytes += random_data_length;
    } // while (available_bytes > 0)
  } // while (read_bytes < length)

  return read_bytes;
}

                    
QUANTIS_EXTERN_C_END