/*
 * Copyright (c) 2004, 2005, 2006, id Quantique SA, Switzerland
 * Copyright (c) 2010, Alex Dupre
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * 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.
 * Neither the name of id Quantique nor the names of its contributors may be
 * used to endorse or promote products derived from this software without
 * specific prior written permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/**# Introduction **/

/**## Preamble
 **
 ** This is a FreeBSD driver for the Quantis PCI card.
 **/

/**## Kernel includes
 **
 ** We need some include files to get the kernel functionality.
 **/
#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>   /* cdevsw stuff */
#include <sys/kernel.h> /* SYSINIT stuff */
#include <sys/uio.h>    /* SYSINIT stuff */
#include <sys/malloc.h> /* malloc region definitions */
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <sys/ioccom.h>
#include <sys/lock.h>
#include <sys/mutex.h>

#include <machine/bus.h>
#include <machine/resource.h>

#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>

/** The `DEV2SOFTC' and `DEVICE2SOFTC' are used to
 ** get a pointer to our private card data. Finally, this is the first
 ** version of the PCI driver, as well as a simple debug routine.
 **/

#include "../common/quantis_pci.h"
#include "../common/quantis_pci_common.h"

#define DEV2SOFTC(dev)  ((struct quantis_pci_device *) (dev)->si_drv1)
#define DEVICE2SOFTC(dev) ((struct quantis_pci_device *) device_get_softc(dev))

#ifdef DEBUG
# define QUANTIS_DEBUG(fmt, args...) uprintf(fmt "\n", ## args)
#else
# define QUANTIS_DEBUG(fmt, args...)
#endif
#define QUANTIS_DEBUG0 QUANTIS_DEBUG
#define QUANTIS_DEBUG2 QUANTIS_DEBUG

/**## PCI Card specific data **/

/** The following structure is used and initialized once per card. It
 ** holds a buffer to copy FIFO data to when interfacing when userland,
 ** and holds the references to the register page (resource and bus
 ** space handles).
 **/
struct quantis_pci_device 
{
  struct mtx mutex;
  int regid, regtype;
  struct resource* reg;  /* Resource for port range. */
  device_t device;
  struct cdev *dev;
  unsigned char buffer[QUANTIS_DEVICE_BUFFER_SIZE];
};

/****************************** Shared functions *****************************
 *
 * Drivers are not allowed to directly access the hardware, but have to use
 * the shared quantis functions (defined in quantis_pci_common.h).
 *
 * quantis_reg_get and quantis_reg_set must be defined here since are
 * os-specific.
 */

quantis_register_value quantis_reg_get(quantis_pci_device* qdev, quantis_register reg)
{
  return (bus_read_4(qdev->reg, reg));
}


void quantis_reg_set(quantis_pci_device* qdev, quantis_register reg, quantis_register_value value)
{
  bus_write_4(qdev->reg, reg, value);
}

/**# Kernel PCI interface routines **/

/** Most of the routines in the following part of the driver have been
 ** generated by the `make_device_driver.sh' script. I will document
 ** only those parts I changed.
 **/

/* Function prototypes (these should all be static). */
static int quantis_deallocate_resources(device_t device);
static int quantis_allocate_resources(device_t device);
static int quantis_attach(device_t device, struct quantis_pci_device *scp);
static int quantis_detach(device_t device, struct quantis_pci_device *scp);

static d_open_t    quantis_open;
static d_close_t  quantis_close;
static d_read_t    quantis_read;
static d_write_t  quantis_write;
static d_ioctl_t  quantis_ioctl;

/** We don't use a write routine, nor a poll nor a mmap routine. The
 ** entries were zeroed out and the functions erased.
 **/
static struct cdevsw quantis_cdevsw = 
{
  .d_version =  D_VERSION,
  .d_open =  quantis_open,
  .d_close =  quantis_close,
  .d_read =  quantis_read,
  .d_write =  quantis_write,
  .d_ioctl =  quantis_ioctl,
  .d_name =  "quantis",
};

static devclass_t quantis_devclass;

/** Keep track of the numbers of card we manage for the corresponding
 ** IOCTL call. I guess this is already managed in some `device_t'
 ** structure.
 **/
static struct mtx quantis_mutex;
static int card_count = 0;

static int quantis_pci_probe(device_t);
static int quantis_pci_attach(device_t);
static int quantis_pci_detach(device_t);

static device_method_t quantis_pci_methods[] = 
{
  /* Device interface */
  DEVMETHOD(device_probe,    quantis_pci_probe),
  DEVMETHOD(device_attach,  quantis_pci_attach),
  DEVMETHOD(device_detach,  quantis_pci_detach),
  { 0, 0 }
};


static driver_t quantis_pci_driver = 
{
  "quantis",
  quantis_pci_methods,
  sizeof(struct quantis_pci_device),
};


static int quantis_load(struct module *module, int cmd, void *arg)
{
  int error = 0;
  switch (cmd) 
  {
    case MOD_LOAD:
      QUANTIS_DEBUG("Load quantis module\n");
      mtx_init(&quantis_mutex, "Quantis Global Mutex", NULL, MTX_DEF);
      break;
  
    case MOD_UNLOAD:
      QUANTIS_DEBUG("Unload quantis module\n");
      mtx_destroy(&quantis_mutex);
      break;

    default:
      error = EOPNOTSUPP;
  }
  return error;
}


DRIVER_MODULE(quantis, pci, quantis_pci_driver, quantis_devclass, quantis_load, 0);


static int quantis_pci_probe(device_t dev)
{
  QUANTIS_DEBUG("Vendor ID : 0x%x\nDevice ID : 0x%x\n", pci_get_vendor(dev), pci_get_device(dev));
  if (pci_get_vendor(dev) == VENDOR_ID_HESSO && pci_get_device(dev) == DEVICE_ID_QUANTIS_PCI) 
  {
    QUANTIS_DEBUG("Found device\n");
    device_set_desc(dev, "Quantis Random Number Generator");
    return (BUS_PROBE_VENDOR);
  }
  return ENXIO;
}


static int quantis_pci_attach(device_t device)
{
  int error;
  struct quantis_pci_device *scp = DEVICE2SOFTC(device);

  error = quantis_attach(device, scp);
  if (error)
  {
    quantis_pci_detach(device);
  }
  
  return error;
}


static int quantis_pci_detach(device_t device)
{
  int error;
  struct quantis_pci_device *scp = DEVICE2SOFTC(device);

  error = quantis_detach(device, scp);
  return error;
}


/** The attachment routines reserves the resources for the register
 ** page. Reset the
 ** board and initialize the modules, and increment the card counter.
 **/
static int quantis_attach(device_t device, struct quantis_pci_device* scp)
{
  int  unit  = device_get_unit(device);

  if (quantis_allocate_resources(device))
  {
    goto errexit;
  }

  quantis_rng_reset(scp);
  quantis_rng_enable_modules(scp, quantis_rng_modules_mask(scp));

  scp->dev = make_dev(&quantis_cdevsw, 0, UID_ROOT, GID_WHEEL, 0666, "qrandom%d", unit);
  scp->dev->si_drv1 = scp;

  mtx_lock(&quantis_mutex);
  card_count++;
  mtx_unlock(&quantis_mutex);
  return 0;

errexit:
  quantis_detach(device, scp);
  return (ENXIO);
}


/** On detachment, decrement the card count. **/
static int quantis_detach(device_t device, struct quantis_pci_device *scp)
{
  mtx_lock(&quantis_mutex);
  card_count--;
  mtx_unlock(&quantis_mutex);
  return quantis_deallocate_resources(device);
}

/** The start address and the length of the register page are held in
 ** the second entry of the Base Address Registers configuration
 ** page. The driver allocates a resource to reference the register
 ** page, and activates it.
 **/
static int quantis_allocate_resources(device_t device)
{
  int  error;
  struct quantis_pci_device *scp = DEVICE2SOFTC(device);

  mtx_init(&scp->mutex, "Quantis Device Mutex", NULL, MTX_DEF);
  scp->regid = PCIR_BAR(1);
  scp->regtype = SYS_RES_MEMORY;
  scp->reg = bus_alloc_resource(device, scp->regtype, &scp->regid, 0ul, ~0ul,  QUANTIS_REG_LENGTH, RF_ACTIVE);
  if (scp->reg == NULL)
  {
    goto errexit;
  }
  return (0);

errexit:
  error = ENXIO;
  quantis_deallocate_resources(device);
  return (ENXIO);
}


/** Free the resource pointing to the register page. **/
static int quantis_deallocate_resources(device_t device)
{
  struct quantis_pci_device *scp = DEVICE2SOFTC(device);

  if (scp->reg != 0) 
  {
    bus_deactivate_resource(device, scp->regtype, scp->regid, scp->reg);
    bus_release_resource(device, scp->regtype, scp->regid, scp->reg);
    scp->reg = 0;
  }
  
  if (scp->dev)
  {
    destroy_dev(scp->dev);
  }
  
  mtx_destroy(&scp->mutex);

  return (0);
}

/** We implement the same IOCTL calls as the linux driver.
 **/

static int quantis_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *t)
{
  int  error = 0;
  u_int32_t  tmp;
  struct quantis_pci_device *scp = DEV2SOFTC(dev);

  mtx_lock(&scp->mutex);
  switch (cmd) 
  {
    case QUANTIS_IOCTL_GET_DRIVER_VERSION:
      QUANTIS_DEBUG("ioctl get driver version\n");
      tmp = QUANTIS_PCI_DRIVER_VERSION;
      memcpy(data, &tmp, sizeof(tmp));
      break;

    case QUANTIS_IOCTL_GET_CARD_COUNT:
      QUANTIS_DEBUG("ioctl get card count\n");
      mtx_lock(&quantis_mutex);
      memcpy(data, &card_count, sizeof(card_count));
      mtx_unlock(&quantis_mutex);
      break;

    case QUANTIS_IOCTL_GET_MODULES_MASK:
      QUANTIS_DEBUG("ioctl get module mask\n");
      tmp = quantis_rng_modules_mask(scp);
      memcpy(data, &tmp, sizeof(tmp));
      break;

    case QUANTIS_IOCTL_GET_BOARD_VERSION:
      QUANTIS_DEBUG("ioctl get board version\n");
      tmp = quantis_rng_version(scp);
      memcpy(data, &tmp, sizeof(tmp));
      break;

    case QUANTIS_IOCTL_RESET_BOARD:
      QUANTIS_DEBUG("ioctl reset board\n");
      quantis_rng_reset(scp);
      break;

    case QUANTIS_IOCTL_ENABLE_MODULE:
      QUANTIS_DEBUG("ioctl enable module\n");
      memcpy(&tmp, data, sizeof(tmp));
      quantis_rng_enable_modules(scp, tmp);
      break;

    case QUANTIS_IOCTL_DISABLE_MODULE:
      QUANTIS_DEBUG("ioctl disable module\n");
      memcpy(&tmp, data, sizeof(tmp));
      quantis_rng_disable_modules(scp, tmp);
      break;

    case QUANTIS_IOCTL_GET_MODULES_STATUS:
      QUANTIS_DEBUG("ioctl get modules status\n");
      tmp = quantis_rng_modules_status(scp);
      memcpy(data, &tmp, sizeof(tmp));
      break;

    default:
      QUANTIS_DEBUG("ioctl %ld\n", cmd);
      error = ENXIO;
  }
  mtx_unlock(&scp->mutex);
  return (error);
}

static int quantis_open(struct cdev *dev, int oflags, int devtype, struct thread *t)
{
  return (0);
}

static int quantis_close(struct cdev *dev, int fflag, int devtype, struct thread *t)
{
  return (0);
}

/** Fill the buffer with random data and copy it to userland. **/
static int quantis_read(struct cdev *dev, struct uio *uio, int ioflag)
{
  QUANTIS_DEBUG("got to read %ld bytes", uio->uio_resid);
  struct quantis_pci_device *scp = DEV2SOFTC(dev);
  int  rv, amnt, read;
  rv = 0;
  mtx_lock(&scp->mutex);
  while (uio->uio_resid > 0) 
  {
    amnt = MIN(uio->uio_resid, sizeof(scp->buffer));
    read = quantis_rng_read(scp, scp->buffer, amnt);
    QUANTIS_DEBUG("Wanted to read %d bytes, read %d bytes\n", amnt, read);
    if (read < amnt) 
    {
      QUANTIS_DEBUG("Could not read enough...");
      rv = ENXIO;
      break;
    }
    rv = uiomove(scp->buffer, amnt, uio);
    if (rv != 0)
    {
      break;
    }
  }
  mtx_unlock(&scp->mutex);
  return (rv);
}


static int quantis_write(struct cdev *dev, struct uio *uio, int ioflag)
{
  return (0);
}

